diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index e5f22cd92d01..50ac0a48ee65 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -135,6 +135,7 @@ internal class ObjCType { public CategoryAttribute CategoryAttribute; public TType Type; public ObjCType BaseType; + // This only contains the leaf protocols implemented by this type. public ObjCType [] Protocols; public string [] AdoptedProtocols; public bool IsModel; @@ -159,6 +160,44 @@ internal class ObjCType { public bool IsCategory { get { return CategoryAttribute != null; } } +#if MTOUCH || MMP + HashSet all_protocols; + // This contains all protocols in the type hierarchy. + // Given a type T that implements a protocol with super protocols: + // class T : NSObject, IProtocol2 {} + // [Protocol] + // interface IP1 {} + // [Protocol] + // interface IP2 : IP1 {} + // This property will contain both IP1 and IP2. The Protocols property only contains IP2. + public IEnumerable AllProtocols { + get { + if (Protocols == null || Protocols.Length == 0) + return null; + + if (all_protocols == null) { + var queue = new Queue (Protocols); + var rv = new HashSet (); + while (queue.Count > 0) { + var type = queue.Dequeue (); + if (rv.Add (type)) { + foreach (var iface in type.Type.Resolve ().Interfaces) { + if (!Registrar.Types.TryGetValue (iface.InterfaceType, out var superIface)) { + // This is not an interface that corresponds to a protocol. + continue; + } + queue.Enqueue (superIface); + } + } + } + all_protocols = rv; + } + + return all_protocols; + } + } +#endif + public void VerifyRegisterAttribute (ref List exceptions) { if (RegisterAttribute == null) diff --git a/tests/monotouch-test/Foundation/UrlProtocolTest.cs b/tests/monotouch-test/Foundation/UrlProtocolTest.cs index 1e4b3a3ad185..79cc22d2546d 100644 --- a/tests/monotouch-test/Foundation/UrlProtocolTest.cs +++ b/tests/monotouch-test/Foundation/UrlProtocolTest.cs @@ -9,6 +9,9 @@ using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; + #if XAMCORE_2_0 using Foundation; #if MONOMAC @@ -66,5 +69,140 @@ public void Task () } } #endif + +#if !__WATCHOS__ + [Test] + public void RegistrarTest () + { + Exception ex = null; + var done = new ManualResetEvent (false); + var success = false; + + Task.Run (async () => { + try { + var config = NSUrlSessionConfiguration.DefaultSessionConfiguration; + config.WeakProtocolClasses = NSArray.FromNSObjects (new Class (typeof (CustomUrlProtocol))); + var session = NSUrlSession.FromConfiguration (config); + var custom_url = new NSUrl ("foo://server"); + using (var task = await session.CreateDownloadTaskAsync (custom_url)) { + success = true; + } + } catch (Exception e) { + ex = e; + } finally { + done.Set (); + } + }); + + Assert.IsTrue (TestRuntime.RunAsync (DateTime.Now.AddSeconds (10), () => { }, () => done.WaitOne (0)), "Timed out"); + Assert.IsNull (ex, "Exception"); + Assert.IsTrue (custom_url_protocol_instance.Called_DidCompleteWithError, "DidCompleteWithError"); + // if DidReceiveChallenge is called or not seems to vary between test runs, so we can't assert it. + //Assert.IsFalse (custom_url_protocol_instance.Called_DidReceiveChallenge, "DidReceiveChallenge"); + Assert.IsTrue (custom_url_protocol_instance.Called_DidReceiveData, "DidReceiveData"); + Assert.IsTrue (custom_url_protocol_instance.Called_DidReceiveResponse, "DidReceiveResponse"); + Assert.IsTrue (custom_url_protocol_instance.Called_StartLoading, "StartLoading"); + Assert.IsTrue (custom_url_protocol_instance.Called_StopLoading, "StopLoading"); + Assert.IsTrue (custom_url_protocol_instance.Called_WillPerformHttpRedirection, "WillPerformHttpRedirection"); + + Assert.IsTrue (CustomUrlProtocol.Called_CanInitWithRequest, "CanInitWithRequest"); + Assert.IsTrue (CustomUrlProtocol.Called_GetCanonicalRequest, "GetCanonicalRequest"); + + Assert.IsTrue (success, "Success"); + } + + static CustomUrlProtocol custom_url_protocol_instance; + + public class CustomUrlProtocol : NSUrlProtocol, INSUrlSessionDelegate, INSUrlSessionTaskDelegate, INSUrlSessionDataDelegate { + [Export ("canInitWithRequest:")] + public static new bool CanInitWithRequest (NSUrlRequest request) + { + Called_CanInitWithRequest = true; + return true; + } + public static bool Called_CanInitWithRequest; + + [Export ("canonicalRequestForRequest:")] + public static new NSUrlRequest GetCanonicalRequest (NSUrlRequest request) + { + Called_GetCanonicalRequest = true; + return request; + } + public static bool Called_GetCanonicalRequest; + + [Export ("initWithRequest:cachedResponse:client:")] + public CustomUrlProtocol (NSUrlRequest request, NSCachedUrlResponse cachedResponse, INSUrlProtocolClient client) + : base (request, cachedResponse, client) + { + custom_url_protocol_instance = this; + } + + [Export ("startLoading")] + public override void StartLoading () + { + Called_StartLoading = true; + var config = NSUrlSession.SharedSession.Configuration; + var session = NSUrlSession.FromConfiguration (config, this, new NSOperationQueue ()); + + var task = session.CreateDataTask (new NSUrlRequest (new NSUrl ("https://microsoft.com"))); + task.Resume (); + } + public bool Called_StartLoading; + + [Export ("stopLoading")] + public override void StopLoading () + { + Called_StopLoading = true; + } + public bool Called_StopLoading; + + //NSURLSessionTaskDelegate + [Export ("URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:")] + public virtual void WillPerformHttpRedirection (NSUrlSession session, NSUrlSessionTask task, NSHttpUrlResponse response, NSUrlRequest newRequest, Action completionHandler) + { + Called_WillPerformHttpRedirection = true; + completionHandler (newRequest); + } + public bool Called_WillPerformHttpRedirection; + + [Export ("URLSession:task:didReceiveChallenge:completionHandler:")] + public virtual void DidReceiveChallenge (NSUrlSession session, NSUrlSessionTask task, NSUrlAuthenticationChallenge challenge, Action completionHandler) + { + Called_DidReceiveChallenge = true; + completionHandler (NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, null); + } + public bool Called_DidReceiveChallenge; + + //NSURLSessionDataDelegate + [Export ("URLSession:dataTask:didReceiveResponse:completionHandler:")] + public virtual void DidReceiveResponse (NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action completionHandler) + { + Called_DidReceiveResponse = true; + completionHandler (NSUrlSessionResponseDisposition.Allow); + this.Client.ReceivedResponse (this, response, NSUrlCacheStoragePolicy.Allowed); + } + public bool Called_DidReceiveResponse; + + [Export ("URLSession:dataTask:didReceiveData:")] + public virtual void DidReceiveData (NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data) + { + Called_DidReceiveData = true; + this.Client.DataLoaded (this, data); + } + public bool Called_DidReceiveData; + + [Export ("URLSession:task:didCompleteWithError:")] + public virtual void DidCompleteWithError (NSUrlSession session, NSUrlSessionTask task, NSError error) + { + Called_DidCompleteWithError = true; + if (error != null) { + this.Client.FailedWithError (this, error); + } else { + this.Client.FinishedLoading (this); + } + } + public bool Called_DidCompleteWithError; + } +#endif // !__WATCHOS__ } } diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 1f7e153943dd..4986d472be65 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -4129,10 +4129,11 @@ TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) } // Might be an implementation of an optional protocol member. - if (obj_method.DeclaringType.Protocols != null) { + var allProtocols = obj_method.DeclaringType.AllProtocols; + if (allProtocols != null) { string selector = null; - foreach (var proto in obj_method.DeclaringType.Protocols) { + foreach (var proto in allProtocols) { // We store the DelegateProxy type in the ProtocolMemberAttribute, so check those. if (selector == null) selector = obj_method.Selector ?? string.Empty; @@ -4175,10 +4176,11 @@ MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter) } // Might be an implementation of an optional protocol member. - if (obj_method.DeclaringType.Protocols != null) { + var allProtocols = obj_method.DeclaringType.AllProtocols; + if (allProtocols != null) { string selector = null; - foreach (var proto in obj_method.DeclaringType.Protocols) { + foreach (var proto in allProtocols) { // We store the BlockProxy type in the ProtocolMemberAttribute, so check those. // We may run into binding assemblies built with earlier versions of the generator, // which means we can't rely on finding the BlockProxy attribute in the ProtocolMemberAttribute.