diff --git a/CriteoAdViewer/Sources/AdViewer/AdViewerViewController.swift b/CriteoAdViewer/Sources/AdViewer/AdViewerViewController.swift index 8b86568a..f8d5580c 100644 --- a/CriteoAdViewer/Sources/AdViewer/AdViewerViewController.swift +++ b/CriteoAdViewer/Sources/AdViewer/AdViewerViewController.swift @@ -104,6 +104,14 @@ class AdViewerViewController: FormViewController { self.updateAdConfig() } + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .landscape + } + + override var shouldAutorotate: Bool { + return false + } + private func advancedSection() -> Section { Section("Advanced options") { $0.hidden = .function( diff --git a/CriteoPublisherSdk/CriteoPublisherSdk.xcodeproj/project.pbxproj b/CriteoPublisherSdk/CriteoPublisherSdk.xcodeproj/project.pbxproj index 5f92dbec..30273ec7 100644 --- a/CriteoPublisherSdk/CriteoPublisherSdk.xcodeproj/project.pbxproj +++ b/CriteoPublisherSdk/CriteoPublisherSdk.xcodeproj/project.pbxproj @@ -275,6 +275,9 @@ A82D7DF02A04FD05001302A8 /* CR_MRAIDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D7DEF2A04FD05001302A8 /* CR_MRAIDUtils.swift */; }; A82D7DF22A04FEAB001302A8 /* CRMRAIDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D7DF12A04FEAB001302A8 /* CRMRAIDUtils.swift */; }; A82D7DF42A0A3ED4001302A8 /* MRAIDUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D7DF32A0A3ED4001302A8 /* MRAIDUtilsTests.swift */; }; + A8304F532A77D11A00ABB951 /* MRAIDFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8304F522A77D11A00ABB951 /* MRAIDFeature.swift */; }; + A8341B3F2AB09A34004AF48E /* MRAIDDeviceOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8341B3E2AB09A34004AF48E /* MRAIDDeviceOrientation.swift */; }; + A8341B432AB09F09004AF48E /* MRAIDOrientationPropertiesMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8341B422AB09F09004AF48E /* MRAIDOrientationPropertiesMessage.swift */; }; A83BB58A29BAEB69002A63B6 /* ActionRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83BB58929BAEB69002A63B6 /* ActionRepresentable.swift */; }; A83BB58C29BAEBC9002A63B6 /* MRAIDLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83BB58B29BAEBC9002A63B6 /* MRAIDLog.swift */; }; A83BB58E29BAEC17002A63B6 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83BB58D29BAEC17002A63B6 /* LogLevel.swift */; }; @@ -283,8 +286,17 @@ A83BB59829BB1F77002A63B6 /* CRLogUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = A83BB59629BB1F77002A63B6 /* CRLogUtil.h */; }; A83BB59929BB1F77002A63B6 /* CRLogUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = A83BB59729BB1F77002A63B6 /* CRLogUtil.m */; }; A83BB59B29BF6B3F002A63B6 /* MRAIDURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83BB59A29BF6B3F002A63B6 /* MRAIDURLHandler.swift */; }; + A83C4A2B2A80EDAB0033687C /* MRAIDPlayVideoMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83C4A2A2A80EDAB0033687C /* MRAIDPlayVideoMessage.swift */; }; + A84FF8932AB8892500DD3AE2 /* MRAIDOrientationPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84FF8922AB8892500DD3AE2 /* MRAIDOrientationPropertiesTests.swift */; }; A879179329A7230F00A3B798 /* CRMRAIDHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A879179229A7230F00A3B798 /* CRMRAIDHandler.swift */; }; A879179629A771CF00A3B798 /* SwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A879179529A771CF00A3B798 /* SwiftExtensions.swift */; }; + A8C3DF392A8E062100B6A4BE /* MRAIDResizeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C3DF382A8E062100B6A4BE /* MRAIDResizeMessage.swift */; }; + A8C3DF3D2A8E071700B6A4BE /* MRAIDCustomClosePosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C3DF3C2A8E071700B6A4BE /* MRAIDCustomClosePosition.swift */; }; + A8C3DF3F2A935D1C00B6A4BE /* MRAIDResizeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C3DF3E2A935D1C00B6A4BE /* MRAIDResizeHandler.swift */; }; + A8C3DF412A94EE3F00B6A4BE /* MRAIDResizeContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C3DF402A94EE3F00B6A4BE /* MRAIDResizeContainerView.swift */; }; + A8C3DF432A94FE4800B6A4BE /* CRPlacementType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C3DF422A94FE4800B6A4BE /* CRPlacementType.swift */; }; + A8C3DF452A94FE6800B6A4BE /* CRMRAIDHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C3DF442A94FE6800B6A4BE /* CRMRAIDHandlerDelegate.swift */; }; + A8C3DF4F2A9E256200B6A4BE /* MRAIDResizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C3DF4E2A9E256200B6A4BE /* MRAIDResizeTests.swift */; }; A8ABDE462ACC31DA00E82CDE /* CR_SKAdNetworkHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = A8ABDE442ACC31DA00E82CDE /* CR_SKAdNetworkHandler.h */; }; A8ABDE472ACC31DA00E82CDE /* CR_SKAdNetworkHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A8ABDE452ACC31DA00E82CDE /* CR_SKAdNetworkHandler.m */; }; A8ABDE552AD5460500E82CDE /* CR_SKAdNetworkFidelityParameter.h in Headers */ = {isa = PBXBuildFile; fileRef = A8ABDE532AD5460500E82CDE /* CR_SKAdNetworkFidelityParameter.h */; }; @@ -293,7 +305,6 @@ A8E22E1B29DD75DF0094FFA4 /* MRAIDExpandMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8E22E1A29DD75DF0094FFA4 /* MRAIDExpandMessage.swift */; }; A8E22E1D29DEFD4A0094FFA4 /* MRAIDState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8E22E1C29DEFD4A0094FFA4 /* MRAIDState.swift */; }; A8E22E1F29DF06250094FFA4 /* CRFulllScreenContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8E22E1E29DF06250094FFA4 /* CRFulllScreenContainer.swift */; }; - A8E967D6299F4B3D00BE226A /* CRMRAIDConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = A8E967D5299F4B3D00BE226A /* CRMRAIDConstants.h */; }; B1BD859C4CDDB6DB1B906C62 /* libPods-CriteoPublisherSdkTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 92039F91AE1122C08CF8EC36 /* libPods-CriteoPublisherSdkTests.a */; }; C07AB0222733EAA918843728 /* CR_NativeAssets+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = C07ABBC696D95F756547DA86 /* CR_NativeAssets+Testing.m */; }; C07AB058AEB4030B2EBD4D15 /* CR_RemoteLogRecordSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = C07AB0DC260182010824A534 /* CR_RemoteLogRecordSerializer.h */; }; @@ -731,6 +742,9 @@ A82D7E1D2A20B28E001302A8 /* CriteoPublisherSdkMC1.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CriteoPublisherSdkMC1.xctestplan; sourceTree = ""; }; A82D7E242A20B28E001302A8 /* CriteoPublisherSdkMC0.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CriteoPublisherSdkMC0.xctestplan; sourceTree = ""; }; A82D7E262A20B28E001302A8 /* CriteoPublisherSdkMC2.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CriteoPublisherSdkMC2.xctestplan; sourceTree = ""; }; + A8304F522A77D11A00ABB951 /* MRAIDFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDFeature.swift; sourceTree = ""; }; + A8341B3E2AB09A34004AF48E /* MRAIDDeviceOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDDeviceOrientation.swift; sourceTree = ""; }; + A8341B422AB09F09004AF48E /* MRAIDOrientationPropertiesMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDOrientationPropertiesMessage.swift; sourceTree = ""; }; A83BB58929BAEB69002A63B6 /* ActionRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRepresentable.swift; sourceTree = ""; }; A83BB58B29BAEBC9002A63B6 /* MRAIDLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDLog.swift; sourceTree = ""; }; A83BB58D29BAEC17002A63B6 /* LogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = ""; }; @@ -739,9 +753,18 @@ A83BB59629BB1F77002A63B6 /* CRLogUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CRLogUtil.h; sourceTree = ""; }; A83BB59729BB1F77002A63B6 /* CRLogUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CRLogUtil.m; sourceTree = ""; }; A83BB59A29BF6B3F002A63B6 /* MRAIDURLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDURLHandler.swift; sourceTree = ""; }; + A83C4A2A2A80EDAB0033687C /* MRAIDPlayVideoMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDPlayVideoMessage.swift; sourceTree = ""; }; + A84FF8922AB8892500DD3AE2 /* MRAIDOrientationPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDOrientationPropertiesTests.swift; sourceTree = ""; }; A863B2D02B345281006E6476 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; A879179229A7230F00A3B798 /* CRMRAIDHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRMRAIDHandler.swift; sourceTree = ""; }; A879179529A771CF00A3B798 /* SwiftExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExtensions.swift; sourceTree = ""; }; + A8C3DF382A8E062100B6A4BE /* MRAIDResizeMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDResizeMessage.swift; sourceTree = ""; }; + A8C3DF3C2A8E071700B6A4BE /* MRAIDCustomClosePosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDCustomClosePosition.swift; sourceTree = ""; }; + A8C3DF3E2A935D1C00B6A4BE /* MRAIDResizeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDResizeHandler.swift; sourceTree = ""; }; + A8C3DF402A94EE3F00B6A4BE /* MRAIDResizeContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDResizeContainerView.swift; sourceTree = ""; }; + A8C3DF422A94FE4800B6A4BE /* CRPlacementType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRPlacementType.swift; sourceTree = ""; }; + A8C3DF442A94FE6800B6A4BE /* CRMRAIDHandlerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRMRAIDHandlerDelegate.swift; sourceTree = ""; }; + A8C3DF4E2A9E256200B6A4BE /* MRAIDResizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDResizeTests.swift; sourceTree = ""; }; A8ABDE442ACC31DA00E82CDE /* CR_SKAdNetworkHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CR_SKAdNetworkHandler.h; sourceTree = ""; }; A8ABDE452ACC31DA00E82CDE /* CR_SKAdNetworkHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CR_SKAdNetworkHandler.m; sourceTree = ""; }; A8ABDE532AD5460500E82CDE /* CR_SKAdNetworkFidelityParameter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CR_SKAdNetworkFidelityParameter.h; sourceTree = ""; }; @@ -750,7 +773,6 @@ A8E22E1A29DD75DF0094FFA4 /* MRAIDExpandMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDExpandMessage.swift; sourceTree = ""; }; A8E22E1C29DEFD4A0094FFA4 /* MRAIDState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRAIDState.swift; sourceTree = ""; }; A8E22E1E29DF06250094FFA4 /* CRFulllScreenContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRFulllScreenContainer.swift; sourceTree = ""; }; - A8E967D5299F4B3D00BE226A /* CRMRAIDConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CRMRAIDConstants.h; sourceTree = ""; }; C07AB093A865F67283614A5D /* CRMediaView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CRMediaView.m; sourceTree = ""; }; C07AB0DC260182010824A534 /* CR_RemoteLogRecordSerializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CR_RemoteLogRecordSerializer.h; sourceTree = ""; }; C07AB0F74CBE74DC33F6ED1C /* CR_RemoteLogRecordTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CR_RemoteLogRecordTests.m; path = Logging/CR_RemoteLogRecordTests.m; sourceTree = ""; }; @@ -1576,6 +1598,15 @@ path = TestPlans; sourceTree = ""; }; + A8341B3D2AB09A1D004AF48E /* OrientationProperties */ = { + isa = PBXGroup; + children = ( + A8341B3E2AB09A34004AF48E /* MRAIDDeviceOrientation.swift */, + A8341B422AB09F09004AF48E /* MRAIDOrientationPropertiesMessage.swift */, + ); + path = OrientationProperties; + sourceTree = ""; + }; A83BB58829BAEB3A002A63B6 /* Logging */ = { isa = PBXGroup; children = ( @@ -1600,12 +1631,16 @@ A84F0D222982AFF100756E68 /* MRAID */ = { isa = PBXGroup; children = ( + A8341B3D2AB09A1D004AF48E /* OrientationProperties */, + A8C3DF3B2A8E070500B6A4BE /* Resize */, A83BB59129BAEE5F002A63B6 /* MessageHandlers */, A83BB58829BAEB3A002A63B6 /* Logging */, A879179429A7719A00A3B798 /* Utils */, - A8E967D5299F4B3D00BE226A /* CRMRAIDConstants.h */, A879179229A7230F00A3B798 /* CRMRAIDHandler.swift */, A879179529A771CF00A3B798 /* SwiftExtensions.swift */, + A8304F522A77D11A00ABB951 /* MRAIDFeature.swift */, + A8C3DF422A94FE4800B6A4BE /* CRPlacementType.swift */, + A8C3DF442A94FE6800B6A4BE /* CRMRAIDHandlerDelegate.swift */, ); path = MRAID; sourceTree = ""; @@ -1619,10 +1654,30 @@ path = Utils; sourceTree = ""; }; - A8E22E1929DD75CC0094FFA4 /* Data */ = { + A8C3DF3A2A8E062600B6A4BE /* Message */ = { isa = PBXGroup; children = ( + A83C4A2A2A80EDAB0033687C /* MRAIDPlayVideoMessage.swift */, + A8C3DF382A8E062100B6A4BE /* MRAIDResizeMessage.swift */, A8E22E1A29DD75DF0094FFA4 /* MRAIDExpandMessage.swift */, + ); + path = Message; + sourceTree = ""; + }; + A8C3DF3B2A8E070500B6A4BE /* Resize */ = { + isa = PBXGroup; + children = ( + A8C3DF3C2A8E071700B6A4BE /* MRAIDCustomClosePosition.swift */, + A8C3DF3E2A935D1C00B6A4BE /* MRAIDResizeHandler.swift */, + A8C3DF402A94EE3F00B6A4BE /* MRAIDResizeContainerView.swift */, + ); + path = Resize; + sourceTree = ""; + }; + A8E22E1929DD75CC0094FFA4 /* Data */ = { + isa = PBXGroup; + children = ( + A8C3DF3A2A8E062600B6A4BE /* Message */, A8E22E1C29DEFD4A0094FFA4 /* MRAIDState.swift */, ); path = Data; @@ -1634,6 +1689,8 @@ A82D7DE12A025811001302A8 /* MRAIDLogTests.swift */, A82D7DE32A027DB2001302A8 /* MRAIDHandlerTests.swift */, A82D7DF32A0A3ED4001302A8 /* MRAIDUtilsTests.swift */, + A8C3DF4E2A9E256200B6A4BE /* MRAIDResizeTests.swift */, + A84FF8922AB8892500DD3AE2 /* MRAIDOrientationPropertiesTests.swift */, ); path = MRAID; sourceTree = ""; @@ -1849,7 +1906,6 @@ 7D167014246D4D5F0097880F /* CR_HeaderBidding.h in Headers */, 650915FD225565E6001D6673 /* CRBannerView.h in Headers */, 46E3478226B0641300A161B6 /* CR_CASDefaultDataSerializer.h in Headers */, - A8E967D6299F4B3D00BE226A /* CRMRAIDConstants.h in Headers */, 4672E1E6251CDE02009F39EF /* CR_CASObjectQueue.h in Headers */, 7D00672F243CD6F900E5D50D /* CR_CASObjectQueue+ArraySet.h in Headers */, DF367C8A232974210044807B /* CRNativeAdUnit.h in Headers */, @@ -2189,6 +2245,7 @@ files = ( A879179629A771CF00A3B798 /* SwiftExtensions.swift in Sources */, DF367CAD232B310D0044807B /* CR_NativePrivacy.m in Sources */, + A8C3DF452A94FE6800B6A4BE /* CRMRAIDHandlerDelegate.swift in Sources */, A83BB59929BB1F77002A63B6 /* CRLogUtil.m in Sources */, 7DFB2B11247E75E5007CDFF3 /* CR_URLOpener.m in Sources */, A8E22E1F29DF06250094FFA4 /* CRFulllScreenContainer.swift in Sources */, @@ -2205,11 +2262,13 @@ 25BD5B622220D1A8004DE311 /* CR_BidManager.m in Sources */, 5470B17123A3BC6600C2B003 /* CR_TargetingKeys.m in Sources */, 4672E1E8251CDE02009F39EF /* CR_CASInMemoryObjectQueue.m in Sources */, + A8304F532A77D11A00ABB951 /* MRAIDFeature.swift in Sources */, 5397C0B322A0AA6C00BC72FE /* CRInterstitialAdUnit.m in Sources */, 46B9780E243782A50061FC24 /* CR_UniqueIdGenerator.m in Sources */, 4660614424754E8100690E27 /* CR_SafeMediaDownloader.m in Sources */, 5397C0B022A0AA5600BC72FE /* CRAdUnit.m in Sources */, 46507F80246C2F6900B1DA78 /* CRNativeAdView.m in Sources */, + A8C3DF432A94FE4800B6A4BE /* CRPlacementType.swift in Sources */, 25BD5B672220D1CC004DE311 /* CR_DeviceInfo.m in Sources */, 7D07DA1F23DAF6DD00ECCF02 /* CR_Ccpa.m in Sources */, 7D58583223E1F1EC0039AC56 /* CR_ThreadManager.m in Sources */, @@ -2235,14 +2294,17 @@ A8E22E1B29DD75DF0094FFA4 /* MRAIDExpandMessage.swift in Sources */, 4672E1EC251CDE03009F39EF /* CR_CASQueueFileElement.m in Sources */, A879179329A7230F00A3B798 /* CRMRAIDHandler.swift in Sources */, + A83C4A2B2A80EDAB0033687C /* MRAIDPlayVideoMessage.swift in Sources */, 7D3E426F23915E4200BAF673 /* CR_DependencyProvider.m in Sources */, 25BD5B6C2220D1EC004DE311 /* CR_CdbResponse.m in Sources */, + A8C3DF392A8E062100B6A4BE /* MRAIDResizeMessage.swift in Sources */, 5397C0B222A0AA6900BC72FE /* CRBannerAdUnit.m in Sources */, 7D1A17D4242A7A1100816D32 /* CR_GdprVersion.m in Sources */, 46C403E0257682D200E5179C /* CR_Logging.m in Sources */, 463791FA256D2B3100DC65B4 /* UIView+Criteo.m in Sources */, 25BD5B642220D1B3004DE311 /* CR_CdbBid.m in Sources */, 7D80F6222406B18F005B7BA3 /* CR_Gdpr.m in Sources */, + A8341B432AB09F09004AF48E /* MRAIDOrientationPropertiesMessage.swift in Sources */, A8ABDE472ACC31DA00E82CDE /* CR_SKAdNetworkHandler.m in Sources */, DF367C9B232AE8510044807B /* CR_NativeImage.m in Sources */, 25BD5B6E2220D201004DE311 /* NSString+CriteoUrl.m in Sources */, @@ -2252,6 +2314,7 @@ 4668FEF52459BD3A00D207AE /* CRNativeLoader.m in Sources */, 25BD5B6D2220D1F0004DE311 /* CR_NetworkManager.m in Sources */, 548968192332F319005B2659 /* CR_NativeProduct.m in Sources */, + A8C3DF3D2A8E071700B6A4BE /* MRAIDCustomClosePosition.swift in Sources */, 25BD5B6A2220D1DD004DE311 /* CR_DataProtectionConsent.m in Sources */, 463791BC256D006F00DC65B4 /* CR_SkAdNetworkParameters.m in Sources */, 25BD5B662220D1C6004DE311 /* CR_ConfigManager.m in Sources */, @@ -2265,6 +2328,7 @@ DF367CA5232B20F90044807B /* CR_NativeAdvertiser.m in Sources */, 464D6FB925480925004502C4 /* SKAdNetworkInfo.swift in Sources */, 46C403EB2577BADD00E5179C /* CR_LogMessage.m in Sources */, + A8C3DF412A94EE3F00B6A4BE /* MRAIDResizeContainerView.swift in Sources */, DF3E3D4123233936009E8CB5 /* NSArray+Criteo.m in Sources */, 53BDFDBE22EA763E0051EB40 /* NSObject+Criteo.m in Sources */, 46678031257FD82000D14B51 /* CRSKAdNetworkInfo.m in Sources */, @@ -2289,10 +2353,12 @@ 4642C9C724CAD93E0080AC4F /* CR_IntegrationRegistry.m in Sources */, C07ABC0F6A8997CD78597D6B /* CR_ImageCache.m in Sources */, C07AB11AEFF7DFF4A4638A4F /* CR_RemoteConfigRequest.m in Sources */, + A8C3DF3F2A935D1C00B6A4BE /* MRAIDResizeHandler.swift in Sources */, C07AB1021EE9DC0423DB6A20 /* CR_DisplaySizeInjector.m in Sources */, 4672E1DF251CDE02009F39EF /* CR_CASQueueFile.m in Sources */, C07AB5076241BAE89085C9A8 /* CRContextData.m in Sources */, C07AB9F79CF1495A3E493B8B /* CRUserData.m in Sources */, + A8341B3F2AB09A34004AF48E /* MRAIDDeviceOrientation.swift in Sources */, C07AB14F9DCFC0B80FADB2F1 /* CR_UserDataHolder.m in Sources */, C07AB3BECD246E49571BD23B /* CREmailHasher.m in Sources */, 461A876D2589008000BB527B /* CR_LogHandler.m in Sources */, @@ -2325,6 +2391,7 @@ A82D7DE72A027FB2001302A8 /* MRAIDLoggerMock.swift in Sources */, 7D49F9D8243B782400CF3941 /* WKWebView+Testing.m in Sources */, 7DD76EB5247D008F0090BCDC /* CR_CustomNativeAdView.m in Sources */, + A84FF8932AB8892500DD3AE2 /* MRAIDOrientationPropertiesTests.swift in Sources */, 7D58582A23E1E6BB0039AC56 /* CR_ThreadManagerWaiter.m in Sources */, 7DB5F90623AA2B2500EF5602 /* CR_NetworkCaptor.m in Sources */, 7DB4E91123953757008DE5E3 /* CRInterstitialTests.m in Sources */, @@ -2407,6 +2474,7 @@ 46FF8F972583B1A6000CFCB5 /* CR_URLRequestTests.m in Sources */, 46D707D224A245450043D66F /* CR_NativeAdTableViewController.m in Sources */, A82D7DE22A025811001302A8 /* MRAIDLogTests.swift in Sources */, + A8C3DF4F2A9E256200B6A4BE /* MRAIDResizeTests.swift in Sources */, 7D3E423A23915B6600BAF673 /* CRBannerAdUnitTests.m in Sources */, 7D0441CF2428EBA600D36C65 /* NSString+APIKeys.m in Sources */, 54F1E6BB239FA1A800985D09 /* CR_DfpCreativeViewChecker.m in Sources */, diff --git a/CriteoPublisherSdk/Sources/Configuration/CR_Config.h b/CriteoPublisherSdk/Sources/Configuration/CR_Config.h index 84722f15..1fd420dd 100644 --- a/CriteoPublisherSdk/Sources/Configuration/CR_Config.h +++ b/CriteoPublisherSdk/Sources/Configuration/CR_Config.h @@ -72,7 +72,9 @@ FOUNDATION_EXTERN NSString *const CR_ConfigConfigurationUrl; @property(nonatomic, readonly) NSString *configUrl; #pragma mark - MRAID -@property(assign, nonatomic, getter=isMRAIDEnabled) BOOL mraidEnabled; +@property(assign, nonatomic, getter=isMraidEnabled) BOOL mraidEnabled; +@property(assign, nonatomic, getter=isMraid2Enabled) BOOL mraid2Enabled; +@property(nonatomic, readonly) BOOL isMRAIDGlobalEnabled; #pragma mark - Lifecycle diff --git a/CriteoPublisherSdk/Sources/Configuration/CR_Config.m b/CriteoPublisherSdk/Sources/Configuration/CR_Config.m index c6bc3c1d..4ce7a706 100644 --- a/CriteoPublisherSdk/Sources/Configuration/CR_Config.m +++ b/CriteoPublisherSdk/Sources/Configuration/CR_Config.m @@ -74,6 +74,8 @@ - (instancetype)initWithCriteoPublisherId:(nullable NSString *)criteoPublisherId _remoteLogLevel = [userDefaults cr_valueForRemoteLogLevel]; _userDefaults = userDefaults; _mraidEnabled = [userDefaults cr_valueForMRAID]; + _mraid2Enabled = [userDefaults cr_valueForMRAID2]; + _isMRAIDGlobalEnabled = _mraidEnabled || _mraid2Enabled; _storeId = storeId; } return self; @@ -141,6 +143,16 @@ - (void)setRemoteLogLevel:(CR_LogSeverity)remoteLogLevel { [self.userDefaults cr_setValueForRemoteLogLevel:remoteLogLevel]; } +- (void)setMraidEnabled:(BOOL)mraidEnabled { + _mraidEnabled = mraidEnabled; + [self.userDefaults cr_setValueForMRAID:mraidEnabled]; +} + +- (void)setMraid2Enabled:(BOOL)mraid2Enabled { + _mraid2Enabled = mraid2Enabled; + [self.userDefaults cr_setValueForMRAID2:mraid2Enabled]; +} + + (NSDictionary *)getConfigValuesFromData:(NSData *)data { NSError *e = nil; NSMutableDictionary *configValues = [NSJSONSerialization JSONObjectWithData:data diff --git a/CriteoPublisherSdk/Sources/Configuration/CR_ConfigManager.m b/CriteoPublisherSdk/Sources/Configuration/CR_ConfigManager.m index eefd2f1a..f67584c5 100644 --- a/CriteoPublisherSdk/Sources/Configuration/CR_ConfigManager.m +++ b/CriteoPublisherSdk/Sources/Configuration/CR_ConfigManager.m @@ -99,6 +99,10 @@ - (void)refreshConfig:(CR_Config *)config { [configValues[@"mraidEnabled"] isKindOfClass:NSNumber.class]) { config.mraidEnabled = ((NSNumber *)configValues[@"mraidEnabled"]).boolValue; } + if (configValues[@"mraid2Enabled"] && + [configValues[@"mraid2Enabled"] isKindOfClass:NSNumber.class]) { + config.mraid2Enabled = ((NSNumber *)configValues[@"mraid2Enabled"]).boolValue; + } }]; } diff --git a/CriteoPublisherSdk/Sources/MRAID/CRMRAIDHandler.swift b/CriteoPublisherSdk/Sources/MRAID/CRMRAIDHandler.swift index 8ed7b039..ac5c66e8 100644 --- a/CriteoPublisherSdk/Sources/MRAID/CRMRAIDHandler.swift +++ b/CriteoPublisherSdk/Sources/MRAID/CRMRAIDHandler.swift @@ -19,70 +19,61 @@ import Foundation import WebKit +import AVKit public typealias VoidCompletion = () -> Void private struct CRMRAIDHandlerConstants { - static let viewabilityRefreshTime: Double = 0.2 -} - -@objc -public protocol CRMRAIDHandlerDelegate: AnyObject { - @objc - optional - func expand(width: Int, height: Int, url: URL?, completion: VoidCompletion?) - func close(completion: VoidCompletion?) + static let viewabilityRefreshTime: Double = 0.2 } @objc public class CRMRAIDHandler: NSObject { - private enum Constants { - static let updateDelay: CGFloat = 0.05 - static let scriptHandlerName = "criteoMraidBridge" - } - private unowned var webView: WKWebView - private unowned var delegate: CRMRAIDHandlerDelegate - private unowned var logger: CRMRAIDLogger - private var timer: Timer? - private var isViewVisible: Bool = false - private var messageHandler: MRAIDMessageHandler - private var state: MRAIDState = .loading - private static let updateDelay: CGFloat = 0.05 - private var mraidBundle: Bundle? = CRMRAIDUtils.mraidResourceBundle() - - @objc - public init( - with webView: WKWebView, - criteoLogger: CRMRAIDLogger, - urlOpener: CRExternalURLOpener, - delegate: CRMRAIDHandlerDelegate - ) { - self.logger = criteoLogger - self.webView = webView - self.messageHandler = MRAIDMessageHandler( - logHandler: MRAIDLogHandler(criteoLogger: criteoLogger), - urlHandler: CRMRAIDURLHandler(with: criteoLogger, urlOpener: urlOpener)) - self.delegate = delegate - super.init() - - self.messageHandler.delegate = self - DispatchQueue.main.async { - self.webView.configuration.userContentController.add(self, name: Constants.scriptHandlerName) + private enum Constants { + static let updateDelay: CGFloat = 0.05 + static let scriptHandlerName = "criteoMraidBridge" + static let onLoadDelay: CGFloat = 0.2 } - } - @objc - public func onAdLoad(with placementType: String) { - state = .default - DispatchQueue.main.async { [weak self] in - self?.setMaxSize() - self?.sendReadyEvent(with: placementType) - } - startViabilityNotifier() - registerDeviceOrientationListener() - } + private let webView: WKWebView + private var timer: Timer? + private var isViewVisible: Bool = false + private var messageHandler: MRAIDMessageHandler + private weak var delegate: CRMRAIDHandlerDelegate? + private var state: MRAIDState = .loading + private let logger: CRMRAIDLogger + private static let updateDelay: CGFloat = 0.05 + private var mraidBundle: Bundle? = CRMRAIDUtils.mraidResourceBundle() + private let placementType: CRPlacementType + private var orientationProperties: MRAIDOrientationProperties? + private var webViewContainer: UIView? + private var resizeHandler: MRAIDResizeHandler! + @objc + public init( + placementType: CRPlacementType, + webView: WKWebView, + criteoLogger: CRMRAIDLogger, + urlOpener: CRExternalURLOpener, + delegate: CRMRAIDHandlerDelegate? + ) { + self.placementType = placementType + self.logger = criteoLogger + self.webView = webView + self.messageHandler = MRAIDMessageHandler( + logHandler: MRAIDLogHandler(criteoLogger: criteoLogger), + urlHandler: CRMRAIDURLHandler(with: criteoLogger, urlOpener: urlOpener)) + super.init() + self.delegate = delegate + self.messageHandler.delegate = self + self.resizeHandler = MRAIDResizeHandler(webView: webView, delegate: self) + + DispatchQueue.main.async { + self.webView.configuration.userContentController.add(self, name: "criteoMraidBridge") + } + } + @objc public func send(error: String, action: String) { evaluate(javascript: "window.mraid.notifyError(\"\(error)\",\"\(action)\");") @@ -113,35 +104,89 @@ public class CRMRAIDHandler: NSObject { return state == .expanded } - @objc - public func onSuccessClose() { - notifyClosed() - state = state == .expanded ? .default : .hidden - } @objc public func inject(into html: String) -> String { return CRMRAIDUtils.build(html: html, from: mraidBundle) } - @objc - public func injectMRAID() { - guard let mraid = CRMRAIDUtils.loadMraid(from: mraidBundle) else { - logger.mraidLog(error: "could not load mraid") - return + + @objc + public func onAdLoad() { + setDefaultSupportedOrientationMask() + registerDeviceOrientationListener() + + DispatchQueue.main.asyncAfter(deadline: .now() + Constants.onLoadDelay) { [weak self] in + guard let self = self else { return } + + self.state = .default + self.setMaxSize() + self.setScreen(size: UIScreen.main.bounds.size) + self.setCurrentPosition() + self.setSupportedFeatures() + self.sendReadyEvent(with: self.placementType.placementTypeString) + self.startViabilityNotifier() + } } - let mraidScript = WKUserScript( - source: mraid, injectionTime: .atDocumentEnd, forMainFrameOnly: false) - DispatchQueue.main.async { [weak self] in - self?.webView.configuration.userContentController.addUserScript(mraidScript) + + @objc + public func onSuccessClose() { + setCurrentPosition() + notifyClosed() + /// default state is set to default only if the ad is in expanded or resized state otherwise set it to hidden. + state = (state == .expanded || state == .resized) ? .default : .hidden } - } - @objc - public func updateMraid(bundle: Bundle?) { - mraidBundle = bundle - } + + @objc + public func injectMRAID() { + guard let mraid = CRMRAIDUtils.loadMraid(from: mraidBundle) else { + logger.mraidLog(error: "could not load mraid") + return + } + + let mraidScript = WKUserScript( + source: mraid, injectionTime: .atDocumentEnd, forMainFrameOnly: false) + DispatchQueue.main.async { [weak self] in + self?.webView.configuration.userContentController.addUserScript(mraidScript) + } + } + + @objc + public func updateMraid(bundle: Bundle?) { + mraidBundle = bundle + } + + @objc + public func setCurrentPosition() { + guard let parentView = webView.cr_parentViewController()?.view else { + logger.mraidLog(error: "Could not get the parent view reference") + return + } + + let origin = webView.bounds.origin + let size = webView.bounds.size + let position = parentView.convert(origin, from: webView) + setCurrent(position: CGRect(origin: position, size: size)) + } + + @objc + public func setScreen(size: CGSize) { + evaluate(javascript: "window.mraid.setScreenSize(\(size.width),\(size.height));") + } + + @objc + public func shouldAdAutoRotate() -> Bool { + guard let orientationProperties = self.orientationProperties else { return true } + return orientationProperties.allowOrientationChange + } + + @objc + public func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { + guard let orientationProperties = self.orientationProperties else { return .all } + return orientationProperties.supportedOrietationMask + } @objc public func onDealloc() { @@ -153,111 +198,228 @@ public class CRMRAIDHandler: NSObject { // MARK: - JS message handler extension CRMRAIDHandler: WKScriptMessageHandler { - public func userContentController( - _ userContentController: WKUserContentController, - didReceive message: WKScriptMessage - ) { - messageHandler.handle(message: message.body) - } + public func userContentController( + _ userContentController: WKUserContentController, + didReceive message: WKScriptMessage + ) { + messageHandler.handle(message: message.body) + } } // MARK: - Private methods -extension CRMRAIDHandler { - fileprivate func stopViabilityNotifier() { - timer?.invalidate() - timer = nil - } +fileprivate extension CRMRAIDHandler { + func setOrientationProperties(from viewController: UIViewController) { + orientationProperties = MRAIDOrientationProperties(allowOrientationChange: viewController.shouldAutorotate, + orientationMask: viewController.supportedInterfaceOrientations) + } - fileprivate func setMaxSize() { - let size: CGSize = - webView.cr_parentViewController()?.view.bounds.size ?? UIScreen.main.bounds.size - evaluate( - javascript: - "window.mraid.setMaxSize(\(size.width), \(size.height), \(UIScreen.main.scale));") - } + func setDefaultSupportedOrientationMask() { + /// Avoid overriding the already set orientation properties + guard orientationProperties == nil else { return } + + switch placementType { + case .banner: + guard let viewController = webView.cr_rootViewController() else { return } + setOrientationProperties(from: viewController) + case .interstitial: + guard + let interstitialViewController = webView.cr_parentViewController(), + let viewController = interstitialViewController.presentingViewController else { + break + } + setOrientationProperties(from: viewController) + } + } - @objc fileprivate func setIsViewable(visible: Bool) { - evaluate(javascript: "window.mraid.setIsViewable(\"\(visible.stringValue)\");") - } + func stopViabilityNotifier() { + timer?.invalidate() + timer = nil + } - @objc fileprivate func viewabilityCheck() { - let isWebViewVisible = webView.isVisibleToUser - guard isWebViewVisible != isViewVisible else { return } + func setMaxSize() { + let size: CGSize = + webView.cr_parentViewController()?.view.bounds.size ?? UIScreen.main.bounds.size + evaluate( + javascript: + "window.mraid.setMaxSize(\(size.width), \(size.height), \(UIScreen.main.scale));") + } - isViewVisible = isWebViewVisible - setIsViewable(visible: isWebViewVisible) - } + @objc func setIsViewable(visible: Bool) { + evaluate(javascript: "window.mraid.setIsViewable(\"\(visible.stringValue)\");") + } - fileprivate func sendReadyEvent(with placement: String) { - evaluate(javascript: "window.mraid.notifyReady(\"\(placement)\");") - } + @objc func viewabilityCheck() { + let isWebViewVisible = webView.isVisibleToUser + guard isWebViewVisible != isViewVisible else { return } - fileprivate func setCurrent(position: CGRect) { - evaluate( - javascript: - "window.mraid.setCurrentPosition({x:\(position.minX), y:\(position.minY), width:\(position.width), height:\(position.height)});" - ) - } + isViewVisible = isWebViewVisible + setIsViewable(visible: isWebViewVisible) + } - fileprivate func evaluate(javascript: String) { - webView.evaluateJavaScript(javascript, completionHandler: handleJSCallback) - } + func sendReadyEvent(with placement: String) { + evaluate(javascript: "window.mraid.notifyReady(\"\(placement)\");") + } - fileprivate func handleJSCallback(_ agent: Any?, _ error: Error?) { - if let error = error { - debugPrint("error on js call: \(error)") - } else { - debugPrint("no error on js callback") + func setCurrent(position: CGRect) { + evaluate( + javascript: + "window.mraid.setCurrentPosition(\(position.minX),\(position.minY),\(position.width),\(position.height));" + ) } - } - fileprivate func notifyExpanded() { - evaluate(javascript: "window.mraid.notifyExpanded();") - } + func setSupportedFeatures() { + guard + let data = try? JSONEncoder().encode(MRAIDFeatures()), + let supportedFeaturesString = String(data: data, encoding: .utf8) else { + logger.mraidLog(error: "Could not set supported features") + return + } + evaluate(javascript: "window.mraid.setSupports(\(supportedFeaturesString));") + } - fileprivate func notifyClosed() { - evaluate(javascript: "window.mraid.notifyClosed();") - } + func evaluate(javascript: String) { + debugPrint("js: \(javascript)") + webView.evaluateJavaScript(javascript, completionHandler: handleJSCallback) + } - fileprivate func registerDeviceOrientationListener() { - NotificationCenter.default.addObserver( - self, selector: #selector(deviceOrientationDidChange), - name: UIDevice.orientationDidChangeNotification, object: nil) - } + func handleJSCallback(_ agent: Any?, _ error: Error?) { + if let error = error { + debugPrint("error on js call: \(error)") + } else { + debugPrint("no error on js callback") + } + } - @objc fileprivate func deviceOrientationDidChange() { - setMaxSize() - } + func notifyExpanded() { + evaluate(javascript: "window.mraid.notifyExpanded();") + } - fileprivate func unregisterDeviceOrientationListener() { - NotificationCenter.default.removeObserver( - self, name: UIDevice.orientationDidChangeNotification, object: nil) - } + func notifyClosed() { + evaluate(javascript: "window.mraid.notifyClosed();") + } + + func notifyResized() { + evaluate(javascript: "window.mraid.notifyResized();") + } + + func registerDeviceOrientationListener() { + NotificationCenter.default.addObserver( + self, selector: #selector(deviceOrientationDidChange), + name: UIDevice.orientationDidChangeNotification, object: nil) + } + + @objc func deviceOrientationDidChange() { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.setCurrentPosition() + self.setMaxSize() + self.setScreen(size: UIScreen.main.bounds.size) + } + } + + func unregisterDeviceOrientationListener() { + NotificationCenter.default.removeObserver( + self, name: UIDevice.orientationDidChangeNotification, object: nil) + } } // MARK: - MRAID Message delegate extension CRMRAIDHandler: MRAIDMessageHandlerDelegate { - public func didReceive(expand action: MRAIDExpandMessage) { - guard state != .expanded else { return } + public func didReceive(expand action: MRAIDExpandMessage) { + guard state != .expanded else { return } + + if state == .resized { + resizeHandler.close() + } + + delegate?.expand?(width: action.width, height: action.width, url: action.url) { [weak self] in + self?.onSuccessClose() + } + + DispatchQueue.main.asyncAfter(deadline: .now() + Constants.updateDelay) { + self.state = .expanded + self.setCurrentPosition() + self.notifyExpanded() + self.onSetOrientation() + } + } - delegate.expand?(width: action.width, height: action.width, url: action.url) { [weak self] in - self?.onSuccessClose() + func onSetOrientation() { + if #available(iOS 16.0, *) { + if let window = UIApplication.shared.connectedScenes.first as? UIWindowScene { + window.requestGeometryUpdate(.iOS(interfaceOrientations: orientationProperties?.orientationMask ?? .all)) + } else if let parentViewController = webView.cr_parentViewController() { + parentViewController.setNeedsUpdateOfSupportedInterfaceOrientations() + } + } else if let interfaceOrientation = orientationProperties?.orientationMask { + UIDevice.current.setValue(interfaceOrientation.rawValue, forKey: "orientation") + UIViewController.attemptRotationToDeviceOrientation() + } } - DispatchQueue.main.asyncAfter(deadline: .now() + CRMRAIDHandler.updateDelay) { - self.state = .expanded - self.notifyExpanded() + public func didReceiveCloseAction() { + guard state == .default || state == .expanded else { + logger.mraidLog(error: "Close action is not valid in current state: \(state)") + return + } + + delegate?.close { [weak self] in + self?.onSuccessClose() + } } - } - public func didReceiveCloseAction() { - guard state == .default || state == .expanded else { - logger.mraidLog(error: "Close action is not valid in current state: \(state)") - return + public func didReceivePlayVideoAction(with url: String) { + guard + let parentViewController = webView.cr_rootViewController(), + let videoURL = URL(string: url) + else { + logger.mraidLog(error: "Could not play video with url: \(url)") + return + } + + let playerViewController = AVPlayerViewController() + playerViewController.player = AVPlayer(url: videoURL) + + parentViewController.present(playerViewController, animated: true) { + playerViewController.player?.play() + } } - delegate.close { [weak self] in - self?.onSuccessClose() + public func didReceive(resize action: MRAIDResizeMessage) { + guard placementType == .banner else { + logger.mraidLog(error: "Resize action can be execute only for banner ad type") + return + + } + + guard resizeHandler.canResize(mraidState: state) else { + logger.mraidLog(error: "Resize action can be execute only on default or resized states") + return + } + + if state == .default { + webViewContainer = webView.superview + } + + do { + try resizeHandler.resize(with: action, webViewContainer: webViewContainer) + state = .resized + notifyResized() + } catch { + logger.mraidLog(error: "Could not resize ad.") + } + } + + public func didReceive(orientation properties: MRAIDOrientationPropertiesMessage) { + orientationProperties = MRAIDOrientationProperties(allowOrientationChange: properties.allowOrientationChange, + forceOrientation: properties.forceOrientation) + guard placementType == .interstitial || state == .expanded else { return } + onSetOrientation() + } +} + +extension CRMRAIDHandler: MRAIDResizeHandlerDelegate { + public func didCloseResizedAdView() { + onSuccessClose() } - } } diff --git a/CriteoPublisherSdk/Sources/MRAID/CRMRAIDHandlerDelegate.swift b/CriteoPublisherSdk/Sources/MRAID/CRMRAIDHandlerDelegate.swift new file mode 100644 index 00000000..5f929d7e --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/CRMRAIDHandlerDelegate.swift @@ -0,0 +1,28 @@ +// +// CRMRAIDHandlerDelegate.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation + +@objc +public protocol CRMRAIDHandlerDelegate: AnyObject { + @objc + optional + func expand(width: Int, height: Int, url: URL?, completion: VoidCompletion?) + func close(completion: VoidCompletion?) +} diff --git a/CriteoPublisherSdk/Sources/MRAID/CRPlacementType.swift b/CriteoPublisherSdk/Sources/MRAID/CRPlacementType.swift new file mode 100644 index 00000000..64b0cdfb --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/CRPlacementType.swift @@ -0,0 +1,33 @@ +// +// CRPlacementType.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation + +@objc +public enum CRPlacementType: Int { + case banner + case interstitial + + var placementTypeString: String { + switch self { + case .banner: return "inline" + case .interstitial: return "interstitial" + } + } +} diff --git a/CriteoPublisherSdk/Sources/MRAID/Logging/ActionRepresentable.swift b/CriteoPublisherSdk/Sources/MRAID/Logging/ActionRepresentable.swift index ab4d5d58..9be34ab9 100644 --- a/CriteoPublisherSdk/Sources/MRAID/Logging/ActionRepresentable.swift +++ b/CriteoPublisherSdk/Sources/MRAID/Logging/ActionRepresentable.swift @@ -29,4 +29,8 @@ public enum Action: String, Decodable { case expand case close case none + case playVideo = "play_video" + case resize + case orientationPropertiesUpdate = "orientation_properties_update" + case orientationPropertiesSet = "set_orientation_properties" } diff --git a/CriteoPublisherSdk/Sources/MRAID/MRAIDFeature.swift b/CriteoPublisherSdk/Sources/MRAID/MRAIDFeature.swift new file mode 100644 index 00000000..43ad2502 --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/MRAIDFeature.swift @@ -0,0 +1,38 @@ +// +// MRAIDFeature.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation +import MessageUI + +public struct MRAIDFeatures: Codable { + var sms: Bool = false + var tel: Bool = false + + public init() { + if + let telURL = URL(string: "tel://"), + UIApplication.shared.canOpenURL(telURL) { + self.tel = true + } + + if MFMessageComposeViewController.canSendText() { + self.sms = true + } + } +} diff --git a/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/MRAIDState.swift b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/MRAIDState.swift index edb9dcc2..b3868c3f 100644 --- a/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/MRAIDState.swift +++ b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/MRAIDState.swift @@ -24,4 +24,5 @@ public enum MRAIDState: String, Decodable { case `default` case expanded case hidden + case resized } diff --git a/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/MRAIDExpandMessage.swift b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDExpandMessage.swift similarity index 100% rename from CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/MRAIDExpandMessage.swift rename to CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDExpandMessage.swift diff --git a/CriteoPublisherSdk/Sources/MRAID/CRMRAIDConstants.h b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDPlayVideoMessage.swift similarity index 75% rename from CriteoPublisherSdk/Sources/MRAID/CRMRAIDConstants.h rename to CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDPlayVideoMessage.swift index 6d3c242c..06632e99 100644 --- a/CriteoPublisherSdk/Sources/MRAID/CRMRAIDConstants.h +++ b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDPlayVideoMessage.swift @@ -1,5 +1,5 @@ // -// CRMRAIDConstants.h +// MRAIDPlayVideoMessage.swift // CriteoPublisherSdk // // Copyright © 2018-2023 Criteo. All rights reserved. @@ -17,10 +17,9 @@ // limitations under the License. // -#ifndef CRMRAIDConstants_h -#define CRMRAIDConstants_h +import Foundation -#define CR_MRAID_PLACEMENT_BANNER @"inline" -#define CR_MRAID_PLACEMENT_INTERSTITIAL @"interstitial" - -#endif /* CRMRAIDConstants_h */ +public struct MRAIDPlayVideoMessage: Decodable { + let action: Action + let url: String +} diff --git a/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDResizeMessage.swift b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDResizeMessage.swift new file mode 100644 index 00000000..9519ed25 --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/Data/Message/MRAIDResizeMessage.swift @@ -0,0 +1,61 @@ +// +// MRAIDResizeMessage.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation + +public struct MRAIDResizeMessage: Decodable { + let action: Action + let width: Int + let height: Int + let offsetX: Int + let offsetY: Int + let customClosePosition: MRAIDCustomClosePosition + let allowOffscreen: Bool + + enum CodingKeys: CodingKey { + case action + case width + case height + case offsetX + case offsetY + case customClosePosition + case allowOffscreen + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.action = try container.decode(Action.self, forKey: .action) + self.width = try container.decode(Int.self, forKey: .width) + self.height = try container.decode(Int.self, forKey: .height) + self.offsetX = try container.decode(Int.self, forKey: .offsetX) + self.offsetY = try container.decode(Int.self, forKey: .offsetY) + self.customClosePosition = try container.decodeIfPresent(MRAIDCustomClosePosition.self, forKey: .customClosePosition) ?? .topRight + self.allowOffscreen = try container.decodeIfPresent(Bool.self, forKey: .allowOffscreen) ?? true + } + + init(action: Action, width: Int, height: Int, offsetX: Int, offsetY: Int, customClosePosition: MRAIDCustomClosePosition, allowOffscreen: Bool) { + self.action = action + self.width = width + self.height = height + self.offsetX = offsetX + self.offsetY = offsetY + self.customClosePosition = customClosePosition + self.allowOffscreen = allowOffscreen + } +} diff --git a/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/MRAIDMessaheHandler.swift b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/MRAIDMessaheHandler.swift index fadedc85..5168bb76 100644 --- a/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/MRAIDMessaheHandler.swift +++ b/CriteoPublisherSdk/Sources/MRAID/MessageHandlers/MRAIDMessaheHandler.swift @@ -20,55 +20,90 @@ import Foundation public protocol MRAIDMessageHandlerDelegate: AnyObject { - func didReceive(expand action: MRAIDExpandMessage) - func didReceiveCloseAction() + func didReceive(expand action: MRAIDExpandMessage) + func didReceiveCloseAction() + func didReceivePlayVideoAction(with url: String) + func didReceive(resize action: MRAIDResizeMessage) + func didReceive(orientation properties: MRAIDOrientationPropertiesMessage) +} + +class MRAIDJSONDecoder: JSONDecoder { + override init() { + super.init() + keyDecodingStrategy = .convertFromSnakeCase + } } public struct MRAIDMessageHandler { - private var logHandler: MRAIDLogHandler - private var urlHandler: MRAIDURLHandler - public var delegate: MRAIDMessageHandlerDelegate! + private let decoder = MRAIDJSONDecoder() + private let logHandler: MRAIDLogHandler + private let urlHandler: MRAIDURLHandler + public weak var delegate: MRAIDMessageHandlerDelegate? - public init(logHandler: MRAIDLogHandler, urlHandler: MRAIDURLHandler) { - self.logHandler = logHandler - self.urlHandler = urlHandler - } + public init(logHandler: MRAIDLogHandler, urlHandler: MRAIDURLHandler) { + self.logHandler = logHandler + self.urlHandler = urlHandler + } - public func handle(message: Any) { - do { - let data = try JSONSerialization.data(withJSONObject: message) - let actionMessage = try JSONDecoder().decode(MRAIDActionMessage.self, from: data) - switch actionMessage.action { - case .log: logHandler.handle(data: data) - case .open: urlHandler.handle(data: data) - case .expand: handleExpand(message: data) - case .close: delegate.didReceiveCloseAction() - case .none: break - } - } catch { - logHandler.handle( - log: .init( - logId: nil, - message: "Could not deserialise the action message from \(message)", - logLevel: .error, - action: .none)) + public func handle(message: Any) { + do { + let data = try JSONSerialization.data(withJSONObject: message) + let actionMessage = try decoder.decode(MRAIDActionMessage.self, from: data) + switch actionMessage.action { + case .log: logHandler.handle(data: data) + case .open: urlHandler.handle(data: data) + case .close: delegate?.didReceiveCloseAction() + case .expand: handleExpand(message: data, action: actionMessage.action) + case .playVideo: handlePlayVideo(message: data, action: actionMessage.action) + case .resize: handleResize(message: data, action: actionMessage.action) + case .orientationPropertiesUpdate, .orientationPropertiesSet: + handleOrientationPropertiesUpdate(message: data, action: actionMessage.action) + case .none: break + } + } catch { + logHandler.handle( + log: .init( + logId: nil, + message: "Could not deserialise the action message from \(message)", + logLevel: .error, + action: .none)) + } } - } } // MARK: - Private methods -extension MRAIDMessageHandler { - fileprivate func handleExpand(message data: Data) { - do { - let expandMessage = try JSONDecoder().decode(MRAIDExpandMessage.self, from: data) - delegate.didReceive(expand: expandMessage) - } catch { - logHandler.handle( - log: .init( - logId: nil, - message: error.localizedDescription, - logLevel: .error, - action: .expand)) +fileprivate extension MRAIDMessageHandler { + func handleExpand(message data: Data, action: Action) { + guard let expandMessage: MRAIDExpandMessage = extractMessage(from: data, action: action) else { return } + delegate?.didReceive(expand: expandMessage) + } + + func handlePlayVideo(message data: Data, action: Action) { + guard let playVideoMessage: MRAIDPlayVideoMessage = extractMessage(from: data, action: action) else { return } + delegate?.didReceivePlayVideoAction(with: playVideoMessage.url) + } + + func handleResize(message data: Data, action: Action) { + guard let resizeMessage: MRAIDResizeMessage = extractMessage(from: data, action: action) else { return } + delegate?.didReceive(resize: resizeMessage) + } + + func handleOrientationPropertiesUpdate(message data: Data, action: Action) { + guard let message: MRAIDOrientationPropertiesMessage = extractMessage(from: data, action: action) else { return } + delegate?.didReceive(orientation: message) + } + + func extractMessage(from data: Data, action: Action) -> T? { + do { + return try decoder.decode(T.self, from: data) + } catch { + logHandler.handle( + log: .init( + logId: nil, + message: error.localizedDescription, + logLevel: .error, + action: action)) + } + return nil } - } } diff --git a/CriteoPublisherSdk/Sources/MRAID/OrientationProperties/MRAIDDeviceOrientation.swift b/CriteoPublisherSdk/Sources/MRAID/OrientationProperties/MRAIDDeviceOrientation.swift new file mode 100644 index 00000000..67a29b3c --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/OrientationProperties/MRAIDDeviceOrientation.swift @@ -0,0 +1,36 @@ +// +// MRAIDDeviceOrientation.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation + +public enum MRAIDDeviceOrientation: String, Decodable { + case portrait + case landscape + case `none` +} + +extension MRAIDDeviceOrientation { + var interfaceOrientation: UIInterfaceOrientation? { + switch self { + case .landscape: return .landscapeLeft + case .portrait: return .portrait + default: return nil + } + } +} diff --git a/CriteoPublisherSdk/Sources/MRAID/OrientationProperties/MRAIDOrientationPropertiesMessage.swift b/CriteoPublisherSdk/Sources/MRAID/OrientationProperties/MRAIDOrientationPropertiesMessage.swift new file mode 100644 index 00000000..089d3796 --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/OrientationProperties/MRAIDOrientationPropertiesMessage.swift @@ -0,0 +1,53 @@ +// +// MRAIDOrientationPropertiesMessage.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation + +public struct MRAIDOrientationPropertiesMessage: Decodable { + let action: Action + let allowOrientationChange: Bool + let forceOrientation: MRAIDDeviceOrientation +} + +public struct MRAIDOrientationProperties { + let allowOrientationChange: Bool + let orientationMask: UIInterfaceOrientationMask + let supportedOrietationMask: UIInterfaceOrientationMask + + public init(allowOrientationChange: Bool, forceOrientation: MRAIDDeviceOrientation) { + self.allowOrientationChange = allowOrientationChange + self.orientationMask = MRAIDOrientationProperties.orientationMask(for: forceOrientation) + self.supportedOrietationMask = allowOrientationChange ? [.all] : MRAIDOrientationProperties.orientationMask(for: forceOrientation) + } + + public init(allowOrientationChange: Bool, orientationMask: UIInterfaceOrientationMask) { + self.allowOrientationChange = allowOrientationChange + self.orientationMask = orientationMask + self.supportedOrietationMask = orientationMask + + } + + public static func orientationMask(for orientation: MRAIDDeviceOrientation) -> UIInterfaceOrientationMask { + switch orientation { + case .portrait: return [.portrait] + case .landscape: return [.landscape] + case .none: return [.all] + } + } +} diff --git a/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDCustomClosePosition.swift b/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDCustomClosePosition.swift new file mode 100644 index 00000000..4f0a9f74 --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDCustomClosePosition.swift @@ -0,0 +1,30 @@ +// +// MRAIDCustomClosePosition.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation + +public enum MRAIDCustomClosePosition: String, Decodable { + case topLeft = "top-left" + case topRight = "top-right" + case center + case bottomLeft = "bottom-left" + case bottomRight = "bottom-right" + case topCenter = "top-center" + case bottomCenter = "bottom-center" +} diff --git a/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDResizeContainerView.swift b/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDResizeContainerView.swift new file mode 100644 index 00000000..daf8b119 --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDResizeContainerView.swift @@ -0,0 +1,162 @@ +// +// MRAIDResizeContainerView.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 UIKit +import WebKit + +protocol MRAIDClosableView { + func closeView() +} + +final class MRAIDResizeContainerView: UIView, MRAIDClosableView { + private enum Constants { + static let closeAreaHeight: CGFloat = 50 + static let closeAreaWidth: CGFloat = 50 + } + + private let resizeMessage: MRAIDResizeMessage + private let webView: WKWebView + private weak var closeAreaView: UIView? + private weak var webViewBannerContainer: UIView? + private let delegate: MRAIDResizeHandlerDelegate + + public init(with resizeMessage: MRAIDResizeMessage, + webView: WKWebView, + delegate: MRAIDResizeHandlerDelegate, + webViewContainer: UIView?) throws { + self.resizeMessage = resizeMessage + self.webView = webView + self.delegate = delegate + self.webViewBannerContainer = webViewContainer + super.init(frame: .zero) + + setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + static func show(webView: WKWebView, + with resizeMessage: MRAIDResizeMessage, + delegate: MRAIDResizeHandlerDelegate, + webViewContainer: UIView?) throws -> UIView { + guard let containerView = webView.cr_rootViewController()?.view else { throw MRAIDResizeHandler.ResizeError.missingParentViewReference } + let resizeView = try MRAIDResizeContainerView(with: resizeMessage, + webView: webView, + delegate: delegate, + webViewContainer: webViewContainer) + + containerView.addSubview(resizeView) + NSLayoutConstraint.activate([ + resizeView.heightAnchor.constraint(equalToConstant: CGFloat(resizeMessage.height)), + resizeView.widthAnchor.constraint(equalToConstant: CGFloat(resizeMessage.width)), + resizeView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: CGFloat(resizeMessage.offsetY)), + resizeView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: CGFloat(resizeMessage.offsetX)) + ]) + return resizeView + } + + @objc + func closeView() { + /// remove from current container + guard let container = webViewBannerContainer else { return } + webView.removeAllConstraints() + webView.removeFromSuperview() + /// add webView back to banner container + container.addSubview(webView) + webView.fill(in: container) + /// remove resized container from parent view and notify mraid handler about close completion + removeFromSuperview() + delegate.didCloseResizedAdView() + } +} + +/// MARK: - Private methods +private extension MRAIDResizeContainerView { + func setup() { + translatesAutoresizingMaskIntoConstraints = false + /// remove from current container + webView.translatesAutoresizingMaskIntoConstraints = false + webView.removeAllConstraints() + webView.removeFromSuperview() + /// add it to new container and set new constraints + addSubview(webView) + NSLayoutConstraint.activate([ + webView.widthAnchor.constraint(equalTo: widthAnchor), + webView.heightAnchor.constraint(equalTo: heightAnchor), + webView.centerXAnchor.constraint(equalTo: centerXAnchor), + webView.centerYAnchor.constraint(equalTo: centerYAnchor) + ]) + /// setup the close button area + initCloseAreaView() + } + + func initCloseAreaView() { + closeAreaView = injectCloseArea(into: self, customClosePosition: resizeMessage.customClosePosition) + closeAreaView?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(closeView))) + } + + func injectCloseArea(into container: UIView, customClosePosition: MRAIDCustomClosePosition) -> UIView { + let closeAreaView = UIView() + closeAreaView.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(closeAreaView) + closeAreaView.backgroundColor = .clear + let safeAreaLayout = container.safeAreaLayoutGuide + + /// set the dimension of the close area + NSLayoutConstraint.activate([ + closeAreaView.heightAnchor.constraint(equalToConstant: Constants.closeAreaHeight), + closeAreaView.widthAnchor.constraint(equalToConstant: Constants.closeAreaWidth) + ]) + /// set the position according to custom close position + switch customClosePosition { + case .topLeft: NSLayoutConstraint.activate([ + closeAreaView.leadingAnchor.constraint(equalTo: safeAreaLayout.leadingAnchor), + closeAreaView.topAnchor.constraint(equalTo: safeAreaLayout.topAnchor) + ]) + case .topRight: NSLayoutConstraint.activate([ + closeAreaView.trailingAnchor.constraint(equalTo: safeAreaLayout.trailingAnchor), + closeAreaView.topAnchor.constraint(equalTo: safeAreaLayout.topAnchor) + ]) + case .center: NSLayoutConstraint.activate([ + closeAreaView.centerXAnchor.constraint(equalTo: safeAreaLayout.centerXAnchor), + closeAreaView.centerYAnchor.constraint(equalTo: safeAreaLayout.centerYAnchor) + ]) + case .bottomLeft: NSLayoutConstraint.activate([ + closeAreaView.leadingAnchor.constraint(equalTo: safeAreaLayout.leadingAnchor), + closeAreaView.bottomAnchor.constraint(equalTo: safeAreaLayout.bottomAnchor) + ]) + case .bottomRight: NSLayoutConstraint.activate([ + closeAreaView.trailingAnchor.constraint(equalTo: safeAreaLayout.trailingAnchor), + closeAreaView.bottomAnchor.constraint(equalTo: safeAreaLayout.bottomAnchor) + ]) + case .topCenter: NSLayoutConstraint.activate([ + closeAreaView.centerXAnchor.constraint(equalTo: safeAreaLayout.centerXAnchor), + closeAreaView.topAnchor.constraint(equalTo: safeAreaLayout.topAnchor) + ]) + case .bottomCenter: NSLayoutConstraint.activate([ + closeAreaView.centerXAnchor.constraint(equalTo: safeAreaLayout.centerXAnchor), + closeAreaView.bottomAnchor.constraint(equalTo: safeAreaLayout.bottomAnchor) + ]) + } + + return closeAreaView + } +} diff --git a/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDResizeHandler.swift b/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDResizeHandler.swift new file mode 100644 index 00000000..0af216a7 --- /dev/null +++ b/CriteoPublisherSdk/Sources/MRAID/Resize/MRAIDResizeHandler.swift @@ -0,0 +1,185 @@ +// +// MRAIDResizeHandler.swift +// CriteoPublisherSdk +// +// Copyright © 2018-2023 Criteo. 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 Foundation +import WebKit + +public protocol MRAIDResizeHandlerDelegate { + func didCloseResizedAdView() +} + +public class MRAIDResizeHandler { + + enum ResizeError: Error { + case missingParentViewReference + case exceedingMaxSize + case exceedingMinSize + case closeAreaOutOfBounds + } + + public enum Constants { + public static let minHeight: CGFloat = 50 + public static let minWidth: CGFloat = 50 + } + + private let webView: WKWebView + private let delegate: MRAIDResizeHandlerDelegate + private var resizedContainer: UIView? + + init(webView: WKWebView, delegate: MRAIDResizeHandlerDelegate) { + self.webView = webView + self.delegate = delegate + } + + public func canResize(mraidState: MRAIDState) -> Bool { + guard mraidState == .default || mraidState == .resized else { return false } + return true + } + + public func resize(with resizeMessage: MRAIDResizeMessage, webViewContainer: UIView?) throws { + guard + let rootViewController = webView.cr_rootViewController(), + let topView = rootViewController.view else { + throw ResizeError.missingParentViewReference + } + + let containerSize = topView.frame.size + let positionInContainer = webView.convert(webView.frame.origin, to: topView) + let autoRotate = rootViewController.shouldAutorotate + + try verifyMinSize(message: resizeMessage) + let container = resizedContainer + if resizeMessage.allowOffscreen { + let updatedResizeMessage = try verifyCloseAreaOutOfBounds(containerSize: containerSize, positionInContainer: positionInContainer, message: resizeMessage, autoRotate: autoRotate) + resizedContainer = try MRAIDResizeContainerView.show(webView: webView, + with: updatedResizeMessage, + delegate: delegate, + webViewContainer: webViewContainer) + } else { + let updatedResizeMessage = try updateResizeMessageToFit(containerSize: containerSize, positionInContainer: positionInContainer, with: resizeMessage) + resizedContainer = try MRAIDResizeContainerView.show(webView: webView, + with: updatedResizeMessage, + delegate: delegate, + webViewContainer: webViewContainer) + } + + if let previousContainer = container { + previousContainer.removeFromSuperview() + } + } + + public func updateResizeMessageToFit(containerSize: CGSize, positionInContainer: CGPoint, with message: MRAIDResizeMessage) throws -> MRAIDResizeMessage { + let containerHeight = Int(containerSize.height) + let containerWidth = Int(containerSize.width) + + /// verify if the new size exceeds the container size + /// ignore autoRotate because we cannot fix all position issues. in case it doesn't the container in one orientation then the ad will be partially off screen. + if message.height > containerHeight || message.width > containerWidth { + throw ResizeError.exceedingMaxSize + } + + /// position of the resized ad container in the container (top view) + let adjustedPosition = CGPoint(x: positionInContainer.x + CGFloat(message.offsetX), y: positionInContainer.y + CGFloat(message.offsetY)) + let adContainerPosition = CGPoint(x: adjustedPosition.x < 0 ? 0 : adjustedPosition.x, y: adjustedPosition.y < 0 ? 0 : adjustedPosition.y ) + + var positionX = Int(adContainerPosition.x) + var positionY = Int(adContainerPosition.y) + + if positionX + message.width > containerWidth { + /// try to adjust offset x in order to fit the ad into container + positionX -= (positionX + message.width - containerWidth) + } + + if positionY + message.height > containerHeight { + positionY -= (positionY + message.height - containerHeight) + } + + return .init(action: message.action, + width: message.width, + height: message.height, + offsetX: positionX, + offsetY: positionY, + customClosePosition: message.customClosePosition, + allowOffscreen: message.allowOffscreen) + } + + public func verifyCloseAreaOutOfBounds(containerSize: CGSize, positionInContainer: CGPoint, message: MRAIDResizeMessage, autoRotate: Bool) throws -> MRAIDResizeMessage { + let adjustedPosition = CGPoint(x: positionInContainer.x + CGFloat(message.offsetX), y: positionInContainer.y + CGFloat(message.offsetY)) + let embededCloseAreaPosition = closeAreaPositionInAdContainer(with: .init(width: message.width, height: message.height), for: message.customClosePosition) + + let caTopLeftCorner: CGPoint = CGPoint(x: embededCloseAreaPosition.x + adjustedPosition.x, y: embededCloseAreaPosition.y + adjustedPosition.y) + let caTopRightCorner: CGPoint = .init(x: caTopLeftCorner.x + CGFloat(Constants.minWidth), y: caTopLeftCorner.y) + let caBottomLeftCorner: CGPoint = .init(x: caTopLeftCorner.x, y: caTopLeftCorner.y + CGFloat(Constants.minHeight)) + let caBottomRightCorner: CGPoint = .init(x: caTopRightCorner.x, y: caTopRightCorner.y + CGFloat(Constants.minHeight)) + + let bounds = CGRect(origin: .zero, size: containerSize) + let closeAreaCorners: [CGPoint] = [caTopLeftCorner, caTopRightCorner, caBottomLeftCorner, caBottomRightCorner] + + /// verify that all corners of the close are view are in the container's frame + guard bounds.contains(points: closeAreaCorners) else { + throw ResizeError.closeAreaOutOfBounds + } + + /// verify that all corners of the close are are in the container's frame when orientation changes + let rotatedBounds = CGRect(origin: .zero, size: .init(width: containerSize.height, height: containerSize.width)) + guard autoRotate, rotatedBounds.contains(points: closeAreaCorners) else { + throw ResizeError.closeAreaOutOfBounds + } + + return .init(action: message.action, + width: message.width, + height: message.height, + offsetX: Int(adjustedPosition.x), + offsetY: Int(adjustedPosition.y), + customClosePosition: message.customClosePosition, + allowOffscreen: message.allowOffscreen) + } + + public func verifyMinSize(message: MRAIDResizeMessage) throws { + guard + message.height >= Int(Constants.minWidth), + message.width >= Int(Constants.minWidth) else { + throw ResizeError.exceedingMinSize + } + } + + public func closeAreaPositionInAdContainer(with newSize: CGSize, for customClosePosition: MRAIDCustomClosePosition) -> CGPoint { + switch customClosePosition { + case .topLeft: return .init(x: 0, + y: 0) + case .topRight: return .init(x: newSize.width - Constants.minWidth, + y: 0) + case .center: return .init(x: newSize.width / 2 - Constants.minWidth / 2, + y: newSize.height / 2 - Constants.minHeight / 2) + case .bottomLeft: return .init(x: 0, + y: newSize.height - Constants.minHeight) + case .bottomRight: return .init(x: newSize.width - Constants.minWidth, + y: newSize.height - Constants.minHeight) + case .topCenter: return .init(x: newSize.width / 2 - Constants.minWidth / 2, + y: 0) + case .bottomCenter: return .init(x: newSize.width / 2 - Constants.minWidth / 2, + y: newSize.height - Constants.minHeight) + } + } + + public func close() { + guard let closeable = resizedContainer as? MRAIDClosableView else { return } + closeable.closeView() + } +} diff --git a/CriteoPublisherSdk/Sources/MRAID/SwiftExtensions.swift b/CriteoPublisherSdk/Sources/MRAID/SwiftExtensions.swift index 3afacd99..ac5e8bb5 100644 --- a/CriteoPublisherSdk/Sources/MRAID/SwiftExtensions.swift +++ b/CriteoPublisherSdk/Sources/MRAID/SwiftExtensions.swift @@ -20,58 +20,64 @@ import UIKit extension UIView { - public var isVisibleToUser: Bool { - - if isHidden || alpha == 0 || superview == nil { - return false + func fill(in container: UIView) { + NSLayoutConstraint.activate([ + widthAnchor.constraint(equalTo: container.widthAnchor), + heightAnchor.constraint(equalTo: container.heightAnchor), + centerXAnchor.constraint(equalTo: container.centerXAnchor), + centerYAnchor.constraint(equalTo: container.centerYAnchor) + ]) } - guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { - return false + func removeAllConstraints() { + removeConstraints(constraints) } - let viewFrame = convert(bounds, to: rootViewController.view) + public var isVisibleToUser: Bool { + if isHidden || alpha == 0 || superview == nil || window == nil { + return false + } - let topSafeArea: CGFloat - let bottomSafeArea: CGFloat + guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { + return false + } - if #available(iOS 11.0, *) { - topSafeArea = rootViewController.view.safeAreaInsets.top - bottomSafeArea = rootViewController.view.safeAreaInsets.bottom - } else { - topSafeArea = rootViewController.topLayoutGuide.length - bottomSafeArea = rootViewController.bottomLayoutGuide.length - } + let viewFrame = convert(bounds, to: rootViewController.view) + let rootRectange = CGRect(origin: .zero, size: rootViewController.view.bounds.size) - return viewFrame.minX >= 0 && viewFrame.maxX <= rootViewController.view.bounds.width - && viewFrame.minY >= topSafeArea - && viewFrame.maxY <= rootViewController.view.bounds.height - bottomSafeArea - } + return rootRectange.intersects(viewFrame) + } - @objc - public func cr_parentViewController() -> UIViewController? { - var responder: UIResponder? = self - while responder != nil { - if responder is UIViewController { - return responder as? UIViewController - } - responder = responder?.next + @objc + public func cr_parentViewController() -> UIViewController? { + var responder: UIResponder? = self + while responder != nil { + if responder is UIViewController { + return responder as? UIViewController + } + responder = responder?.next + } + return nil } - return nil - } - @objc - public func cr_rootViewController() -> UIViewController? { - var controller: UIViewController? = cr_parentViewController() - while controller?.parent != nil { - controller = controller?.parent + @objc + public func cr_rootViewController() -> UIViewController? { + var controller: UIViewController? = cr_parentViewController() + while controller?.parent != nil { + controller = controller?.parent + } + return controller } - return controller - } } extension Bool { - public var stringValue: String { - return self == true ? "true" : "false" - } + public var stringValue: String { + return self == true ? "true" : "false" + } +} + +extension CGRect { + public func contains(points: [CGPoint]) -> Bool { + return points.filter({ !contains($0)}).isEmpty + } } diff --git a/CriteoPublisherSdk/Sources/MRAID/Utils/CRFulllScreenContainer.swift b/CriteoPublisherSdk/Sources/MRAID/Utils/CRFulllScreenContainer.swift index da914184..a14b57c1 100644 --- a/CriteoPublisherSdk/Sources/MRAID/Utils/CRFulllScreenContainer.swift +++ b/CriteoPublisherSdk/Sources/MRAID/Utils/CRFulllScreenContainer.swift @@ -32,12 +32,17 @@ public class CRFulllScreenContainer: UIViewController { weak var delegate: CRFulllScreenContainerDelegate? private weak var webViewBannerContainer: UIView? private var dismissCompletion: VoidCompletion? + private let mraidHandler: CRMRAIDHandler @objc - public init(with webView: WKWebView, size: CGSize, dismissCompletion: VoidCompletion?) { + public init(with webView: WKWebView, + size: CGSize, + mraidHandler: CRMRAIDHandler, + dismissCompletion: VoidCompletion?) { self.closeButton = UIButton(type: .custom) self.webView = webView self.webViewSize = size + self.mraidHandler = mraidHandler self.dismissCompletion = dismissCompletion super.init(nibName: nil, bundle: nil) @@ -57,15 +62,18 @@ public class CRFulllScreenContainer: UIViewController { webView.removeFromSuperview() // 2. add webview back to banner container container.addSubview(webView) - NSLayoutConstraint.activate([ - webView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 1), - webView.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 1), - webView.centerXAnchor.constraint(equalTo: container.centerXAnchor), - webView.centerYAnchor.constraint(equalTo: container.centerYAnchor) - ]) + webView.fill(in: container) // 3. dismiss full screen controller dismiss(animated: true, completion: completion) } + + public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return mraidHandler.supportedInterfaceOrientations() + } + + public override var shouldAutorotate: Bool { + return mraidHandler.shouldAdAutoRotate() + } } // MARK: - Private methods @@ -169,6 +177,7 @@ extension CRFulllScreenContainer { webViewBannerContainer = webView.superview // 2. remove all constraints webView.removeFromSuperview() + closeButton.removeFromSuperview() // 3. add webview to new container view.addSubview(webView) view.addSubview(closeButton) @@ -188,6 +197,7 @@ extension CRFulllScreenContainer { } fileprivate func referenceViewForCloseButton(for size: CGSize) -> UIView { + if size.width <= 0 || size.height <= 0 { return view } return (view.frame.width < size.width || view.frame.height < webViewSize.height) ? view : webView } diff --git a/CriteoPublisherSdk/Sources/Network/Serializers/CR_BidRequestSerializer.m b/CriteoPublisherSdk/Sources/Network/Serializers/CR_BidRequestSerializer.m index b0276186..b054425b 100644 --- a/CriteoPublisherSdk/Sources/Network/Serializers/CR_BidRequestSerializer.m +++ b/CriteoPublisherSdk/Sources/Network/Serializers/CR_BidRequestSerializer.m @@ -157,10 +157,11 @@ - (NSArray *)slotsWithCdbRequest:(CR_CdbRequest *)cdbRequest config:(CR_Config * } else if (adUnit.adUnitType == CRAdUnitTypeRewarded) { slotDict[CR_ApiQueryKeys.bidSlotsIsRewarded] = @(YES); } - if (config.isMRAIDEnabled && (adUnit.adUnitType == CRAdUnitTypeBanner || - adUnit.adUnitType == CRAdUnitTypeInterstitial)) { + + if (config.isMRAIDGlobalEnabled && (adUnit.adUnitType == CRAdUnitTypeBanner || + adUnit.adUnitType == CRAdUnitTypeInterstitial)) { NSMutableDictionary *mraidDict = [NSMutableDictionary new]; - mraidDict[CR_ApiQueryKeys.api] = [NSArray arrayWithObject:@(3)]; + mraidDict[CR_ApiQueryKeys.api] = [self mraidAPI:config]; slotDict[CR_ApiQueryKeys.banner] = mraidDict; } @@ -169,6 +170,17 @@ - (NSArray *)slotsWithCdbRequest:(CR_CdbRequest *)cdbRequest config:(CR_Config * return slots; } +- (NSArray *)mraidAPI:(CR_Config *)config { + NSMutableArray *mraidVersions = [NSMutableArray new]; + if (config.isMraidEnabled) { + [mraidVersions addObject:@(3)]; + } + if (config.isMraid2Enabled) { + [mraidVersions addObject:@(5)]; + } + return mraidVersions; +} + - (NSArray *)slotsWithCdbRequest:(CR_CdbRequest *)cdbRequest { return [self slotsWithCdbRequest:cdbRequest config:[CR_Config new]]; } diff --git a/CriteoPublisherSdk/Sources/Public/CRInterstitial.h b/CriteoPublisherSdk/Sources/Public/CRInterstitial.h index 57214a52..cc3099cb 100644 --- a/CriteoPublisherSdk/Sources/Public/CRInterstitial.h +++ b/CriteoPublisherSdk/Sources/Public/CRInterstitial.h @@ -31,7 +31,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) BOOL isAdLoaded; @property(nullable, nonatomic, weak) id delegate; -//@property(nonatomic, strong) ska - (instancetype)initWithAdUnit:(CRInterstitialAdUnit *)adUnit; @@ -42,8 +41,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)presentFromRootViewController:(UIViewController *)rootViewController; +- (BOOL)shouldAutorotate; +- (UIInterfaceOrientationMask)supportedInterfaceOrientations; + - (void)startSKAdImpression API_AVAILABLE(ios(14.5)); - (void)endSKAdImpression API_AVAILABLE(ios(14.5)); + @end NS_ASSUME_NONNULL_END diff --git a/CriteoPublisherSdk/Sources/Standalone/CRBannerView.m b/CriteoPublisherSdk/Sources/Standalone/CRBannerView.m index 4953ed6d..42d66e5c 100644 --- a/CriteoPublisherSdk/Sources/Standalone/CRBannerView.m +++ b/CriteoPublisherSdk/Sources/Standalone/CRBannerView.m @@ -25,7 +25,6 @@ #import "CR_DependencyProvider.h" #import "CR_Logging.h" #import "NSError+Criteo.h" -#import "CRMRAIDConstants.h" #import "CRLogUtil.h" #import "UIView+Criteo.h" #import "CR_SKAdNetworkHandler.h" @@ -99,16 +98,22 @@ - (instancetype)initWithFrame:(CGRect)rect } _adUnit = adUnit; _urlOpener = opener; - if (criteo.config.isMRAIDEnabled) { - _mraidHandler = [[CRMRAIDHandler alloc] initWith:_webView - criteoLogger:[CRLogUtil new] - urlOpener:self - delegate:self]; + if (criteo.config.isMRAIDGlobalEnabled) { + _mraidHandler = [[CRMRAIDHandler alloc] initWithPlacementType:CRPlacementTypeBanner + webView:_webView + criteoLogger:[CRLogUtil new] + urlOpener:self + delegate:self]; } } return self; } +- (void)layoutSubviews { + [super layoutSubviews]; + [_mraidHandler setCurrentPosition]; +} + - (void)safelyLoadWebViewWithDisplayUrl:(NSString *)displayUrl { dispatch_async(dispatch_get_main_queue(), ^{ [self loadWebViewWithDisplayUrl:displayUrl]; @@ -238,7 +243,7 @@ - (void)webView:(WKWebView *)webView } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - [_mraidHandler onAdLoadWith:CR_MRAID_PLACEMENT_BANNER]; + [_mraidHandler onAdLoad]; if (@available(iOS 14.5, *)) { [_skadNetworkHandler startSKAdImpression]; } @@ -340,8 +345,9 @@ - (void)expandWithWidth:(NSInteger)width UIViewController *mraidFullScreenContainer = [[CRFulllScreenContainer alloc] initWith:_webView size:CGSizeMake(width, height) + mraidHandler:_mraidHandler dismissCompletion:completion]; - mraidFullScreenContainer.modalPresentationStyle = UIModalPresentationOverCurrentContext; + mraidFullScreenContainer.modalPresentationStyle = UIModalPresentationOverFullScreen; mraidFullScreenContainer.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [webViewViewController presentViewController:mraidFullScreenContainer animated:YES diff --git a/CriteoPublisherSdk/Sources/Standalone/CRInterstitial.m b/CriteoPublisherSdk/Sources/Standalone/CRInterstitial.m index fc5382bc..1b0cd4d5 100644 --- a/CriteoPublisherSdk/Sources/Standalone/CRInterstitial.m +++ b/CriteoPublisherSdk/Sources/Standalone/CRInterstitial.m @@ -31,7 +31,6 @@ #import "CR_DisplaySizeInjector.h" #import "CR_IntegrationRegistry.h" #import "CR_Logging.h" -#import "CRMRAIDConstants.h" #import "CRLogUtil.h" #import "CR_SKAdNetworkHandler.h" @@ -63,11 +62,12 @@ - (instancetype)initWithCriteo:(Criteo *)criteo _isAdLoaded = isAdLoaded; _adUnit = adUnit; _urlOpener = urlOpener; - if (criteo.config.isMRAIDEnabled) { - _mraidHandler = [[CRMRAIDHandler alloc] initWith:viewController.webView - criteoLogger:[CRLogUtil new] - urlOpener:self - delegate:self]; + if (criteo.config.isMRAIDGlobalEnabled) { + _mraidHandler = [[CRMRAIDHandler alloc] initWithPlacementType:CRPlacementTypeInterstitial + webView:viewController.webView + criteoLogger:[CRLogUtil new] + urlOpener:self + delegate:self]; __weak typeof(self) weakSelf = self; _viewController.dismissCompletion = ^{ [weakSelf.mraidHandler onSuccessClose]; @@ -232,7 +232,7 @@ - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigat self.isAdLoading = NO; self.isAdLoaded = YES; [self dispatchDidReceiveAdDelegate]; - [_mraidHandler onAdLoadWith:CR_MRAID_PLACEMENT_INTERSTITIAL]; + [_mraidHandler onAdLoad]; } - (void)presentFromRootViewController:(UIViewController *)rootViewController { @@ -390,6 +390,15 @@ - (void)closeWithCompletion:(void (^)(void))completion { [self.viewController dismissViewController]; } +#pragma CRMRAID Orientation Properties +- (BOOL)shouldAutorotate { + return [_mraidHandler shouldAdAutoRotate]; +} + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + return [_mraidHandler supportedInterfaceOrientations]; +} + #pragma SKAdImpression - (void)startSKAdImpression { [_skadNetworkHandler startSKAdImpression]; diff --git a/CriteoPublisherSdk/Sources/Standalone/CR_InterstitialViewController.m b/CriteoPublisherSdk/Sources/Standalone/CR_InterstitialViewController.m index ef274261..ee8b66e6 100644 --- a/CriteoPublisherSdk/Sources/Standalone/CR_InterstitialViewController.m +++ b/CriteoPublisherSdk/Sources/Standalone/CR_InterstitialViewController.m @@ -243,4 +243,13 @@ - (void)closeWithCompletion:(void (^)(void))completion { [self dismissViewControllerWithCompletion:completion]; } +#pragma CRMRAID Orientation Properties +- (BOOL)shouldAutorotate { + return [_interstitial shouldAutorotate]; +} + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + return [_interstitial supportedInterfaceOrientations]; +} + @end diff --git a/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.h b/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.h index 183d4ad3..237b7bd5 100644 --- a/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.h +++ b/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.h @@ -51,6 +51,12 @@ - (BOOL)cr_valueForMRAID; +- (BOOL)cr_valueForMRAID2; + +- (void)cr_setValueForMRAID:(BOOL)mraidEnabled; + +- (void)cr_setValueForMRAID2:(BOOL)mraid2Enabled; + @end #endif /* NSUserDefaults_CR_Config_H */ diff --git a/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.m b/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.m index 37e29de1..354a62bd 100644 --- a/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.m +++ b/CriteoPublisherSdk/Sources/Util/NSUserDefaults+CR_Config.m @@ -28,6 +28,7 @@ NSString *const NSUserDefaultsLiveBiddingTimeBudgetKey = @"CRITEO_LiveBiddingTimeBudget"; NSString *const NSUserDefaultsRemoteLogLevelKey = @"CRITEO_RemoteLogLevel"; NSString *const NSUserDefaultsMRAIDKey = @"CRITEO_MRAID"; +NSString *const NSUserDefaultsMRAID2Key = @"CRITEO_MRAID2"; @implementation NSUserDefaults (CR_Config) @@ -85,4 +86,16 @@ - (BOOL)cr_valueForMRAID { return [self boolForKey:NSUserDefaultsMRAIDKey withDefaultValue:NO]; } +- (BOOL)cr_valueForMRAID2 { + return [self boolForKey:NSUserDefaultsMRAID2Key withDefaultValue:NO]; +} + +- (void)cr_setValueForMRAID:(BOOL)mraidEnabled { + [self setBool:mraidEnabled forKey:NSUserDefaultsMRAIDKey]; +} + +- (void)cr_setValueForMRAID2:(BOOL)mraid2Enabled { + [self setBool:mraid2Enabled forKey:NSUserDefaultsMRAID2Key]; +} + @end diff --git a/CriteoPublisherSdk/Tests/TestPlans/CriteoPublisherSdkMC2.xctestplan b/CriteoPublisherSdk/Tests/TestPlans/CriteoPublisherSdkMC2.xctestplan index 813aa42d..00b76764 100644 --- a/CriteoPublisherSdk/Tests/TestPlans/CriteoPublisherSdkMC2.xctestplan +++ b/CriteoPublisherSdk/Tests/TestPlans/CriteoPublisherSdkMC2.xctestplan @@ -28,6 +28,7 @@ "skippedTests" : [ "CRBannerViewDelegateTests", "CRBannerViewTests\/testAllowNavigationActionPolicyForWebView", + "CRBannerViewTests\/testExpandMRAIDAction", "CRBannerViewTests\/testWithRendering", "CRInterstitialDelegateTests\/testCacheHasAdButAdContentFetchFailed", "CRInterstitialDelegateTests\/testInterstitialAdFetchFail", diff --git a/CriteoPublisherSdk/Tests/UnitTests/Configuration/CR_ConfigTests.m b/CriteoPublisherSdk/Tests/UnitTests/Configuration/CR_ConfigTests.m index 4432ab24..7f20b973 100644 --- a/CriteoPublisherSdk/Tests/UnitTests/Configuration/CR_ConfigTests.m +++ b/CriteoPublisherSdk/Tests/UnitTests/Configuration/CR_ConfigTests.m @@ -42,6 +42,7 @@ - (void)tearDown { [userDefaults removeObjectForKey:NSUserDefaultsLiveBiddingTimeBudgetKey]; [userDefaults removeObjectForKey:NSUserDefaultsRemoteLogLevelKey]; [userDefaults removeObjectForKey:NSUserDefaultsMRAIDKey]; + [userDefaults removeObjectForKey:NSUserDefaultsMRAID2Key]; [super tearDown]; } @@ -201,10 +202,29 @@ - (void)testSetCsmEnabled_GivenDisabledFeature_WriteItInUserDefaults { - (void)testInit_GivenUserDefaultWithMRAIDEnabled { NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; [userDefaults setBool:YES forKey:NSUserDefaultsMRAIDKey]; + [userDefaults setBool:YES forKey:NSUserDefaultsMRAID2Key]; CR_Config *config = [[CR_Config alloc] initWithUserDefaults:userDefaults]; - XCTAssertTrue(config.isMRAIDEnabled); + XCTAssertTrue(config.isMraidEnabled); + XCTAssertTrue(config.isMraid2Enabled); + XCTAssertTrue(config.isMRAIDGlobalEnabled); +} + +- (void)testInit_GivenUserDefaultWithMRAIDDisabled { + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + CR_Config *config = [[CR_Config alloc] initWithUserDefaults:userDefaults]; + + XCTAssertFalse(config.isMraidEnabled); + XCTAssertFalse(config.isMraid2Enabled); + XCTAssertFalse(config.isMRAIDGlobalEnabled); + + [userDefaults setBool:YES forKey:NSUserDefaultsMRAID2Key]; + config = [[CR_Config alloc] initWithUserDefaults:userDefaults]; + + XCTAssertFalse(config.isMraidEnabled); + XCTAssertTrue(config.isMraid2Enabled); + XCTAssertTrue(config.isMRAIDGlobalEnabled); } #pragma mark - Prefetch on init Enabled diff --git a/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDHandlerTests.swift b/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDHandlerTests.swift index c580cb19..a98bb94c 100644 --- a/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDHandlerTests.swift +++ b/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDHandlerTests.swift @@ -22,94 +22,109 @@ import WebKit import XCTest class MockMessageDelegate: MRAIDMessageHandlerDelegate { - typealias ExpandBlock = (Int, Int, URL?) -> Void - typealias CloseBlock = () -> Void + typealias ExpandBlock = (Int, Int, URL?) -> Void + typealias CloseBlock = () -> Void + typealias OrientationPropertiesBlock = (MRAIDOrientationPropertiesMessage) -> Void - var expandBlock: ExpandBlock? - var closeBlock: CloseBlock? + var expandBlock: ExpandBlock? + var closeBlock: CloseBlock? + var orientationPropertiesBlock: OrientationPropertiesBlock? - func didReceive(expand action: MRAIDExpandMessage) { - expandBlock?(action.width, action.height, action.url) - } + func didReceive(expand action: MRAIDExpandMessage) { + expandBlock?(action.width, action.height, action.url) + } + + func didReceiveCloseAction() { + closeBlock?() + } + + func didReceivePlayVideoAction(with url: String) { + debugPrint("play video from url: \(url)") + } + + func didReceive(resize action: MRAIDResizeMessage) { + debugPrint(#function) + } - func didReceiveCloseAction() { - closeBlock?() - } + func didReceive(orientation properties: MRAIDOrientationPropertiesMessage) { + orientationPropertiesBlock?(properties) + } } final class MRAIDHandlerTests: XCTestCase { - var logger: MRAIDLoggerMock! - var urlOpener: URLOpenerMock! - var messageHandler: MRAIDMessageHandler! - var urlHandler: MRAIDURLHandler! - var logHandler: MRAIDLogHandler! - var messageMockListener: MockMessageDelegate! - - override func setUp() { - logger = MRAIDLoggerMock() - urlOpener = URLOpenerMock(openBlock: nil) - urlHandler = CRMRAIDURLHandler(with: logger, urlOpener: urlOpener) - logHandler = MRAIDLogHandler(criteoLogger: logger) - messageHandler = MRAIDMessageHandler(logHandler: logHandler, urlHandler: urlHandler) - messageMockListener = MockMessageDelegate() - } - - override func tearDown() { - logger = nil - urlOpener = nil - urlHandler = nil - messageHandler = nil - logHandler = nil - } - - func testOpenAction() { - let urlString = "https://criteo.com" - let expectation = XCTestExpectation(description: "url to match") - urlOpener.openBlock = { url in - if url.absoluteString == urlString { - expectation.fulfill() - } + var logger: MRAIDLoggerMock! + var urlOpener: URLOpenerMock! + var messageHandler: MRAIDMessageHandler! + var urlHandler: MRAIDURLHandler! + var logHandler: MRAIDLogHandler! + var messageMockListener: MockMessageDelegate! + + override func setUp() { + logger = MRAIDLoggerMock() + urlOpener = URLOpenerMock(openBlock: nil) + urlHandler = CRMRAIDURLHandler(with: logger, urlOpener: urlOpener) + logHandler = MRAIDLogHandler(criteoLogger: logger) + messageHandler = MRAIDMessageHandler(logHandler: logHandler, urlHandler: urlHandler) + messageMockListener = MockMessageDelegate() + } + + override func tearDown() { + logger = nil + urlOpener = nil + urlHandler = nil + messageHandler = nil + logHandler = nil + messageMockListener = nil } - messageHandler.handle(message: [ - "action": Action.open.rawValue, - "url": urlString - ]) - - wait(for: [expectation], timeout: 0.1) - } - - func testExpandAction() { - let urlString = "https://criteo.com" - messageHandler.delegate = messageMockListener - let expectation = XCTestExpectation(description: "expand action to be received with all data") - messageMockListener.expandBlock = { width, height, url in - if width == 200, height == 100, url?.absoluteString == urlString { - expectation.fulfill() - } + + func testOpenAction() { + let urlString = "https://criteo.com" + let expectation = XCTestExpectation(description: "url to match") + urlOpener.openBlock = { url in + if url.absoluteString == urlString { + expectation.fulfill() + } + } + messageHandler.handle(message: [ + "action": Action.open.rawValue, + "url": urlString + ]) + + wait(for: [expectation], timeout: 0.1) } - messageHandler.handle( - message: [ - "action": Action.expand.rawValue, - "width": 200, - "height": 100, - "url": urlString - ] as [String: Any]) - - wait(for: [expectation], timeout: 0.1) - } - - func testCloseAction() { - let expectation = XCTestExpectation(description: "close action is received") - messageMockListener.closeBlock = { - expectation.fulfill() + func testExpandAction() { + let urlString = "https://criteo.com" + messageHandler.delegate = messageMockListener + let expectation = XCTestExpectation(description: "expand action to be received with all data") + messageMockListener.expandBlock = { width, height, url in + if width == 200, height == 100, url?.absoluteString == urlString { + expectation.fulfill() + } + } + + messageHandler.handle( + message: [ + "action": Action.expand.rawValue, + "width": 200, + "height": 100, + "url": urlString + ] as [String: Any]) + + wait(for: [expectation], timeout: 0.1) } - messageHandler.delegate = messageMockListener - messageHandler.handle(message: [ - "action": Action.close.rawValue - ]) + func testCloseAction() { + let expectation = XCTestExpectation(description: "close action is received") + messageMockListener.closeBlock = { + expectation.fulfill() + } + + messageHandler.delegate = messageMockListener + messageHandler.handle(message: [ + "action": Action.close.rawValue + ]) - wait(for: [expectation], timeout: 0.1) - } + wait(for: [expectation], timeout: 0.1) + } } diff --git a/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDOrientationPropertiesTests.swift b/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDOrientationPropertiesTests.swift new file mode 100644 index 00000000..4ab85b41 --- /dev/null +++ b/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDOrientationPropertiesTests.swift @@ -0,0 +1,90 @@ +// +// MRAIDOrientationPropertiesTests.swift +// CriteoPublisherSdkTests +// +// Copyright © 2018-2023 Criteo. 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 XCTest +@testable import CriteoPublisherSdk + +final class MRAIDOrientationPropertiesTests: XCTestCase { + var logger: MRAIDLoggerMock! + var urlOpener: URLOpenerMock! + var messageHandler: MRAIDMessageHandler! + var urlHandler: MRAIDURLHandler! + var logHandler: MRAIDLogHandler! + var messageMockListener: MockMessageDelegate! + + override func setUpWithError() throws { + logger = MRAIDLoggerMock() + urlOpener = URLOpenerMock(openBlock: nil) + urlHandler = CRMRAIDURLHandler(with: logger, urlOpener: urlOpener) + logHandler = MRAIDLogHandler(criteoLogger: logger) + messageHandler = MRAIDMessageHandler(logHandler: logHandler, urlHandler: urlHandler) + messageMockListener = MockMessageDelegate() + messageHandler.delegate = messageMockListener + } + + override func tearDownWithError() throws { + logger = nil + urlOpener = nil + urlHandler = nil + messageHandler = nil + logHandler = nil + messageMockListener = nil + } + + func testOrientationMaskMapper() { + XCTAssertEqual(MRAIDOrientationProperties.orientationMask(for: .landscape), [.landscape]) + XCTAssertEqual(MRAIDOrientationProperties.orientationMask(for: .portrait), [.portrait]) + XCTAssertEqual(MRAIDOrientationProperties.orientationMask(for: .none), [.all]) + } + + func testOrientationPropertiesAction() throws { + let expectation = XCTestExpectation(description: "orientation properties update action is executed") + messageMockListener.orientationPropertiesBlock = { orientaions in + guard + orientaions.action == Action.orientationPropertiesUpdate, + orientaions.allowOrientationChange == true, + orientaions.forceOrientation == .landscape + else { return } + expectation.fulfill() + } + messageHandler.handle( + message: [ + "action": Action.orientationPropertiesUpdate.rawValue, + "allow_orientation_change": true, + "force_orientation": "landscape" + ] as [String: Any]) + wait(for: [expectation], timeout: 0.1) + } + + func testOrientationPropertiesDecoder() throws { + let decoder = MRAIDJSONDecoder() + let json = try XCTUnwrap(""" + { + "action": "orientation_properties_update", + "allow_orientation_change": true, + "force_orientation": "landscape" + } + """.data(using: .utf8)) + + let orientationProperties = try XCTUnwrap(try? decoder.decode(MRAIDOrientationPropertiesMessage.self, from: json)) + XCTAssertEqual(orientationProperties.action, Action.orientationPropertiesUpdate) + XCTAssertEqual(orientationProperties.allowOrientationChange, true) + XCTAssertEqual(orientationProperties.forceOrientation, .landscape) + } +} diff --git a/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDResizeTests.swift b/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDResizeTests.swift new file mode 100644 index 00000000..d0ad8649 --- /dev/null +++ b/CriteoPublisherSdk/Tests/UnitTests/MRAID/MRAIDResizeTests.swift @@ -0,0 +1,153 @@ +// +// MRAIDResizeTests.swift +// CriteoPublisherSdkTests +// +// Copyright © 2018-2023 Criteo. 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 XCTest +import WebKit +@testable import CriteoPublisherSdk + +final class MockMRAIDResizeHandlerDelegate: MRAIDResizeHandlerDelegate { + func didCloseResizedAdView() { + debugPrint(#function) + } +} + +final class MRAIDResizeTests: XCTestCase { + private var resizeHandler: MRAIDResizeHandler? + private var resizeMessage: MRAIDResizeMessage? + private let containerWidth = 100 + private let containerHeight = 100 + private let offsetX = 10 + private let offsetY = 10 + private var webView: WKWebView? + private var viewController: UIViewController? + private var delegate: MockMRAIDResizeHandlerDelegate? + let container = UIView(frame: .init(x: 0, y: 0, width: 390, height: 800)) + + override func setUpWithError() throws { + try super.setUpWithError() + + resizeMessage = MRAIDResizeMessage(action: .resize, + width: containerWidth, + height: containerHeight, + offsetX: offsetX, + offsetY: offsetY, + customClosePosition: .bottomCenter, + allowOffscreen: true) + + webView = WKWebView() + viewController = UIViewController() + viewController?.view.addSubview(webView!) + delegate = MockMRAIDResizeHandlerDelegate() + resizeHandler = MRAIDResizeHandler(webView: webView!, delegate: delegate!) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + + resizeMessage = nil + resizeHandler = nil + viewController = nil + webView = nil + delegate = nil + } + + + func testCloseAreaPositionInAdContainer() throws { + let containerSize = CGSize(width: containerWidth, height: containerHeight) + let handler = try XCTUnwrap(resizeHandler) + let topLeftPosition = handler.closeAreaPositionInAdContainer(with: containerSize, for: .topLeft) + let topRightPosition = handler.closeAreaPositionInAdContainer(with: containerSize, for: .topRight) + let centerPosition = handler.closeAreaPositionInAdContainer(with: containerSize, for: .center) + let bottomLeftPosition = handler.closeAreaPositionInAdContainer(with: containerSize, for: .bottomLeft) + let bottomRightPosition = handler.closeAreaPositionInAdContainer(with: containerSize, for: .bottomRight) + let topCenterPosition = handler.closeAreaPositionInAdContainer(with: containerSize, for: .topCenter) + let bottomCenterPosition = handler.closeAreaPositionInAdContainer(with: containerSize, for: .bottomCenter) + + let minWidth = Int(MRAIDResizeHandler.Constants.minWidth) + let minHeight = Int(MRAIDResizeHandler.Constants.minHeight) + XCTAssertEqual(topLeftPosition, .init(x: 0, y: 0)) + XCTAssertEqual(topRightPosition, .init(x: containerWidth - minWidth, y: 0)) + XCTAssertEqual(centerPosition, .init(x: containerWidth / 2 - minWidth / 2, y: containerHeight / 2 - minHeight / 2)) + XCTAssertEqual(bottomLeftPosition, .init(x: 0, y: containerHeight - minHeight)) + XCTAssertEqual(bottomRightPosition, .init(x: containerWidth - minWidth, y: containerHeight - minHeight)) + XCTAssertEqual(topCenterPosition, .init(x: containerWidth / 2 - minWidth / 2, y: 0)) + XCTAssertEqual(bottomCenterPosition, .init(x: containerWidth / 2 - minWidth / 2, y: containerHeight - minHeight)) + } + + func testCloseAreaOutOfBounds() throws { + let handler = try XCTUnwrap(resizeHandler) + let containerSize = CGSize(width: containerWidth, height: containerHeight) + let positionInContainer: CGPoint = .init(x: 60, y: 0) + + let outOfBoundsResizeMessage = MRAIDResizeMessage(action: .resize, + width: containerWidth, + height: containerHeight, + offsetX: offsetX, + offsetY: offsetY, + customClosePosition: .topRight, + allowOffscreen: true) + XCTAssertNil(try? handler.verifyCloseAreaOutOfBounds(containerSize: containerSize, + positionInContainer: positionInContainer, + message: outOfBoundsResizeMessage, + autoRotate: true)) + + + } + + func testResizeState() { + /// ad can resize only in default or resized state + XCTAssertTrue(try XCTUnwrap(resizeHandler?.canResize(mraidState: .resized))) + XCTAssertTrue(try XCTUnwrap(resizeHandler?.canResize(mraidState: .default))) + XCTAssertFalse(try XCTUnwrap(resizeHandler?.canResize(mraidState: .loading))) + XCTAssertFalse(try XCTUnwrap(resizeHandler?.canResize(mraidState: .hidden))) + XCTAssertFalse(try XCTUnwrap(resizeHandler?.canResize(mraidState: .expanded))) + } + + func testMinSize(for resizeMessage: MRAIDResizeMessage) throws { + resizeHandler = MRAIDResizeHandler(webView: webView!, delegate: delegate!) + do { + try resizeHandler?.resize(with: resizeMessage, webViewContainer: container) + XCTFail("Resize shouldn't be possible if the width or hight are below min values") + } catch { + let resizeErro = try XCTUnwrap(error as? MRAIDResizeHandler.ResizeError) + XCTAssertEqual(resizeErro, MRAIDResizeHandler.ResizeError.exceedingMinSize) + } + } + + func testMinSize() throws { + resizeMessage = MRAIDResizeMessage(action: .resize, + width: 49, + height: 100, + offsetX: offsetX, + offsetY: offsetY, + customClosePosition: .topLeft, + allowOffscreen: true) + try testMinSize(for: resizeMessage!) + + resizeMessage = MRAIDResizeMessage(action: .resize, + width: 50, + height: -1, + offsetX: offsetX, + offsetY: offsetY, + customClosePosition: .topLeft, + allowOffscreen: true) + + try testMinSize(for: resizeMessage! ) + } +} diff --git a/CriteoPublisherSdk/Tests/UnitTests/Network/CR_ApiHandlerTests.m b/CriteoPublisherSdk/Tests/UnitTests/Network/CR_ApiHandlerTests.m index 0610f7c4..2867b731 100644 --- a/CriteoPublisherSdk/Tests/UnitTests/Network/CR_ApiHandlerTests.m +++ b/CriteoPublisherSdk/Tests/UnitTests/Network/CR_ApiHandlerTests.m @@ -698,7 +698,8 @@ - (CR_Config *)buildConfigMock { OCMStub([mockConfig deviceOs]).andReturn(@"ios"); OCMStub([mockConfig appEventsUrl]).andReturn(@"https://appevent.com"); OCMStub([mockConfig appEventsSenderId]).andReturn(@"com.sdk.test"); - OCMStub([mockConfig isMRAIDEnabled]).andReturn(NO); + OCMStub([mockConfig isMraidEnabled]).andReturn(NO); + OCMStub([mockConfig isMraid2Enabled]).andReturn(NO); OCMStub([mockConfig storeId]).andReturn(@("12")); return mockConfig; } diff --git a/CriteoPublisherSdk/Tests/UnitTests/Standalone/CRBannerViewTests.m b/CriteoPublisherSdk/Tests/UnitTests/Standalone/CRBannerViewTests.m index 2a56a0f6..ce57caca 100644 --- a/CriteoPublisherSdk/Tests/UnitTests/Standalone/CRBannerViewTests.m +++ b/CriteoPublisherSdk/Tests/UnitTests/Standalone/CRBannerViewTests.m @@ -31,6 +31,7 @@ #import "MockWKWebView.h" #import "XCTestCase+Criteo.h" #import "CR_NativeAssets+Testing.h" +#import "NSUserDefaults+CR_Config.h" #if __has_include("CriteoPublisherSdkTests-Swift.h") #import "CriteoPublisherSdkTests-Swift.h" @@ -363,12 +364,10 @@ - (void)testTemplatingFromConfig { } - (CRBannerView *)testbannerViewWithMRAID:(BOOL)mraidFlag { - CR_Config *config = [CR_Config new]; - config.adTagUrlMode = @"Good Morning, my width is #WEEDTH# and my URL is ˆURLˆ"; - config.displayURLMacro = @"ˆURLˆ"; - config.mraidEnabled = mraidFlag; - self.criteo.dependencyProvider.config = config; - + NSUserDefaults *userdefaults = self.criteo.dependencyProvider.userDefaults; + [userdefaults cr_setValueForMRAID:mraidFlag]; + [userdefaults cr_setValueForMRAID2:mraidFlag]; + self.criteo.dependencyProvider.config = [[CR_Config alloc] initWithUserDefaults:userdefaults]; WKWebView *mockWebView = OCMPartialMock([WKWebView new]); CR_CdbBid *cdbBid = [self cdbBidWithDisplayUrl:TEST_DISPLAY_URL]; CRBid *bid = [[CRBid alloc] initWithCdbBid:cdbBid adUnit:self.adUnit]; @@ -460,7 +459,11 @@ - (id)checkMessageContainsString:(NSString *)string { - (void)testExpandMRAIDAction { WKWebView *mockWebView = [WKWebView new]; - self.criteo.dependencyProvider.config.mraidEnabled = YES; + NSUserDefaults *userdefaults = self.criteo.dependencyProvider.userDefaults; + [userdefaults cr_setValueForMRAID:YES]; + [userdefaults cr_setValueForMRAID2:YES]; + + self.criteo.dependencyProvider.config = [[CR_Config alloc] initWithUserDefaults:userdefaults]; CRBannerView *bannerView = [[CRBannerView alloc] initWithFrame:CGRectMake(13.0f, 17.0f, 47.0f, 57.0f) criteo:self.criteo @@ -473,6 +476,7 @@ - (void)testExpandMRAIDAction { [bannerView.mraidHandler updateMraidWithBundle:[CR_MRAIDUtils mraidBundle]]; [bannerView loadAdWithBid:bid]; + XCTAssertNotNil(bannerView.mraidHandler); XCTestExpectation *bannerReceiveExpandAction = [[XCTestExpectation alloc] initWithDescription:@"expand action is received"]; diff --git a/CriteoPublisherSdk/Tests/Utility/Categories/NSUserDefaults+Testing.h b/CriteoPublisherSdk/Tests/Utility/Categories/NSUserDefaults+Testing.h index 70128669..10764345 100644 --- a/CriteoPublisherSdk/Tests/Utility/Categories/NSUserDefaults+Testing.h +++ b/CriteoPublisherSdk/Tests/Utility/Categories/NSUserDefaults+Testing.h @@ -29,5 +29,6 @@ FOUNDATION_EXPORT NSString* const NSUserDefaultsLiveBiddingEnabledKey; FOUNDATION_EXPORT NSString* const NSUserDefaultsLiveBiddingTimeBudgetKey; FOUNDATION_EXPORT NSString* const NSUserDefaultsRemoteLogLevelKey; FOUNDATION_EXPORT NSString* const NSUserDefaultsMRAIDKey; +FOUNDATION_EXPORT NSString* const NSUserDefaultsMRAID2Key; #endif /* NSUserDefaults_Testing_H */ diff --git a/Gemfile.lock b/Gemfile.lock index e9bb7482..62558e8d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,7 +9,7 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.4) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) @@ -17,20 +17,20 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.780.0) - aws-sdk-core (3.175.0) + aws-partitions (1.797.0) + aws-sdk-core (3.180.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.67.0) - aws-sdk-core (~> 3, >= 3.174.0) + aws-sdk-kms (1.71.0) + aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.126.0) - aws-sdk-core (~> 3, >= 3.174.0) + aws-sdk-s3 (1.132.0) + aws-sdk-core (~> 3, >= 3.179.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.2) + aws-sigv4 (~> 1.6) + aws-sigv4 (1.6.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -78,7 +78,7 @@ GEM highline (~> 2.0.0) concurrent-ruby (1.1.9) declarative (0.0.20) - digest-crc (0.6.4) + digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) @@ -167,9 +167,9 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.44.0) + google-apis-androidpublisher_v3 (0.46.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.0) + google-apis-core (0.11.1) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -198,7 +198,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.5.2) + googleauth (1.7.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -238,7 +238,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.5) + rexml (3.2.6) rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) diff --git a/Podfile b/Podfile index 02dbddf7..4ffd11be 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ target 'CriteoPublisherSdkTests' do # Third party SDKs pod 'Google-Mobile-Ads-SDK' - pod 'CriteoMRAID', '~> 1.0.1' + pod 'CriteoMRAID', '~> 2.0.0' end target 'CriteoAdViewer' do @@ -46,7 +46,7 @@ end # Development tools pod 'SwiftLint', '~> 0.45.0' -pod 'CriteoMRAID', '~> 1.0.1' +pod 'CriteoMRAID', '~> 2.0.0' post_install do |installer| installer.pods_project.targets.each do |target| diff --git a/Podfile.lock b/Podfile.lock index 30f065f1..529ac94a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -2,46 +2,46 @@ PODS: - CriteoMRAID (1.0.1) - Eureka (5.4.0) - FunctionalObjC (1.0.2) - - Google-Mobile-Ads-SDK (10.1.0): + - Google-Mobile-Ads-SDK (10.8.0): - GoogleAppMeasurement (< 11.0, >= 7.0) - GoogleUserMessagingPlatform (>= 1.1) - - GoogleAppMeasurement (10.5.0): - - GoogleAppMeasurement/AdIdSupport (= 10.5.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleAppMeasurement (10.12.0): + - GoogleAppMeasurement/AdIdSupport (= 10.12.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.5.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.5.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleAppMeasurement/AdIdSupport (10.12.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.12.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.5.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/MethodSwizzler (~> 7.8) - - GoogleUtilities/Network (~> 7.8) - - "GoogleUtilities/NSData+zlib (~> 7.8)" + - GoogleAppMeasurement/WithoutAdIdSupport (10.12.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30910.0, >= 2.30908.0) - GoogleUserMessagingPlatform (2.0.1) - - GoogleUtilities/AppDelegateSwizzler (7.11.0): + - GoogleUtilities/AppDelegateSwizzler (7.11.4): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.11.0): + - GoogleUtilities/Environment (7.11.4): - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.11.0): + - GoogleUtilities/Logger (7.11.4): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.11.0): + - GoogleUtilities/MethodSwizzler (7.11.4): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.11.0): + - GoogleUtilities/Network (7.11.4): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.11.0)" - - GoogleUtilities/Reachability (7.11.0): + - "GoogleUtilities/NSData+zlib (7.11.4)" + - GoogleUtilities/Reachability (7.11.4): - GoogleUtilities/Logger - nanopb (2.30909.0): - nanopb/decode (= 2.30909.0) @@ -49,7 +49,7 @@ PODS: - nanopb/decode (2.30909.0) - nanopb/encode (2.30909.0) - OCMock (3.9.1) - - PromisesObjC (2.2.0) + - PromisesObjC (2.3.1) - SwiftLint (0.45.1) DEPENDENCIES: @@ -78,13 +78,13 @@ SPEC CHECKSUMS: CriteoMRAID: a037bee17b929a8bede8a07279e01a269cd85210 Eureka: fadaa9fa3d6e402d3c60f78f24edf3d7bafc9c29 FunctionalObjC: bd6aaa4b69abea0a5ac0e860a052c1ebebd5311c - Google-Mobile-Ads-SDK: 894be2e3e3e8281004bda14381f9af7ad73eeffd - GoogleAppMeasurement: 40c70a7d89013f0eca72006c4b9732163ea4cdae + Google-Mobile-Ads-SDK: 69daa7fb42061b425340706e382e87fab3e666a3 + GoogleAppMeasurement: 2d800fab85e7848b1e66a6f8ce5bca06c5aad892 GoogleUserMessagingPlatform: 5f8b30daf181805317b6b985bb51c1ff3beca054 - GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f + GoogleUtilities: c63691989bf362ba0505507da00eeb326192e83e nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 OCMock: 9491e4bec59e0b267d52a9184ff5605995e74be8 - PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef + PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 SwiftLint: 06ac37e4d38c7068e0935bb30cda95f093bec761 PODFILE CHECKSUM: 9c1567c5079d29487ac4dde74c7dd0ce58ab717b