Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Unified Diff: ios/chrome/browser/crash_report/crash_report_background_uploader.mm

Issue 1138703004: [iOS] Upstream CrashReportBackgroundUploader (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update tools/metrics/histograms/histograms.xml Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/crash_report/crash_report_background_uploader.mm
diff --git a/ios/chrome/browser/crash_report/crash_report_background_uploader.mm b/ios/chrome/browser/crash_report/crash_report_background_uploader.mm
new file mode 100644
index 0000000000000000000000000000000000000000..b538de0e04883a1abeefdf6b18e763933b450190
--- /dev/null
+++ b/ios/chrome/browser/crash_report/crash_report_background_uploader.mm
@@ -0,0 +1,366 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/crash_report/crash_report_background_uploader.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/logging.h"
+#include "base/mac/scoped_block.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/time/time.h"
+#import "breakpad/src/client/ios/BreakpadController.h"
+#include "ios/chrome/browser/experimental_flags.h"
+#include "ios/web/public/user_metrics.h"
+
+using base::UserMetricsAction;
+
+namespace {
+
+NSString* const kBackgroundReportUploader =
+ @"com.google.chrome.breakpad.backgroundupload";
+const char* const kUMAMobileCrashBackgroundUploadDelay =
+ "CrashReport.CrashBackgroundUploadDelay";
+const char* const kUMAMobilePendingReportsOnBackgroundWakeUp =
+ "CrashReport.PendingReportsOnBackgroundWakeUp";
+NSString* const kUploadedInBackground = @"uploaded_in_background";
+NSString* const kReportsUploadedInBackground = @"ReportsUploadedInBackground";
+
+NSString* CreateSessionIdentifierFromTask(NSURLSessionTask* task) {
+ return [NSString stringWithFormat:@"%@.%ld", kBackgroundReportUploader,
+ (unsigned long)[task taskIdentifier]];
+}
+
+} // namespace
+
+@interface UrlSessionDelegate : NSObject<NSURLSessionDelegate,
+ NSURLSessionTaskDelegate,
+ NSURLSessionDataDelegate>
++ (instancetype)sharedInstance;
+
+// Sets the completion handler for the URL session current tasks. The
+// |completionHandler| cannot be nil.
+- (void)setSessionCompletionHandler:(ProceduralBlock)completionHandler;
+
+@end
+
+@implementation UrlSessionDelegate {
+ // The completion handler to call when all tasks are completed.
+ base::mac::ScopedBlock<ProceduralBlock> _sessionCompletionHandler;
+ // The number of tasks in progress for the session.
+ int _tasks;
+ // Flag to indicate that URLSessionDidFinishEventsForBackgroundURLSession
+ // has been called, so that no new task will be launched for this session.
+ // It is safe to call completion handler when the pending tasks are completed.
+ BOOL _didFinishEventsCalled;
+}
+
++ (instancetype)sharedInstance {
+ static UrlSessionDelegate* instance = [[UrlSessionDelegate alloc] init];
+ return instance;
+}
+
+- (void)setSessionCompletionHandler:(ProceduralBlock)completionHandler {
+ DCHECK(completionHandler);
+ _sessionCompletionHandler.reset(completionHandler,
+ base::scoped_policy::RETAIN);
+ _didFinishEventsCalled = NO;
+}
+
+- (void)URLSession:(NSURLSession*)session
+ task:(NSURLSessionTask*)dataTask
+ didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge
+ completionHandler:
+ (void (^)(NSURLSessionAuthChallengeDisposition disposition,
+ NSURLCredential* credential))completionHandler {
+ if (![challenge.protectionSpace.authenticationMethod
+ isEqualToString:NSURLAuthenticationMethodServerTrust]) {
+ completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
+ return;
+ }
+ NSString* identifier = CreateSessionIdentifierFromTask(dataTask);
+
+ NSDictionary* configuration =
+ [[NSUserDefaults standardUserDefaults] dictionaryForKey:identifier];
+ NSString* host =
+ [[NSURL URLWithString:[configuration objectForKey:@BREAKPAD_URL]] host];
+ if ([challenge.protectionSpace.host isEqualToString:host]) {
+ NSURLCredential* credential = [NSURLCredential
+ credentialForTrust:challenge.protectionSpace.serverTrust];
+ completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
+ return;
+ }
+ completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
+}
+
+- (void)URLSessionDidFinishEventsForBackgroundURLSession:
+ (NSURLSession*)session {
+ _didFinishEventsCalled = YES;
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ [self callCompletionHandler];
+ }];
+}
+
+- (void)taskFinished {
+ DCHECK_GT(_tasks, 0);
+ _tasks--;
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ [self callCompletionHandler];
+ }];
+}
+
+- (void)callCompletionHandler {
+ if (_tasks > 0 || !_didFinishEventsCalled)
+ return;
+ if (_sessionCompletionHandler) {
+ void (^completionHandler)() = _sessionCompletionHandler.get();
+ completionHandler();
+ _sessionCompletionHandler.reset();
+ }
+}
+
+- (void)URLSession:(NSURLSession*)session
+ dataTask:(NSURLSessionDataTask*)dataTask
+ didReceiveResponse:(NSURLResponse*)response
+ completionHandler:
+ (void (^)(NSURLSessionResponseDisposition disposition))handler {
+ handler(NSURLSessionResponseAllow);
+}
+
+- (void)URLSession:(NSURLSession*)session
+ dataTask:(NSURLSessionDataTask*)dataTask
+ didReceiveData:(NSData*)data {
+ NSString* identifier = CreateSessionIdentifierFromTask(dataTask);
+
+ NSDictionary* configuration =
+ [[NSUserDefaults standardUserDefaults] dictionaryForKey:identifier];
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:identifier];
+ _tasks++;
+
+ if (experimental_flags::IsAlertOnBackgroundUploadEnabled()) {
+ base::scoped_nsobject<UILocalNotification> localNotification(
+ [[UILocalNotification alloc] init]);
+ localNotification.get().fireDate = [NSDate date];
+ base::scoped_nsobject<NSString> reportId(
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
+ localNotification.get().alertBody = [NSString
+ stringWithFormat:@"Crash report uploaded: %@", reportId.get()];
+ [[UIApplication sharedApplication]
+ scheduleLocalNotification:localNotification];
+ }
+
+ [[BreakpadController sharedInstance] withBreakpadRef:^(BreakpadRef ref) {
+ BreakpadHandleNetworkResponse(ref, configuration, data, nil);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self taskFinished];
+ });
+ }];
+}
+
+@end
+
+@implementation CrashReportBackgroundUploader
+
+@synthesize hasPendingCrashReportsToUploadAtStartup;
+
++ (instancetype)sharedInstance {
+ static CrashReportBackgroundUploader* instance =
+ [[CrashReportBackgroundUploader alloc] init];
+ return instance;
+}
+
++ (NSURLSession*)BreakpadBackgroundURLSessionWithCompletionHandler:
+ (ProceduralBlock)completionHandler {
+ static NSURLSession* session = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+
+ // TODO(olivierrobin) When all bots compile with iOS8 release SDK, use
+ // only backgroundSessionConfigurationWithIdentifier.
+ NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration
+ backgroundSessionConfiguration:kBackgroundReportUploader];
+
+ session = [NSURLSession
+ sessionWithConfiguration:sessionConfig
+ delegate:[UrlSessionDelegate sharedInstance]
+ delegateQueue:[NSOperationQueue mainQueue]];
+ });
+ DCHECK(session);
+ if (completionHandler) {
+ [[UrlSessionDelegate sharedInstance]
+ setSessionCompletionHandler:completionHandler];
+ }
+ return session;
+}
+
++ (BOOL)sendNextReport:(NSDictionary*)nextReport
+ withBreakpadRef:(BreakpadRef)ref {
+ NSString* uploadURL =
+ [NSString stringWithString:[nextReport valueForKey:@BREAKPAD_URL]];
+ NSString* tmpDir = NSTemporaryDirectory();
+ NSString* tmpFile = [tmpDir
+ stringByAppendingPathComponent:
+ [NSString
+ stringWithFormat:@"%.0f.%@",
+ [NSDate timeIntervalSinceReferenceDate] * 1000.0,
+ @"txt"]];
+ NSURL* fileURL = [NSURL fileURLWithPath:tmpFile];
+ [nextReport setValue:[fileURL absoluteString] forKey:@BREAKPAD_URL];
+
+#ifndef NDEBUG
+ NSString* BreakpadMinidumpLocation = [NSHomeDirectory()
+ stringByAppendingPathComponent:@"Library/Caches/Breakpad"];
+ [nextReport setValue:BreakpadMinidumpLocation
+ forKey:@kReporterMinidumpDirectoryKey];
+ [nextReport setValue:BreakpadMinidumpLocation
+ forKey:@BREAKPAD_DUMP_DIRECTORY];
+#endif
+
+ [[BreakpadController sharedInstance]
+ threadUnsafeSendReportWithConfiguration:nextReport
+ withBreakpadRef:ref];
+
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ if (![fileManager fileExistsAtPath:tmpFile]) {
+ return NO;
+ }
+
+ NSError* error;
+ NSString* fileString =
+ [NSString stringWithContentsOfFile:tmpFile
+ encoding:NSISOLatin1StringEncoding
+ error:&error];
+
+ // The HTTP content is a MIME multipart. The delimiter of the mime body must
+ // be added to the HTTP headers.
+ // A mime body is of the form
+ // --{delimiter}
+ // content 1
+ // --{delimiter}
+ // content 2
+ // --{delimiter}--
+ // The delimiter can be read on the first line of the file.
+ NSString* delimiter =
+ [[fileString componentsSeparatedByCharactersInSet:
+ [NSCharacterSet newlineCharacterSet]] firstObject];
+ if (![delimiter hasPrefix:@"--"]) {
+ [fileManager removeItemAtPath:tmpFile error:&error];
+ return NO;
+ }
+ delimiter = [[delimiter
+ stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]
+ substringFromIndex:2];
+
+ NSMutableURLRequest* request =
+ [NSMutableURLRequest requestWithURL:[NSURL URLWithString:uploadURL]];
+ [request setHTTPMethod:@"POST"];
+ [request setValue:[NSString
+ stringWithFormat:@"multipart/form-data; boundary=%@",
+ delimiter]
+ forHTTPHeaderField:@"Content-type"];
+ [request setHTTPBody:[NSData dataWithContentsOfFile:tmpFile]];
+
+ NSURLSession* session = [CrashReportBackgroundUploader
+ BreakpadBackgroundURLSessionWithCompletionHandler:nil];
+ NSURLSessionDataTask* dataTask =
+ [session uploadTaskWithRequest:request fromFile:fileURL];
+
+ NSString* identifier = CreateSessionIdentifierFromTask(dataTask);
+ [[NSUserDefaults standardUserDefaults] setObject:nextReport
+ forKey:identifier];
+
+ [dataTask resume];
+ return YES;
+}
+
++ (void)performFetchWithCompletionHandler:
+ (BackgroundFetchCompletionBlock)completionHandler {
+ [[BreakpadController sharedInstance] stop];
+ [[BreakpadController sharedInstance] setParametersToAddAtUploadTime:@{
+ kUploadedInBackground : @"yes"
+ }];
+ [[BreakpadController sharedInstance] start:YES];
+ [[BreakpadController sharedInstance] withBreakpadRef:^(BreakpadRef ref) {
+ // Note that this processing will be done before |sendNextCrashReport|
+ // starts uploading the crashes. The ordering is ensured here because both
+ // the crash report processing and the upload enabling are handled by
+ // posting blocks to a single |dispath_queue_t| in BreakpadController.
+ [[BreakpadController sharedInstance] setUploadingEnabled:YES];
+ [[BreakpadController sharedInstance]
+ getNextReportConfigurationOrSendDelay:^(NSDictionary* nextReport,
+ int delay) {
+ BOOL reportToSend = NO;
+ BOOL uploaded = NO;
+ UMA_HISTOGRAM_COUNTS_100(kUMAMobilePendingReportsOnBackgroundWakeUp,
+ BreakpadGetCrashReportCount(ref));
+ if (delay == 0 && nextReport) {
+ reportToSend = YES;
+ NSNumber* crashTimeNum =
+ [nextReport valueForKey:@BREAKPAD_PROCESS_CRASH_TIME];
+ base::Time crashTime =
+ base::Time::FromTimeT([crashTimeNum intValue]);
+ base::Time now = base::Time::Now();
+ UMA_HISTOGRAM_LONG_TIMES_100(kUMAMobileCrashBackgroundUploadDelay,
+ now - crashTime);
+ uploaded = [self sendNextReport:nextReport withBreakpadRef:ref];
+ }
+ int pendingReports = BreakpadGetCrashReportCount(ref);
+ [[BreakpadController sharedInstance] setUploadingEnabled:NO];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (reportToSend) {
+ if (uploaded) {
+ NSUserDefaults* defaults =
+ [NSUserDefaults standardUserDefaults];
+ NSInteger uploadedCrashes =
+ [defaults integerForKey:kReportsUploadedInBackground];
+ [defaults setInteger:(uploadedCrashes + 1)
+ forKey:kReportsUploadedInBackground];
+ web::RecordAction(
+ UserMetricsAction("BackgroundUploadReportSucceeded"));
+
+ } else {
+ web::RecordAction(
+ UserMetricsAction("BackgroundUploadReportAborted"));
+ }
+ }
+ if (uploaded && pendingReports) {
+ completionHandler(UIBackgroundFetchResultNewData);
+ } else if (pendingReports) {
+ completionHandler(UIBackgroundFetchResultFailed);
+ } else {
+ [[UIApplication sharedApplication]
+ setMinimumBackgroundFetchInterval:
+ UIApplicationBackgroundFetchIntervalNever];
+ completionHandler(UIBackgroundFetchResultNoData);
+ }
+ });
+ }];
+ }];
+}
+
++ (BOOL)canHandleBackgroundURLSession:(NSString*)identifier {
+ return [identifier isEqualToString:kBackgroundReportUploader];
+}
+
++ (void)handleEventsForBackgroundURLSession:(NSString*)identifier
+ completionHandler:(ProceduralBlock)completionHandler {
+ [CrashReportBackgroundUploader
+ BreakpadBackgroundURLSessionWithCompletionHandler:completionHandler];
+}
+
++ (BOOL)hasUploadedCrashReportsInBackground {
+ NSInteger uploadedCrashReportsInBackgroundCount =
+ [[NSUserDefaults standardUserDefaults]
+ integerForKey:kReportsUploadedInBackground];
+ return uploadedCrashReportsInBackgroundCount > 0;
+}
+
++ (void)resetReportsUploadedInBackgroundCount {
+ [[NSUserDefaults standardUserDefaults]
+ removeObjectForKey:kReportsUploadedInBackground];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698