Index: ios/chrome/browser/crash_report/crash_report_helper.mm |
diff --git a/ios/chrome/browser/crash_report/crash_report_helper.mm b/ios/chrome/browser/crash_report/crash_report_helper.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ffa13f6a886f3e749b34ea53e544b31aa8c4275c |
--- /dev/null |
+++ b/ios/chrome/browser/crash_report/crash_report_helper.mm |
@@ -0,0 +1,352 @@ |
+// Copyright 2012 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. |
+ |
+#include "ios/chrome/browser/crash_report/crash_report_helper.h" |
+ |
+#include <Foundation/Foundation.h> |
+ |
+#include "base/auto_reset.h" |
+#include "base/bind.h" |
+#include "base/debug/crash_logging.h" |
+#include "base/files/file_enumerator.h" |
+#include "base/files/file_path.h" |
+#include "base/files/file_util.h" |
+#include "base/location.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/path_service.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/time/time.h" |
+#include "components/upload_list/crash_upload_list.h" |
+#include "ios/chrome/browser/chrome_paths.h" |
+#include "ios/chrome/browser/crash_report/breakpad_helper.h" |
+#import "ios/chrome/browser/crash_report/crash_report_user_application_state.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model.h" |
+#import "ios/chrome/browser/tabs/tab_model_observer.h" |
+#include "ios/web/public/browser_state.h" |
+#include "ios/web/public/web_state/web_state.h" |
+#include "ios/web/public/web_thread.h" |
+#import "net/base/mac/url_conversions.h" |
+ |
+// TabModelObserver that allows loaded urls to be sent to the crash server. |
+@interface CrashReporterURLObserver : NSObject<TabModelObserver> { |
+ @private |
+ // Map associating the tab id to the breakpad key used to keep track of the |
+ // loaded URL. |
+ base::scoped_nsobject<NSMutableDictionary> breakpadKeyByTabId_; |
+ // List of keys to use for recording URLs. This list is sorted such that a new |
+ // tab must use the first key in this list to record its URLs. |
+ base::scoped_nsobject<NSMutableArray> breakpadKeys_; |
+} |
++ (CrashReporterURLObserver*)uniqueInstance; |
+// Removes the URL for the tab with the given id from the URLs sent to the crash |
+// server. |
+- (void)removeTabId:(NSString*)tabId; |
+// Records the given URL associated to the given id to the list of URLs to send |
+// to the crash server. If |pending| is true, the URL is one that is |
+// expected to start loading, but hasn't actually been seen yet. |
+- (void)recordURL:(NSString*)url |
+ forTabId:(NSString*)tabId |
+ pending:(BOOL)pending; |
+// Callback for the kTabUrlStartedLoadingNotificationForCrashReporting |
+// notification. Extracts the parameter from the notification and calls |
+// |recordURL:forTabId:pending:|. |
+- (void)urlChanged:(NSNotification*)notification; |
+// Callback for the kTabUrlMayStartLoadingNotificationForCrashReporting |
+// notification. Extracts the parameter from the notification and calls |
+// |recordURL:forTabId:pending:|. |
+- (void)urlChangeExpected:(NSNotification*)notification; |
+@end |
+ |
+// TabModelObserver that some tabs stats to be sent to the crash server. |
+@interface CrashReporterTabStateObserver : NSObject<TabModelObserver> { |
+ @private |
+ // Map associating the tab id to an object describing the current state of the |
+ // tab. |
+ base::scoped_nsobject<NSMutableDictionary> tabCurrentStateByTabId_; |
+} |
++ (CrashReporterURLObserver*)uniqueInstance; |
+// Removes the stats for the tab tabId |
+- (void)removeTabId:(NSString*)tabId; |
+// Callback for the kTabClosingCurrentDocumentNotificationForCrashReporting |
+// notification. Removes document related information from |
+// tabCurrentStateByTabId_ by calling closingDocumentInTab:tabId. |
+- (void)closingDocument:(NSNotification*)notification; |
+// Removes document related information from tabCurrentStateByTabId_. |
+- (void)closingDocumentInTab:(NSString*)tabId; |
+// Callback for the kTabIsShowingExportableNotificationForCrashReporting |
+// notification. Sets the mimeType in tabCurrentStateByTabId_. |
+- (void)showingExportableDocument:(NSNotification*)notification; |
+ |
+// Sets a tab |tabId| specific information with key |key| and value |value| in |
+// tabCurrentStateByTabId_. |
+- (void)setTabInfo:(NSString*)key |
+ withValue:(NSString*)value |
+ forTab:(NSString*)tabId; |
+// Retrieves the |key| information for tab |tabId|. |
+- (id)getTabInfo:(NSString*)key forTab:(NSString*)tabId; |
+// Removes the |key| information for tab |tabId| |
+- (void)removeTabInfo:(NSString*)key forTab:(NSString*)tabId; |
+@end |
+ |
+namespace { |
+ |
+// Returns the breakpad key to use for a pending URL corresponding to the |
+// same tab that is using |key|. |
+NSString* PendingURLKeyForKey(NSString* key) { |
+ return [key stringByAppendingString:@"-pending"]; |
+} |
+ |
+// Max number of urls to send. This must be kept low for privacy issue as well |
+// as because breakpad does limit the total number of parameters to 64. |
+const int kNumberOfURLsToSend = 1; |
+} |
+ |
+@implementation CrashReporterURLObserver |
+ |
++ (CrashReporterURLObserver*)uniqueInstance { |
+ static CrashReporterURLObserver* instance = |
+ [[CrashReporterURLObserver alloc] init]; |
+ return instance; |
+} |
+ |
+- (id)init { |
+ if ((self = [super init])) { |
+ breakpadKeyByTabId_.reset( |
+ [[NSMutableDictionary alloc] initWithCapacity:kNumberOfURLsToSend]); |
+ breakpadKeys_.reset( |
+ [[NSMutableArray alloc] initWithCapacity:kNumberOfURLsToSend]); |
+ for (int i = 0; i < kNumberOfURLsToSend; ++i) |
+ [breakpadKeys_ addObject:[NSString stringWithFormat:@"url%d", i]]; |
+ // Register for url changed notifications. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(urlChanged:) |
+ name:kTabUrlStartedLoadingNotificationForCrashReporting |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(urlChangeExpected:) |
+ name:kTabUrlMayStartLoadingNotificationForCrashReporting |
+ object:nil]; |
+ } |
+ return self; |
+} |
+ |
+- (void)urlChanged:(NSNotification*)notification { |
+ Tab* tab = notification.object; |
+ DCHECK(tab); |
+ if (tab.webState->GetBrowserState()->IsOffTheRecord()) |
+ return; |
+ NSString* url = [notification.userInfo objectForKey:kTabUrlKey]; |
+ DCHECK(url); |
+ [self recordURL:url forTabId:tab.tabId pending:NO]; |
+} |
+ |
+- (void)urlChangeExpected:(NSNotification*)notification { |
+ Tab* tab = notification.object; |
+ DCHECK(tab); |
+ if (tab.webState->GetBrowserState()->IsOffTheRecord()) |
+ return; |
+ NSString* url = [notification.userInfo objectForKey:kTabUrlKey]; |
+ DCHECK(url); |
+ [self recordURL:url forTabId:tab.tabId pending:YES]; |
+} |
+ |
+- (void)removeTabId:(NSString*)tabId { |
+ NSString* key = [breakpadKeyByTabId_ objectForKey:tabId]; |
+ if (!key) |
+ return; |
+ base::scoped_nsobject<NSString> alive([key retain]); |
+ breakpad_helper::RemoveReportParameter(key); |
+ breakpad_helper::RemoveReportParameter(PendingURLKeyForKey(key)); |
+ [breakpadKeyByTabId_ removeObjectForKey:tabId]; |
+ [breakpadKeys_ removeObject:key]; |
+ [breakpadKeys_ insertObject:key atIndex:0]; |
+} |
+ |
+- (void)recordURL:(NSString*)url |
+ forTabId:(NSString*)tabId |
+ pending:(BOOL)pending { |
+ NSString* breakpadKey = [breakpadKeyByTabId_ objectForKey:tabId]; |
+ BOOL reusingKey = NO; |
+ if (!breakpadKey) { |
+ // Get the first breakpad key and push it back at the end of the keys. |
+ base::scoped_nsobject<NSString> alive( |
+ [[breakpadKeys_ objectAtIndex:0] retain]); |
+ breakpadKey = alive.get(); |
+ [breakpadKeys_ removeObject:breakpadKey]; |
+ [breakpadKeys_ addObject:breakpadKey]; |
+ // Remove the current mapping to the breakpad key. |
+ for (NSString* tabId in |
+ [breakpadKeyByTabId_ allKeysForObject:breakpadKey]) { |
+ reusingKey = YES; |
+ [breakpadKeyByTabId_ removeObjectForKey:tabId]; |
+ } |
+ // Associate the breakpad key to the tab id. |
+ [breakpadKeyByTabId_ setObject:breakpadKey forKey:tabId]; |
+ } |
+ NSString* pendingKey = PendingURLKeyForKey(breakpadKey); |
+ if (pending) { |
+ if (reusingKey) |
+ breakpad_helper::RemoveReportParameter(breakpadKey); |
+ breakpad_helper::AddReportParameter(pendingKey, url, true); |
+ } else { |
+ breakpad_helper::AddReportParameter(breakpadKey, url, true); |
+ breakpad_helper::RemoveReportParameter(pendingKey); |
+ } |
+} |
+ |
+- (void)tabModel:(TabModel*)model |
+ didRemoveTab:(Tab*)tab |
+ atIndex:(NSUInteger)index { |
+ [self removeTabId:tab.tabId]; |
+} |
+ |
+- (void)tabModel:(TabModel*)model |
+ didReplaceTab:(Tab*)oldTab |
+ withTab:(Tab*)newTab |
+ atIndex:(NSUInteger)index { |
+ [self removeTabId:oldTab.tabId]; |
+} |
+ |
+- (void)tabModel:(TabModel*)model |
+ didChangeActiveTab:(Tab*)newTab |
+ previousTab:(Tab*)previousTab |
+ atIndex:(NSUInteger)modelIndex { |
+ [self recordURL:base::SysUTF8ToNSString(newTab.url.spec()) |
+ forTabId:newTab.tabId |
+ pending:NO]; |
+} |
+ |
+// Empty method left in place in case jailbreakers are swizzling this. |
+- (void)detectJailbrokenDevice { |
+ // This method has been intentionally left blank. |
+} |
+ |
+@end |
+ |
+@implementation CrashReporterTabStateObserver |
+ |
++ (CrashReporterTabStateObserver*)uniqueInstance { |
+ static CrashReporterTabStateObserver* instance = |
+ [[CrashReporterTabStateObserver alloc] init]; |
+ return instance; |
+} |
+ |
+- (id)init { |
+ if ((self = [super init])) { |
+ tabCurrentStateByTabId_.reset([[NSMutableDictionary alloc] init]); |
+ // Register for url changed notifications. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(closingDocument:) |
+ name:kTabClosingCurrentDocumentNotificationForCrashReporting |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(showingExportableDocument:) |
+ name:kTabIsShowingExportableNotificationForCrashReporting |
+ object:nil]; |
+ } |
+ return self; |
+} |
+ |
+- (void)closingDocument:(NSNotification*)notification { |
+ Tab* tab = notification.object; |
+ [self closingDocumentInTab:[tab tabId]]; |
+} |
+ |
+- (void)closingDocumentInTab:(NSString*)tabId { |
+ NSString* mime = (NSString*)[self getTabInfo:@"mime" forTab:tabId]; |
+ if ([mime isEqualToString:@"application/pdf"]) |
+ breakpad_helper::SetCurrentTabIsPDF(false); |
+ [self removeTabInfo:@"mime" forTab:tabId]; |
+} |
+ |
+- (void)setTabInfo:(NSString*)key |
+ withValue:(NSString*)value |
+ forTab:(NSString*)tabId { |
+ NSMutableDictionary* tabCurrentState = |
+ [tabCurrentStateByTabId_ objectForKey:tabId]; |
+ if (tabCurrentState == nil) { |
+ base::scoped_nsobject<NSMutableDictionary> currentStateOfNewTab( |
+ [[NSMutableDictionary alloc] init]); |
+ [tabCurrentStateByTabId_ setObject:currentStateOfNewTab.get() forKey:tabId]; |
+ tabCurrentState = [tabCurrentStateByTabId_ objectForKey:tabId]; |
+ } |
+ [tabCurrentState setObject:value forKey:key]; |
+} |
+ |
+- (id)getTabInfo:(NSString*)key forTab:(NSString*)tabId { |
+ NSMutableDictionary* tabValues = [tabCurrentStateByTabId_ objectForKey:tabId]; |
+ return [tabValues objectForKey:key]; |
+} |
+ |
+- (void)removeTabInfo:(NSString*)key forTab:(NSString*)tabId { |
+ [[tabCurrentStateByTabId_ objectForKey:tabId] removeObjectForKey:key]; |
+} |
+ |
+- (void)showingExportableDocument:(NSNotification*)notification { |
+ Tab* tab = notification.object; |
+ NSString* oldMime = (NSString*)[self getTabInfo:@"mime" forTab:[tab tabId]]; |
+ if ([oldMime isEqualToString:@"application/pdf"]) |
+ return; |
+ |
+ std::string mime = [tab webState]->GetContentsMimeType(); |
+ NSString* nsMime = base::SysUTF8ToNSString(mime); |
+ [self setTabInfo:@"mime" withValue:nsMime forTab:[tab tabId]]; |
+ breakpad_helper::SetCurrentTabIsPDF(true); |
+} |
+ |
+- (void)removeTabId:(NSString*)tabId { |
+ [self closingDocumentInTab:tabId]; |
+ [tabCurrentStateByTabId_ removeObjectForKey:tabId]; |
+} |
+ |
+- (void)tabModel:(TabModel*)model |
+ didRemoveTab:(Tab*)tab |
+ atIndex:(NSUInteger)index { |
+ [self removeTabId:tab.tabId]; |
+} |
+ |
+- (void)tabModel:(TabModel*)model |
+ didReplaceTab:(Tab*)oldTab |
+ withTab:(Tab*)newTab |
+ atIndex:(NSUInteger)index { |
+ [self removeTabId:oldTab.tabId]; |
+} |
+ |
+@end |
+ |
+namespace ios_internal { |
+namespace breakpad { |
+ |
+void MonitorURLsForTabModel(TabModel* tab_model) { |
+ DCHECK(!tab_model.isOffTheRecord); |
+ [tab_model addObserver:[CrashReporterURLObserver uniqueInstance]]; |
+} |
+ |
+void StopMonitoringURLsForTabModel(TabModel* tab_model) { |
+ [tab_model removeObserver:[CrashReporterURLObserver uniqueInstance]]; |
+} |
+ |
+void MonitorTabStateForTabModel(TabModel* tab_model) { |
+ [tab_model addObserver:[CrashReporterTabStateObserver uniqueInstance]]; |
+} |
+ |
+void StopMonitoringTabStateForTabModel(TabModel* tab_model) { |
+ [tab_model removeObserver:[CrashReporterTabStateObserver uniqueInstance]]; |
+} |
+ |
+void ClearStateForTabModel(TabModel* tab_model) { |
+ CrashReporterURLObserver* observer = |
+ [CrashReporterURLObserver uniqueInstance]; |
+ for (Tab* tab in tab_model) { |
+ [observer removeTabId:tab.tabId]; |
+ } |
+} |
+ |
+} // namespace breakpad |
+} // namespace ios_internal |