From 63fac49dd31b8cf56c37fce8f450586b2223de07 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Wed, 27 Nov 2024 17:33:56 +0800 Subject: [PATCH] refactor(ios): rewrite snapshot's retrieve and restore functionality --- framework/ios/base/bridge/HippyBridge.h | 7 - framework/ios/base/bridge/HippyBridge.mm | 31 --- renderer/native/ios/renderer/HippyRootView.h | 10 + renderer/native/ios/renderer/HippyRootView.mm | 54 +++++ tests/ios/HippyRootViewTest.mm | 192 ++++++++++++++++++ 5 files changed, 256 insertions(+), 38 deletions(-) create mode 100644 tests/ios/HippyRootViewTest.mm diff --git a/framework/ios/base/bridge/HippyBridge.h b/framework/ios/base/bridge/HippyBridge.h index c4151c1b55a..38cb804a2be 100644 --- a/framework/ios/base/bridge/HippyBridge.h +++ b/framework/ios/base/bridge/HippyBridge.h @@ -340,13 +340,6 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); @property (nonatomic, copy, readonly) NSArray *moduleClasses; -#pragma mark - Snapshot - -- (NSData *)snapShotData; - -- (void)setSnapShotData:(NSData *)data; - - #pragma mark - App UI State Related /// NightMode or not, default is NO. diff --git a/framework/ios/base/bridge/HippyBridge.mm b/framework/ios/base/bridge/HippyBridge.mm index 845be2ddcdb..8f7eba783fd 100644 --- a/framework/ios/base/bridge/HippyBridge.mm +++ b/framework/ios/base/bridge/HippyBridge.mm @@ -604,12 +604,6 @@ - (void)handleBuffer:(NSArray *)buffer { NSArray *methodIDs = [HippyConvert NSNumberArray:requestsArray[HippyBridgeFieldMethodIDs]]; NSArray *paramsArrays = [HippyConvert NSArrayArray:requestsArray[HippyBridgeFieldParams]]; - int64_t callID = -1; - - if (requestsArray.count > 3) { - callID = [requestsArray[HippyBridgeFieldCallID] longLongValue]; - } - if (HIPPY_DEBUG && (moduleIDs.count != methodIDs.count || moduleIDs.count != paramsArrays.count)) { HippyLogError(@"Invalid data message - all must be length: %lu", (unsigned long)moduleIDs.count); return; @@ -659,9 +653,6 @@ - (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:( // hippy will send 'destroyInstance' event to JS. // JS may call actions after that. // so HippyBatchBridge needs to be valid - // if (!_valid) { - // return nil; - // } BOOL isValid = [self isValid]; NSArray *moduleDataByID = [_moduleSetup moduleDataByID]; if (moduleID >= [moduleDataByID count]) { @@ -960,28 +951,6 @@ - (void)sendEvent:(NSString *)eventName params:(NSDictionary *_Nullable)params { args:@{@"eventName": eventName, @"extra": params ? : @{}}]; } -- (NSData *)snapShotData { - auto rootNode = _javaScriptExecutor.pScope->GetRootNode().lock(); - if (!rootNode) { - return nil; - } - std::string data = hippy::DomManager::GetSnapShot(rootNode); - return [NSData dataWithBytes:reinterpret_cast(data.c_str()) length:data.length()]; -} - -- (void)setSnapShotData:(NSData *)data { - auto domManager = _javaScriptExecutor.pScope->GetDomManager().lock(); - if (!domManager) { - return; - } - auto rootNode = _javaScriptExecutor.pScope->GetRootNode().lock(); - if (!rootNode) { - return; - } - std::string string(reinterpret_cast([data bytes]), [data length]); - domManager->SetSnapShot(rootNode, string); -} - #pragma mark - RootView Related diff --git a/renderer/native/ios/renderer/HippyRootView.h b/renderer/native/ios/renderer/HippyRootView.h index f09829bc04a..9da8587ab88 100644 --- a/renderer/native/ios/renderer/HippyRootView.h +++ b/renderer/native/ios/renderer/HippyRootView.h @@ -184,6 +184,16 @@ extern NSString *const HippySecondaryBundleDidLoadNotification DEPRECATED_MSG_AT /// you don't want any touch to be registered as soon as the UIScrollView starts scrolling. - (void)cancelTouches; + +#pragma mark - Snapshot + +/// Retrieves the current snapshot data. +- (nullable NSData *)retrieveCurrentSnapshotData; + +/// Restores the snapshot data with the provided NSData object. +/// - Parameter data: NSData object +- (BOOL)restoreSnapshotData:(nullable NSData *)data; + @end NS_ASSUME_NONNULL_END diff --git a/renderer/native/ios/renderer/HippyRootView.mm b/renderer/native/ios/renderer/HippyRootView.mm index 95e90d40da1..6cb26cf5cc8 100644 --- a/renderer/native/ios/renderer/HippyRootView.mm +++ b/renderer/native/ios/renderer/HippyRootView.mm @@ -24,15 +24,19 @@ #import "HippyAssert.h" #import "HippyView.h" #import "UIView+Hippy.h" +#import "UIView+Render.h" +#import "HippyComponentMap.h" #import "HippyInvalidating.h" #import "HippyBridge.h" #import "Hippybridge+PerformanceAPI.h" #import "HippyBridge+BundleLoad.h" #import "HippyUIManager.h" +#import "HippyUIManager+Private.h" #import "HippyUtils.h" #import "HippyDeviceBaseInfo.h" #import "HippyTouchHandler.h" #import "HippyJSExecutor.h" +#import "dom/dom_manager.h" #include // Sent when the first subviews are added to the root view @@ -78,6 +82,16 @@ - (instancetype)init NS_UNAVAILABLE; /// Unvaliable, use designated initializer. + (instancetype)new NS_UNAVAILABLE; + +#pragma mark - Snapshot + +/// Retrieves the current snapshot data. +- (nullable NSData *)retrieveCurrentSnapshotData; + +/// Restores the snapshot data with the provided NSData object. +/// - Parameter data: NSData object +- (BOOL)restoreSnapshotData:(nullable NSData *)data; + @end #pragma mark - HippyRootView @@ -361,6 +375,17 @@ - (void)onHostControllerTransitionedToSize:(CGSize)size { userInfo:@{HippyHostControllerSizeKeyNewSize : @(size)}]; } + +#pragma mark - Snapshot + +- (NSData *)retrieveCurrentSnapshotData { + return [self.contentView retrieveCurrentSnapshotData]; +} + +- (BOOL)restoreSnapshotData:(NSData *)data { + return [self.contentView restoreSnapshotData:data]; +} + @end @@ -441,5 +466,34 @@ - (void)invalidate { } } +#pragma mark - Snapshot + +- (NSData *)retrieveCurrentSnapshotData { + auto rootNode = [self.uiManager.viewRegistry rootNodeForTag:self.hippyTag].lock(); + if (!rootNode) { + return nil; + } + std::string data = hippy::DomManager::GetSnapShot(rootNode); + return [NSData dataWithBytes:data.c_str() length:data.length()]; +} + +- (BOOL)restoreSnapshotData:(NSData *)data { + HippyUIManager *uiManager = self.uiManager; + if (!uiManager || !data) { + return NO; + } + auto domManager = [uiManager domManager].lock(); + if (!domManager) { + return NO; + } + auto rootNode = [uiManager.viewRegistry rootNodeForTag:self.hippyTag].lock(); + if (!rootNode) { + return NO; + } + std::string string(reinterpret_cast([data bytes]), [data length]); + bool result = domManager->SetSnapShot(rootNode, string); + HippyLogInfo(@"Snapshot restore result:%d for root:%@", result, self.hippyTag); + return result; +} @end diff --git a/tests/ios/HippyRootViewTest.mm b/tests/ios/HippyRootViewTest.mm new file mode 100644 index 00000000000..3afd2818629 --- /dev/null +++ b/tests/ios/HippyRootViewTest.mm @@ -0,0 +1,192 @@ +/*! + * iOS SDK + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import +#import +#import +#import +#import +#import "HippyComponentMap.h" +#import "HippyUIManager+Private.h" +#import "HippyJSEnginesMapper.h" +#import "dom/root_node.h" +#import + + +@interface UIView (HippyUIManagerUnitTest) + +/// Bind UIView with HippyUIManager, for UnitTest +/// - Parameter uiManager: HippyUIManager instance +- (void)setUiManager:(HippyUIManager *)uiManager; + +@end + + +@interface HippyRootContentView : HippyView + +/// Init method +- (instancetype)initWithFrame:(CGRect)frame + bridge:(HippyBridge *)bridge + hippyTag:(NSNumber *)hippyTag + sizeFlexiblity:(HippyRootViewSizeFlexibility)sizeFlexibility; + +#pragma mark - Snapshot + +/// Retrieves the current snapshot data. +- (nullable NSData *)retrieveCurrentSnapshotData; + +/// Restores the snapshot data with the provided NSData object. +/// - Parameter data: NSData object +- (BOOL)restoreSnapshotData:(nullable NSData *)data; + +@end + + +@interface HippyRootView (UnitTest) + +/// ContentView for HippyRootView +@property (nonatomic, strong) HippyRootContentView *contentView; + +@end + +/// Root tag for test +static NSNumber *const kHippyTestRootTag = @10; + + +@interface HippyRootViewTest : XCTestCase + +/// Mock rootView +@property (nonatomic, strong) HippyRootView *mockRootView; +/// Mock HippyRootContentView +@property (nonatomic, strong) HippyRootContentView *mockContentView; +/// UIManager instance +@property (nonatomic, strong) HippyUIManager *uiManager; + +@end + +@implementation HippyRootViewTest + +- (void)setUp { + [super setUp]; + HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; + HippyRootView *rootView = [[HippyRootView alloc] initWithBridge:bridge moduleName:@"Test" initialProperties:nil delegate:nil]; + self.mockRootView = OCMPartialMock(rootView); + + // Create ContentView's mock instance + HippyRootContentView *contentView = [[HippyRootContentView alloc] initWithFrame:CGRectZero + bridge:bridge + hippyTag:kHippyTestRootTag + sizeFlexiblity:HippyRootViewSizeFlexibilityNone]; + self.mockContentView = OCMPartialMock(contentView); + OCMStub([self.mockRootView contentView]).andReturn(self.mockContentView); + + // Create UIManager's instance + HippyUIManager *uiManager = [[HippyUIManager alloc] initWithBridge:bridge]; + ((HippyRootContentView *)self.mockContentView).uiManager = uiManager; + self.uiManager = uiManager; +} + +- (void)tearDown { + [(id)self.mockContentView stopMocking]; + [(id)self.mockRootView stopMocking]; + [super tearDown]; +} + +- (void)testRetrieveCurrentSnapshotData_WhenRootNotNil { + // Set rootTag + OCMStub([self.mockContentView hippyTag]).andReturn(kHippyTestRootTag); + auto rootNode = std::make_shared(kHippyTestRootTag.unsignedIntValue); + [self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag]; + // Retrive snapshot + NSData *result = [self.mockRootView retrieveCurrentSnapshotData]; + OCMVerify([self.mockContentView retrieveCurrentSnapshotData]); + XCTAssertNotNil(result); +} + +- (void)testRetrieveCurrentSnapshotData_WhenRootIsNil { + // Set test invalid rootTag @0 + OCMStub([self.mockContentView hippyTag]).andReturn(@0); + auto rootNode = std::make_shared(kHippyTestRootTag.unsignedIntValue); + [self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag]; + // Retrive snapshot + NSData *result = [self.mockRootView retrieveCurrentSnapshotData]; + OCMVerify([self.mockContentView retrieveCurrentSnapshotData]); + XCTAssertNil(result); +} + +- (void)testRestoreSnapshotData_WhenDataIsNil_ReturnsNO { + NSData *data = nil; + BOOL result = [self.mockRootView restoreSnapshotData:data]; + OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]); + XCTAssertFalse(result); +} + +- (void)testRestoreSnapshotData_WhenUIManagerIsNil_ReturnsNO { + self.mockContentView.uiManager = nil; + NSData *data = [NSData dataWithBytes:"mocked data" length:strlen("mocked data")]; + BOOL result = [self.mockRootView restoreSnapshotData:data]; + OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]); + XCTAssertFalse(result); +} + +- (void)testRestoreSnapshotData_WhenRootNodeIsNil_ReturnsNO { + // Set invalid rootTag + OCMStub([self.mockContentView hippyTag]).andReturn(@0); + auto rootNode = std::make_shared(kHippyTestRootTag.unsignedIntValue); + [self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag]; + + // Set dom_manager + auto engineResource = [[HippyJSEnginesMapper defaultInstance] createJSEngineResourceForKey:@"testKey"]; + auto domManager = engineResource->GetDomManager(); + [self.mockContentView.uiManager setDomManager:domManager]; + + // Restore snapshot + NSData *data = [NSData dataWithBytes:"mocked data" length:strlen("mocked data")]; + BOOL result = [self.mockRootView restoreSnapshotData:data]; + OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]); + XCTAssertFalse(result); +} + +- (void)testRestoreSnapshotData_WhenValidData_ReturnsYES { + // Set rootTag + OCMStub([self.mockContentView hippyTag]).andReturn(kHippyTestRootTag); + auto rootNode = std::make_shared(kHippyTestRootTag.unsignedIntValue); + [self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag]; + + // Retrive snapshot + NSData *snapshotData = [self.mockRootView retrieveCurrentSnapshotData]; + OCMVerify([self.mockContentView retrieveCurrentSnapshotData]); + + // Set dom_manager + auto engineResource = [[HippyJSEnginesMapper defaultInstance] createJSEngineResourceForKey:@"testKey"]; + auto domManager = engineResource->GetDomManager(); + [self.mockContentView.uiManager setDomManager:domManager]; + + // Restore snapshot + BOOL result = [self.mockRootView restoreSnapshotData:snapshotData]; + OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]); + XCTAssertTrue(result); +} + + +@end