google/EarlGrey

View on GitHub
EarlGrey/Additions/UIApplication+GREYAdditions.m

Summary

Maintainability
Test Coverage
//
// Copyright 2016 Google Inc.
//
// 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 "Additions/UIApplication+GREYAdditions.h"

#include <objc/runtime.h>

#import "Common/GREYAppleInternals.h"
#import "Common/GREYFatalAsserts.h"
#import "Common/GREYSwizzler.h"
#import "Synchronization/GREYAppStateTracker.h"
#import "Synchronization/GREYAppStateTrackerObject.h"

/**
 *  List for all the runloop modes that have been pushed and unpopped using UIApplication's push/pop
 *  runloop mode methods. The most recently pushed runloop mode is at the end of the list.
 */
static NSMutableArray *gRunLoopModes;

@implementation UIApplication (GREYAdditions)

+ (void)load {
  @autoreleasepool {
    gRunLoopModes = [[NSMutableArray alloc] init];

    GREYSwizzler *swizzler = [[GREYSwizzler alloc] init];
    SEL originalSel = @selector(beginIgnoringInteractionEvents);
    SEL swizzledSel = @selector(greyswizzled_beginIgnoringInteractionEvents);
    BOOL swizzleSuccess = [swizzler swizzleClass:self
                           replaceInstanceMethod:originalSel
                                      withMethod:swizzledSel];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle UIApplication beginIgnoringInteractionEvents");
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:@selector(endIgnoringInteractionEvents)
                                 withMethod:@selector(greyswizzled_endIgnoringInteractionEvents)];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle UIApplication endIgnoringInteractionEvents");
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:@selector(pushRunLoopMode:)
                                 withMethod:@selector(greyswizzled_pushRunLoopMode:)];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle UIApplication pushRunLoopMode:");
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:@selector(pushRunLoopMode:requester:)
                                 withMethod:@selector(greyswizzled_pushRunLoopMode:requester:)];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle UIApplication pushRunLoopMode:requester:");
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:@selector(popRunLoopMode:)
                                 withMethod:@selector(greyswizzled_popRunLoopMode:)];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle UIApplication popRunLoopMode:");
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:@selector(popRunLoopMode:requester:)
                                 withMethod:@selector(greyswizzled_popRunLoopMode:requester:)];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle UIApplication popRunLoopMode:requester:");
  }
}

- (NSString *)grey_activeRunLoopMode {
  @synchronized(gRunLoopModes) {
    // could be nil.
    return [gRunLoopModes lastObject];
  }
}

#pragma mark - Swizzled Implementation

- (void)greyswizzled_beginIgnoringInteractionEvents {
  INVOKE_ORIGINAL_IMP(void, @selector(greyswizzled_beginIgnoringInteractionEvents));
  GREYAppStateTrackerObject *object =
      TRACK_STATE_FOR_OBJECT(kGREYIgnoringSystemWideUserInteraction, self);
  objc_setAssociatedObject(self,
                           @selector(greyswizzled_beginIgnoringInteractionEvents),
                           object,
                           OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)greyswizzled_endIgnoringInteractionEvents {
  INVOKE_ORIGINAL_IMP(void, @selector(greyswizzled_endIgnoringInteractionEvents));
  // begin/end can be nested, instead of keeping the count, simply use isIgnoringInteractionEvents.
  if (!self.isIgnoringInteractionEvents) {
    GREYAppStateTrackerObject *object =
        objc_getAssociatedObject(self, @selector(greyswizzled_beginIgnoringInteractionEvents));
    UNTRACK_STATE_FOR_OBJECT(kGREYIgnoringSystemWideUserInteraction, object);
    objc_setAssociatedObject(self,
                             @selector(greyswizzled_beginIgnoringInteractionEvents),
                             nil,
                             OBJC_ASSOCIATION_ASSIGN);
  }
}

- (void)greyswizzled_pushRunLoopMode:(NSString *)mode {
  [self grey_pushRunLoopMode:mode];
  INVOKE_ORIGINAL_IMP1(void, @selector(greyswizzled_pushRunLoopMode:), mode);
}

- (void)greyswizzled_pushRunLoopMode:(NSString *)mode requester:(id)requester {
  [self grey_pushRunLoopMode:mode];
  INVOKE_ORIGINAL_IMP2(void, @selector(greyswizzled_pushRunLoopMode:requester:), mode, requester);
}

- (void)greyswizzled_popRunLoopMode:(NSString *)mode {
  [self grey_popRunLoopMode:mode];
  INVOKE_ORIGINAL_IMP1(void, @selector(greyswizzled_popRunLoopMode:), mode);
}

- (void)greyswizzled_popRunLoopMode:(NSString *)mode requester:(id)requester {
  [self grey_popRunLoopMode:mode];
  INVOKE_ORIGINAL_IMP2(void, @selector(greyswizzled_popRunLoopMode:requester:), mode, requester);
}

#pragma mark - Private

- (void)grey_pushRunLoopMode:(NSString *)mode {
  @synchronized(gRunLoopModes) {
    [gRunLoopModes addObject:mode];
  }
}

- (void)grey_popRunLoopMode:(NSString *)mode {
  @synchronized(gRunLoopModes) {
    NSString *topOfStackMode = [gRunLoopModes lastObject];
    if (![topOfStackMode isEqual:mode]) {
      NSLog(@"Mode being popped: %@ isn't top of stack: %@", mode, topOfStackMode);
      abort();
    }
    [gRunLoopModes removeLastObject];
  }
}

@end