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#import <Foundation/Foundation.h>
6
7#include "ios/chrome/browser/crash_report/crash_reporter_url_observer.h"
8
9#include "base/strings/sys_string_conversions.h"
10#include "base/test/task_environment.h"
11#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
12#import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
13#import "ios/chrome/browser/web_state_list/web_state_list.h"
14#import "ios/chrome/browser/web_state_list/web_state_opener.h"
15#import "ios/web/public/navigation/navigation_item.h"
16#import "ios/web/public/test/fakes/fake_navigation_context.h"
17#import "ios/web/public/test/fakes/test_navigation_manager.h"
18#import "ios/web/public/test/fakes/test_web_state.h"
19#include "testing/gtest/include/gtest/gtest.h"
20#include "testing/gtest_mac.h"
21#include "testing/platform_test.h"
22
23#if !defined(__has_feature) || !__has_feature(objc_arc)
24#error "This file requires ARC support."
25#endif
26
27namespace {
28
29class TestWebState : public web::TestWebState {
30 public:
31  void LoadURL(const GURL& url) {
32    SetCurrentURL(url);
33    web::FakeNavigationContext context;
34    context.SetUrl(url);
35    web::TestNavigationManager* navigation_manager =
36        static_cast<web::TestNavigationManager*>(GetNavigationManager());
37    navigation_manager->SetPendingItem(nullptr);
38    pending_item_.reset();
39    OnNavigationFinished(&context);
40  }
41
42  void LoadPendingURL(const GURL& url) {
43    SetCurrentURL(url);
44    web::FakeNavigationContext context;
45    context.SetUrl(url);
46    web::TestNavigationManager* navigation_manager =
47        static_cast<web::TestNavigationManager*>(GetNavigationManager());
48    DCHECK(!pending_item_);
49    pending_item_ = web::NavigationItem::Create();
50    pending_item_->SetURL(url);
51    navigation_manager->SetPendingItem(pending_item_.get());
52    OnNavigationStarted(&context);
53  }
54
55 private:
56  std::unique_ptr<web::NavigationItem> pending_item_;
57};
58
59}  // namespace
60
61@interface DictionaryParameterSetter : NSObject <CrashReporterParameterSetter>
62@property(nonatomic) NSMutableDictionary* params;
63@end
64
65@implementation DictionaryParameterSetter
66
67- (instancetype)init {
68  self = [super init];
69  if (self) {
70    _params = [[NSMutableDictionary alloc] init];
71  }
72  return self;
73}
74
75- (void)removeReportParameter:(NSString*)key {
76  [_params removeObjectForKey:key];
77}
78
79- (void)setReportParameterURL:(const GURL&)URL forKey:(NSString*)key {
80  [_params setObject:base::SysUTF8ToNSString(URL.spec()) forKey:key];
81}
82
83@end
84
85class CrashReporterURLObserverTest : public PlatformTest {
86 public:
87  CrashReporterURLObserverTest() {
88    TestChromeBrowserState::Builder test_cbs_builder;
89    test_chrome_browser_state_ = test_cbs_builder.Build();
90    params_ = [[DictionaryParameterSetter alloc] init];
91    observer_ = std::make_unique<CrashReporterURLObserver>(params_);
92  }
93
94  TestWebState* CreateWebState(WebStateList* web_state_list) {
95    std::unique_ptr<TestWebState> test_web_state =
96        std::make_unique<TestWebState>();
97    test_web_state->SetBrowserState(test_chrome_browser_state_.get());
98    test_web_state->SetNavigationManager(
99        std::make_unique<web::TestNavigationManager>());
100    TestWebState* test_web_state_ptr = test_web_state.get();
101    web_state_list->InsertWebState(0, std::move(test_web_state),
102                                   WebStateList::INSERT_NO_FLAGS,
103                                   WebStateOpener());
104    return test_web_state_ptr;
105  }
106
107  std::unique_ptr<TestWebState> CreatePreloadWebState() {
108    std::unique_ptr<TestWebState> test_web_state =
109        std::make_unique<TestWebState>();
110    test_web_state->SetBrowserState(test_chrome_browser_state_.get());
111    test_web_state->SetNavigationManager(
112        std::make_unique<web::TestNavigationManager>());
113    observer_->ObservePreloadWebState(test_web_state.get());
114    return test_web_state;
115  }
116
117 protected:
118  base::test::TaskEnvironment task_environment_;
119  std::unique_ptr<ChromeBrowserState> test_chrome_browser_state_;
120  FakeWebStateListDelegate web_state_list_delegate_;
121  DictionaryParameterSetter* params_;
122  std::unique_ptr<CrashReporterURLObserver> observer_;
123};
124
125TEST_F(CrashReporterURLObserverTest, TestBasicBehaviors) {
126  EXPECT_NSEQ(@{}, params_.params);
127
128  // Create 5 WebStateLists to have 5 groups
129  WebStateList web_state_list_1(&web_state_list_delegate_);
130  observer_->ObserveWebStateList(&web_state_list_1);
131  WebStateList web_state_list_2(&web_state_list_delegate_);
132  observer_->ObserveWebStateList(&web_state_list_2);
133  WebStateList web_state_list_3(&web_state_list_delegate_);
134  observer_->ObserveWebStateList(&web_state_list_3);
135  WebStateList web_state_list_4(&web_state_list_delegate_);
136  observer_->ObserveWebStateList(&web_state_list_4);
137  WebStateList web_state_list_5(&web_state_list_delegate_);
138  observer_->ObserveWebStateList(&web_state_list_5);
139
140  TestWebState* web_state_11 = CreateWebState(&web_state_list_1);
141  TestWebState* web_state_12 = CreateWebState(&web_state_list_1);
142  TestWebState* web_state_21 = CreateWebState(&web_state_list_2);
143  TestWebState* web_state_31 = CreateWebState(&web_state_list_3);
144  TestWebState* web_state_41 = CreateWebState(&web_state_list_4);
145  TestWebState* web_state_51 = CreateWebState(&web_state_list_5);
146
147  // Load in every group in turn. The last 3 should be reported.
148  web_state_11->LoadURL(GURL("http://example11.test/"));
149  NSDictionary* expected = @{@"url0" : @"http://example11.test/"};
150  EXPECT_NSEQ(expected, params_.params);
151
152  web_state_21->LoadURL(GURL("http://example21.test/"));
153  expected = @{
154    @"url0" : @"http://example11.test/",
155    @"url1" : @"http://example21.test/"
156  };
157  EXPECT_NSEQ(expected, params_.params);
158
159  web_state_31->LoadURL(GURL("http://example31.test/"));
160  expected = @{
161    @"url0" : @"http://example11.test/",
162    @"url1" : @"http://example21.test/",
163    @"url2" : @"http://example31.test/"
164  };
165  EXPECT_NSEQ(expected, params_.params);
166
167  web_state_41->LoadURL(GURL("http://example41.test/"));
168  expected = @{
169    @"url0" : @"http://example41.test/",
170    @"url1" : @"http://example21.test/",
171    @"url2" : @"http://example31.test/"
172  };
173  EXPECT_NSEQ(expected, params_.params);
174
175  web_state_51->LoadURL(GURL("http://example51.test/"));
176  expected = @{
177    @"url0" : @"http://example41.test/",
178    @"url1" : @"http://example51.test/",
179    @"url2" : @"http://example31.test/"
180  };
181  EXPECT_NSEQ(expected, params_.params);
182
183  web_state_11->LoadURL(GURL("http://example12.test/"));
184  expected = @{
185    @"url0" : @"http://example41.test/",
186    @"url1" : @"http://example51.test/",
187    @"url2" : @"http://example12.test/"
188  };
189  EXPECT_NSEQ(expected, params_.params);
190
191  // Load again in group 4. URL 0 should be updated.
192  web_state_41->LoadURL(GURL("http://example42.test/"));
193  expected = @{
194    @"url0" : @"http://example42.test/",
195    @"url1" : @"http://example51.test/",
196    @"url2" : @"http://example12.test/"
197  };
198  EXPECT_NSEQ(expected, params_.params);
199
200  // Load again in group 2.
201  web_state_21->LoadURL(GURL("http://example22.test/"));
202  expected = @{
203    @"url0" : @"http://example42.test/",
204    @"url1" : @"http://example22.test/",
205    @"url2" : @"http://example12.test/"
206  };
207  EXPECT_NSEQ(expected, params_.params);
208
209  // Load again in group 1, on multiple WebState.
210  web_state_11->LoadURL(GURL("http://example13.test/"));
211  expected = @{
212    @"url0" : @"http://example42.test/",
213    @"url1" : @"http://example22.test/",
214    @"url2" : @"http://example13.test/"
215  };
216  EXPECT_NSEQ(expected, params_.params);
217
218  web_state_12->LoadURL(GURL("http://example14.test/"));
219  expected = @{
220    @"url0" : @"http://example42.test/",
221    @"url1" : @"http://example22.test/",
222    @"url2" : @"http://example14.test/"
223  };
224  EXPECT_NSEQ(expected, params_.params);
225
226  // Activate different WebState
227  web_state_list_1.ActivateWebStateAt(0);
228  expected = @{
229    @"url0" : @"http://example42.test/",
230    @"url1" : @"http://example22.test/",
231    @"url2" : @"http://example13.test/"
232  };
233  EXPECT_NSEQ(expected, params_.params);
234
235  web_state_list_1.ActivateWebStateAt(1);
236  expected = @{
237    @"url0" : @"http://example42.test/",
238    @"url1" : @"http://example22.test/",
239    @"url2" : @"http://example14.test/"
240  };
241  EXPECT_NSEQ(expected, params_.params);
242
243  // Load a pending URL in a group already reported, then load it.
244  web_state_41->LoadPendingURL(GURL("http://example43.test/"));
245  expected = @{
246    @"url0" : @"http://example42.test/",
247    @"url0-pending" : @"http://example43.test/",
248    @"url1" : @"http://example22.test/",
249    @"url2" : @"http://example14.test/"
250  };
251  EXPECT_NSEQ(expected, params_.params);
252
253  web_state_41->LoadURL(GURL("http://example43.test/"));
254  expected = @{
255    @"url0" : @"http://example43.test/",
256    @"url1" : @"http://example22.test/",
257    @"url2" : @"http://example14.test/"
258  };
259  EXPECT_NSEQ(expected, params_.params);
260
261  // Load a pending URL in a group not already reported, then load it.
262  web_state_51->LoadPendingURL(GURL("http://example53.test/"));
263  expected = @{
264    @"url0" : @"http://example43.test/",
265    @"url1-pending" : @"http://example53.test/",
266    @"url2" : @"http://example14.test/"
267  };
268  EXPECT_NSEQ(expected, params_.params);
269
270  web_state_51->LoadURL(GURL("http://example53.test/"));
271  expected = @{
272    @"url0" : @"http://example43.test/",
273    @"url1" : @"http://example53.test/",
274    @"url2" : @"http://example14.test/"
275  };
276  EXPECT_NSEQ(expected, params_.params);
277
278  // Remove a group and some reload URLs
279  observer_->RemoveWebStateList(&web_state_list_5);
280  expected = @{
281    @"url0" : @"http://example43.test/",
282    @"url2" : @"http://example14.test/"
283  };
284  EXPECT_NSEQ(expected, params_.params);
285
286  web_state_31->LoadURL(GURL("http://example33.test/"));
287  expected = @{
288    @"url0" : @"http://example43.test/",
289    @"url1" : @"http://example33.test/",
290    @"url2" : @"http://example14.test/"
291  };
292  EXPECT_NSEQ(expected, params_.params);
293
294  web_state_51->LoadURL(GURL("http://example54.test/"));
295  expected = @{
296    @"url0" : @"http://example43.test/",
297    @"url1" : @"http://example33.test/",
298    @"url2" : @"http://example54.test/"
299  };
300  EXPECT_NSEQ(expected, params_.params);
301
302  // Remove a WebState
303  web_state_12->LoadURL(GURL("http://example14.test/"));
304  expected = @{
305    @"url0" : @"http://example14.test/",
306    @"url1" : @"http://example33.test/",
307    @"url2" : @"http://example54.test/"
308  };
309  EXPECT_NSEQ(expected, params_.params);
310
311  // This should activate the other WebState.
312  std::unique_ptr<web::WebState> tmp_web_state =
313      web_state_list_1.DetachWebStateAt(1);
314  expected = @{
315    @"url0" : @"http://example13.test/",
316    @"url1" : @"http://example33.test/",
317    @"url2" : @"http://example54.test/"
318  };
319  EXPECT_NSEQ(expected, params_.params);
320
321  web_state_list_1.InsertWebState(0, std::move(tmp_web_state),
322                                  WebStateList::INSERT_ACTIVATE,
323                                  WebStateOpener());
324  expected = @{
325    @"url0" : @"http://example14.test/",
326    @"url1" : @"http://example33.test/",
327    @"url2" : @"http://example54.test/"
328  };
329  EXPECT_NSEQ(expected, params_.params);
330
331  std::unique_ptr<web::WebState> tmp_web_state2 =
332      web_state_list_3.DetachWebStateAt(0);
333  expected = @{
334    @"url0" : @"http://example14.test/",
335    @"url2" : @"http://example54.test/"
336  };
337  EXPECT_NSEQ(expected, params_.params);
338
339  web_state_list_3.InsertWebState(0, std::move(tmp_web_state2),
340                                  WebStateList::INSERT_ACTIVATE,
341                                  WebStateOpener());
342  expected = @{
343    @"url0" : @"http://example14.test/",
344    @"url1" : @"http://example33.test/",
345    @"url2" : @"http://example54.test/"
346  };
347  EXPECT_NSEQ(expected, params_.params);
348
349  std::unique_ptr<TestWebState> preload_web_state = CreatePreloadWebState();
350  TestWebState* preload_web_state_ptr = preload_web_state.get();
351  expected = @{
352    @"url0" : @"http://example14.test/",
353    @"url1" : @"http://example33.test/",
354    @"url2" : @"http://example54.test/"
355  };
356  EXPECT_NSEQ(expected, params_.params);
357
358  preload_web_state->LoadPendingURL(GURL("http://example-preload.test/"));
359  expected = @{
360    @"url0" : @"http://example14.test/",
361    @"url1" : @"http://example33.test/",
362    @"url2-pending" : @"http://example-preload.test/"
363  };
364  EXPECT_NSEQ(expected, params_.params);
365
366  observer_->StopObservingPreloadWebState(preload_web_state.get());
367  web_state_list_3.ReplaceWebStateAt(0, std::move(preload_web_state));
368  expected = @{
369    @"url0" : @"http://example14.test/",
370    @"url1" : @"http://example33.test/",
371    @"url1-pending" : @"http://example-preload.test/"
372  };
373  EXPECT_NSEQ(expected, params_.params);
374
375  preload_web_state_ptr->LoadURL(GURL("http://example-preload.test/"));
376  expected = @{
377    @"url0" : @"http://example14.test/",
378    @"url1" : @"http://example-preload.test/"
379  };
380  EXPECT_NSEQ(expected, params_.params);
381
382  observer_->StopObservingWebStateList(&web_state_list_1);
383  observer_->StopObservingWebStateList(&web_state_list_2);
384  observer_->StopObservingWebStateList(&web_state_list_3);
385  observer_->StopObservingWebStateList(&web_state_list_4);
386  observer_->StopObservingWebStateList(&web_state_list_5);
387}
388