OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "ios/chrome/browser/crash_report/crash_report_helper.h" |
| 6 |
| 7 #include <Foundation/Foundation.h> |
| 8 |
| 9 #include "base/auto_reset.h" |
| 10 #include "base/bind.h" |
| 11 #include "base/debug/crash_logging.h" |
| 12 #include "base/files/file_enumerator.h" |
| 13 #include "base/files/file_path.h" |
| 14 #include "base/files/file_util.h" |
| 15 #include "base/location.h" |
| 16 #include "base/mac/scoped_nsobject.h" |
| 17 #include "base/path_service.h" |
| 18 #include "base/strings/sys_string_conversions.h" |
| 19 #include "base/time/time.h" |
| 20 #include "components/upload_list/crash_upload_list.h" |
| 21 #include "ios/chrome/browser/chrome_paths.h" |
| 22 #include "ios/chrome/browser/crash_report/breakpad_helper.h" |
| 23 #import "ios/chrome/browser/crash_report/crash_report_user_application_state.h" |
| 24 #import "ios/chrome/browser/tabs/tab.h" |
| 25 #import "ios/chrome/browser/tabs/tab_model.h" |
| 26 #import "ios/chrome/browser/tabs/tab_model_observer.h" |
| 27 #include "ios/web/public/browser_state.h" |
| 28 #include "ios/web/public/web_state/web_state.h" |
| 29 #include "ios/web/public/web_thread.h" |
| 30 #import "net/base/mac/url_conversions.h" |
| 31 |
| 32 // TabModelObserver that allows loaded urls to be sent to the crash server. |
| 33 @interface CrashReporterURLObserver : NSObject<TabModelObserver> { |
| 34 @private |
| 35 // Map associating the tab id to the breakpad key used to keep track of the |
| 36 // loaded URL. |
| 37 base::scoped_nsobject<NSMutableDictionary> breakpadKeyByTabId_; |
| 38 // List of keys to use for recording URLs. This list is sorted such that a new |
| 39 // tab must use the first key in this list to record its URLs. |
| 40 base::scoped_nsobject<NSMutableArray> breakpadKeys_; |
| 41 } |
| 42 + (CrashReporterURLObserver*)uniqueInstance; |
| 43 // Removes the URL for the tab with the given id from the URLs sent to the crash |
| 44 // server. |
| 45 - (void)removeTabId:(NSString*)tabId; |
| 46 // Records the given URL associated to the given id to the list of URLs to send |
| 47 // to the crash server. If |pending| is true, the URL is one that is |
| 48 // expected to start loading, but hasn't actually been seen yet. |
| 49 - (void)recordURL:(NSString*)url |
| 50 forTabId:(NSString*)tabId |
| 51 pending:(BOOL)pending; |
| 52 // Callback for the kTabUrlStartedLoadingNotificationForCrashReporting |
| 53 // notification. Extracts the parameter from the notification and calls |
| 54 // |recordURL:forTabId:pending:|. |
| 55 - (void)urlChanged:(NSNotification*)notification; |
| 56 // Callback for the kTabUrlMayStartLoadingNotificationForCrashReporting |
| 57 // notification. Extracts the parameter from the notification and calls |
| 58 // |recordURL:forTabId:pending:|. |
| 59 - (void)urlChangeExpected:(NSNotification*)notification; |
| 60 @end |
| 61 |
| 62 // TabModelObserver that some tabs stats to be sent to the crash server. |
| 63 @interface CrashReporterTabStateObserver : NSObject<TabModelObserver> { |
| 64 @private |
| 65 // Map associating the tab id to an object describing the current state of the |
| 66 // tab. |
| 67 base::scoped_nsobject<NSMutableDictionary> tabCurrentStateByTabId_; |
| 68 } |
| 69 + (CrashReporterURLObserver*)uniqueInstance; |
| 70 // Removes the stats for the tab tabId |
| 71 - (void)removeTabId:(NSString*)tabId; |
| 72 // Callback for the kTabClosingCurrentDocumentNotificationForCrashReporting |
| 73 // notification. Removes document related information from |
| 74 // tabCurrentStateByTabId_ by calling closingDocumentInTab:tabId. |
| 75 - (void)closingDocument:(NSNotification*)notification; |
| 76 // Removes document related information from tabCurrentStateByTabId_. |
| 77 - (void)closingDocumentInTab:(NSString*)tabId; |
| 78 // Callback for the kTabIsShowingExportableNotificationForCrashReporting |
| 79 // notification. Sets the mimeType in tabCurrentStateByTabId_. |
| 80 - (void)showingExportableDocument:(NSNotification*)notification; |
| 81 |
| 82 // Sets a tab |tabId| specific information with key |key| and value |value| in |
| 83 // tabCurrentStateByTabId_. |
| 84 - (void)setTabInfo:(NSString*)key |
| 85 withValue:(NSString*)value |
| 86 forTab:(NSString*)tabId; |
| 87 // Retrieves the |key| information for tab |tabId|. |
| 88 - (id)getTabInfo:(NSString*)key forTab:(NSString*)tabId; |
| 89 // Removes the |key| information for tab |tabId| |
| 90 - (void)removeTabInfo:(NSString*)key forTab:(NSString*)tabId; |
| 91 @end |
| 92 |
| 93 namespace { |
| 94 |
| 95 // Returns the breakpad key to use for a pending URL corresponding to the |
| 96 // same tab that is using |key|. |
| 97 NSString* PendingURLKeyForKey(NSString* key) { |
| 98 return [key stringByAppendingString:@"-pending"]; |
| 99 } |
| 100 |
| 101 // Max number of urls to send. This must be kept low for privacy issue as well |
| 102 // as because breakpad does limit the total number of parameters to 64. |
| 103 const int kNumberOfURLsToSend = 1; |
| 104 } |
| 105 |
| 106 @implementation CrashReporterURLObserver |
| 107 |
| 108 + (CrashReporterURLObserver*)uniqueInstance { |
| 109 static CrashReporterURLObserver* instance = |
| 110 [[CrashReporterURLObserver alloc] init]; |
| 111 return instance; |
| 112 } |
| 113 |
| 114 - (id)init { |
| 115 if ((self = [super init])) { |
| 116 breakpadKeyByTabId_.reset( |
| 117 [[NSMutableDictionary alloc] initWithCapacity:kNumberOfURLsToSend]); |
| 118 breakpadKeys_.reset( |
| 119 [[NSMutableArray alloc] initWithCapacity:kNumberOfURLsToSend]); |
| 120 for (int i = 0; i < kNumberOfURLsToSend; ++i) |
| 121 [breakpadKeys_ addObject:[NSString stringWithFormat:@"url%d", i]]; |
| 122 // Register for url changed notifications. |
| 123 [[NSNotificationCenter defaultCenter] |
| 124 addObserver:self |
| 125 selector:@selector(urlChanged:) |
| 126 name:kTabUrlStartedLoadingNotificationForCrashReporting |
| 127 object:nil]; |
| 128 [[NSNotificationCenter defaultCenter] |
| 129 addObserver:self |
| 130 selector:@selector(urlChangeExpected:) |
| 131 name:kTabUrlMayStartLoadingNotificationForCrashReporting |
| 132 object:nil]; |
| 133 } |
| 134 return self; |
| 135 } |
| 136 |
| 137 - (void)urlChanged:(NSNotification*)notification { |
| 138 Tab* tab = notification.object; |
| 139 DCHECK(tab); |
| 140 if (tab.webState->GetBrowserState()->IsOffTheRecord()) |
| 141 return; |
| 142 NSString* url = [notification.userInfo objectForKey:kTabUrlKey]; |
| 143 DCHECK(url); |
| 144 [self recordURL:url forTabId:tab.tabId pending:NO]; |
| 145 } |
| 146 |
| 147 - (void)urlChangeExpected:(NSNotification*)notification { |
| 148 Tab* tab = notification.object; |
| 149 DCHECK(tab); |
| 150 if (tab.webState->GetBrowserState()->IsOffTheRecord()) |
| 151 return; |
| 152 NSString* url = [notification.userInfo objectForKey:kTabUrlKey]; |
| 153 DCHECK(url); |
| 154 [self recordURL:url forTabId:tab.tabId pending:YES]; |
| 155 } |
| 156 |
| 157 - (void)removeTabId:(NSString*)tabId { |
| 158 NSString* key = [breakpadKeyByTabId_ objectForKey:tabId]; |
| 159 if (!key) |
| 160 return; |
| 161 base::scoped_nsobject<NSString> alive([key retain]); |
| 162 breakpad_helper::RemoveReportParameter(key); |
| 163 breakpad_helper::RemoveReportParameter(PendingURLKeyForKey(key)); |
| 164 [breakpadKeyByTabId_ removeObjectForKey:tabId]; |
| 165 [breakpadKeys_ removeObject:key]; |
| 166 [breakpadKeys_ insertObject:key atIndex:0]; |
| 167 } |
| 168 |
| 169 - (void)recordURL:(NSString*)url |
| 170 forTabId:(NSString*)tabId |
| 171 pending:(BOOL)pending { |
| 172 NSString* breakpadKey = [breakpadKeyByTabId_ objectForKey:tabId]; |
| 173 BOOL reusingKey = NO; |
| 174 if (!breakpadKey) { |
| 175 // Get the first breakpad key and push it back at the end of the keys. |
| 176 base::scoped_nsobject<NSString> alive( |
| 177 [[breakpadKeys_ objectAtIndex:0] retain]); |
| 178 breakpadKey = alive.get(); |
| 179 [breakpadKeys_ removeObject:breakpadKey]; |
| 180 [breakpadKeys_ addObject:breakpadKey]; |
| 181 // Remove the current mapping to the breakpad key. |
| 182 for (NSString* tabId in |
| 183 [breakpadKeyByTabId_ allKeysForObject:breakpadKey]) { |
| 184 reusingKey = YES; |
| 185 [breakpadKeyByTabId_ removeObjectForKey:tabId]; |
| 186 } |
| 187 // Associate the breakpad key to the tab id. |
| 188 [breakpadKeyByTabId_ setObject:breakpadKey forKey:tabId]; |
| 189 } |
| 190 NSString* pendingKey = PendingURLKeyForKey(breakpadKey); |
| 191 if (pending) { |
| 192 if (reusingKey) |
| 193 breakpad_helper::RemoveReportParameter(breakpadKey); |
| 194 breakpad_helper::AddReportParameter(pendingKey, url, true); |
| 195 } else { |
| 196 breakpad_helper::AddReportParameter(breakpadKey, url, true); |
| 197 breakpad_helper::RemoveReportParameter(pendingKey); |
| 198 } |
| 199 } |
| 200 |
| 201 - (void)tabModel:(TabModel*)model |
| 202 didRemoveTab:(Tab*)tab |
| 203 atIndex:(NSUInteger)index { |
| 204 [self removeTabId:tab.tabId]; |
| 205 } |
| 206 |
| 207 - (void)tabModel:(TabModel*)model |
| 208 didReplaceTab:(Tab*)oldTab |
| 209 withTab:(Tab*)newTab |
| 210 atIndex:(NSUInteger)index { |
| 211 [self removeTabId:oldTab.tabId]; |
| 212 } |
| 213 |
| 214 - (void)tabModel:(TabModel*)model |
| 215 didChangeActiveTab:(Tab*)newTab |
| 216 previousTab:(Tab*)previousTab |
| 217 atIndex:(NSUInteger)modelIndex { |
| 218 [self recordURL:base::SysUTF8ToNSString(newTab.url.spec()) |
| 219 forTabId:newTab.tabId |
| 220 pending:NO]; |
| 221 } |
| 222 |
| 223 // Empty method left in place in case jailbreakers are swizzling this. |
| 224 - (void)detectJailbrokenDevice { |
| 225 // This method has been intentionally left blank. |
| 226 } |
| 227 |
| 228 @end |
| 229 |
| 230 @implementation CrashReporterTabStateObserver |
| 231 |
| 232 + (CrashReporterTabStateObserver*)uniqueInstance { |
| 233 static CrashReporterTabStateObserver* instance = |
| 234 [[CrashReporterTabStateObserver alloc] init]; |
| 235 return instance; |
| 236 } |
| 237 |
| 238 - (id)init { |
| 239 if ((self = [super init])) { |
| 240 tabCurrentStateByTabId_.reset([[NSMutableDictionary alloc] init]); |
| 241 // Register for url changed notifications. |
| 242 [[NSNotificationCenter defaultCenter] |
| 243 addObserver:self |
| 244 selector:@selector(closingDocument:) |
| 245 name:kTabClosingCurrentDocumentNotificationForCrashReporting |
| 246 object:nil]; |
| 247 [[NSNotificationCenter defaultCenter] |
| 248 addObserver:self |
| 249 selector:@selector(showingExportableDocument:) |
| 250 name:kTabIsShowingExportableNotificationForCrashReporting |
| 251 object:nil]; |
| 252 } |
| 253 return self; |
| 254 } |
| 255 |
| 256 - (void)closingDocument:(NSNotification*)notification { |
| 257 Tab* tab = notification.object; |
| 258 [self closingDocumentInTab:[tab tabId]]; |
| 259 } |
| 260 |
| 261 - (void)closingDocumentInTab:(NSString*)tabId { |
| 262 NSString* mime = (NSString*)[self getTabInfo:@"mime" forTab:tabId]; |
| 263 if ([mime isEqualToString:@"application/pdf"]) |
| 264 breakpad_helper::SetCurrentTabIsPDF(false); |
| 265 [self removeTabInfo:@"mime" forTab:tabId]; |
| 266 } |
| 267 |
| 268 - (void)setTabInfo:(NSString*)key |
| 269 withValue:(NSString*)value |
| 270 forTab:(NSString*)tabId { |
| 271 NSMutableDictionary* tabCurrentState = |
| 272 [tabCurrentStateByTabId_ objectForKey:tabId]; |
| 273 if (tabCurrentState == nil) { |
| 274 base::scoped_nsobject<NSMutableDictionary> currentStateOfNewTab( |
| 275 [[NSMutableDictionary alloc] init]); |
| 276 [tabCurrentStateByTabId_ setObject:currentStateOfNewTab.get() forKey:tabId]; |
| 277 tabCurrentState = [tabCurrentStateByTabId_ objectForKey:tabId]; |
| 278 } |
| 279 [tabCurrentState setObject:value forKey:key]; |
| 280 } |
| 281 |
| 282 - (id)getTabInfo:(NSString*)key forTab:(NSString*)tabId { |
| 283 NSMutableDictionary* tabValues = [tabCurrentStateByTabId_ objectForKey:tabId]; |
| 284 return [tabValues objectForKey:key]; |
| 285 } |
| 286 |
| 287 - (void)removeTabInfo:(NSString*)key forTab:(NSString*)tabId { |
| 288 [[tabCurrentStateByTabId_ objectForKey:tabId] removeObjectForKey:key]; |
| 289 } |
| 290 |
| 291 - (void)showingExportableDocument:(NSNotification*)notification { |
| 292 Tab* tab = notification.object; |
| 293 NSString* oldMime = (NSString*)[self getTabInfo:@"mime" forTab:[tab tabId]]; |
| 294 if ([oldMime isEqualToString:@"application/pdf"]) |
| 295 return; |
| 296 |
| 297 std::string mime = [tab webState]->GetContentsMimeType(); |
| 298 NSString* nsMime = base::SysUTF8ToNSString(mime); |
| 299 [self setTabInfo:@"mime" withValue:nsMime forTab:[tab tabId]]; |
| 300 breakpad_helper::SetCurrentTabIsPDF(true); |
| 301 } |
| 302 |
| 303 - (void)removeTabId:(NSString*)tabId { |
| 304 [self closingDocumentInTab:tabId]; |
| 305 [tabCurrentStateByTabId_ removeObjectForKey:tabId]; |
| 306 } |
| 307 |
| 308 - (void)tabModel:(TabModel*)model |
| 309 didRemoveTab:(Tab*)tab |
| 310 atIndex:(NSUInteger)index { |
| 311 [self removeTabId:tab.tabId]; |
| 312 } |
| 313 |
| 314 - (void)tabModel:(TabModel*)model |
| 315 didReplaceTab:(Tab*)oldTab |
| 316 withTab:(Tab*)newTab |
| 317 atIndex:(NSUInteger)index { |
| 318 [self removeTabId:oldTab.tabId]; |
| 319 } |
| 320 |
| 321 @end |
| 322 |
| 323 namespace ios_internal { |
| 324 namespace breakpad { |
| 325 |
| 326 void MonitorURLsForTabModel(TabModel* tab_model) { |
| 327 DCHECK(!tab_model.isOffTheRecord); |
| 328 [tab_model addObserver:[CrashReporterURLObserver uniqueInstance]]; |
| 329 } |
| 330 |
| 331 void StopMonitoringURLsForTabModel(TabModel* tab_model) { |
| 332 [tab_model removeObserver:[CrashReporterURLObserver uniqueInstance]]; |
| 333 } |
| 334 |
| 335 void MonitorTabStateForTabModel(TabModel* tab_model) { |
| 336 [tab_model addObserver:[CrashReporterTabStateObserver uniqueInstance]]; |
| 337 } |
| 338 |
| 339 void StopMonitoringTabStateForTabModel(TabModel* tab_model) { |
| 340 [tab_model removeObserver:[CrashReporterTabStateObserver uniqueInstance]]; |
| 341 } |
| 342 |
| 343 void ClearStateForTabModel(TabModel* tab_model) { |
| 344 CrashReporterURLObserver* observer = |
| 345 [CrashReporterURLObserver uniqueInstance]; |
| 346 for (Tab* tab in tab_model) { |
| 347 [observer removeTabId:tab.tabId]; |
| 348 } |
| 349 } |
| 350 |
| 351 } // namespace breakpad |
| 352 } // namespace ios_internal |
OLD | NEW |