1 // Copyright 2018 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 "fuchsia/base/test_navigation_listener.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "base/auto_reset.h"
11 #include "base/bind.h"
12 #include "base/fuchsia/fuchsia_logging.h"
13 #include "base/run_loop.h"
14 #include "base/strings/stringprintf.h"
15 #include "fuchsia/base/mem_buffer_util.h"
16 
17 namespace cr_fuchsia {
18 namespace {
19 
QuitRunLoopAndRunCallback(base::OnceClosure quit_run_loop_closure,TestNavigationListener::BeforeAckCallback before_ack_callback,const fuchsia::web::NavigationState & change,fuchsia::web::NavigationEventListener::OnNavigationStateChangedCallback ack_callback)20 void QuitRunLoopAndRunCallback(
21     base::OnceClosure quit_run_loop_closure,
22     TestNavigationListener::BeforeAckCallback before_ack_callback,
23     const fuchsia::web::NavigationState& change,
24     fuchsia::web::NavigationEventListener::OnNavigationStateChangedCallback
25         ack_callback) {
26   std::move(quit_run_loop_closure).Run();
27   before_ack_callback.Run(change, std::move(ack_callback));
28 }
29 
30 }  // namespace
31 
TestNavigationListener()32 TestNavigationListener::TestNavigationListener() {
33   // Set up the default acknowledgement handling behavior.
34   SetBeforeAckHook({});
35 }
36 
37 TestNavigationListener::~TestNavigationListener() = default;
38 
RunUntilNavigationStateMatches(const fuchsia::web::NavigationState & expected_state)39 void TestNavigationListener::RunUntilNavigationStateMatches(
40     const fuchsia::web::NavigationState& expected_state) {
41   DCHECK(before_ack_);
42 
43   // Spin the runloop until the expected conditions are met.
44   while (!AllFieldsMatch(expected_state)) {
45     base::RunLoop run_loop;
46     base::AutoReset<BeforeAckCallback> callback_setter(
47         &before_ack_, base::BindRepeating(&QuitRunLoopAndRunCallback,
48                                           run_loop.QuitClosure(), before_ack_));
49     run_loop.Run();
50   }
51 }
52 
RunUntilUrlEquals(const GURL & expected_url)53 void TestNavigationListener::RunUntilUrlEquals(const GURL& expected_url) {
54   fuchsia::web::NavigationState state;
55   state.set_url(expected_url.spec());
56   state.set_is_main_document_loaded(true);
57   RunUntilNavigationStateMatches(state);
58 }
59 
RunUntilTitleEquals(const base::StringPiece expected_title)60 void TestNavigationListener::RunUntilTitleEquals(
61     const base::StringPiece expected_title) {
62   fuchsia::web::NavigationState state;
63   state.set_title(expected_title.as_string());
64   RunUntilNavigationStateMatches(state);
65 }
66 
RunUntilUrlAndTitleEquals(const GURL & expected_url,const base::StringPiece expected_title)67 void TestNavigationListener::RunUntilUrlAndTitleEquals(
68     const GURL& expected_url,
69     const base::StringPiece expected_title) {
70   fuchsia::web::NavigationState state;
71   state.set_url(expected_url.spec());
72   state.set_title(expected_title.as_string());
73   RunUntilNavigationStateMatches(state);
74 }
75 
RunUntilUrlTitleBackForwardEquals(const GURL & expected_url,base::StringPiece expected_title,bool expected_can_go_back,bool expected_can_go_forward)76 void TestNavigationListener::RunUntilUrlTitleBackForwardEquals(
77     const GURL& expected_url,
78     base::StringPiece expected_title,
79     bool expected_can_go_back,
80     bool expected_can_go_forward) {
81   fuchsia::web::NavigationState state;
82   state.set_url(expected_url.spec());
83   state.set_title(expected_title.as_string());
84   state.set_can_go_back(expected_can_go_back);
85   state.set_can_go_forward(expected_can_go_forward);
86   RunUntilNavigationStateMatches(state);
87 }
88 
OnNavigationStateChanged(fuchsia::web::NavigationState change,OnNavigationStateChangedCallback callback)89 void TestNavigationListener::OnNavigationStateChanged(
90     fuchsia::web::NavigationState change,
91     OnNavigationStateChangedCallback callback) {
92   DCHECK(before_ack_);
93 
94   // Update our local cache of the Frame's current state.
95   if (change.has_url())
96     current_state_.set_url(change.url());
97   if (change.has_title())
98     current_state_.set_title(change.title());
99   if (change.has_can_go_back())
100     current_state_.set_can_go_back(change.can_go_back());
101   if (change.has_can_go_forward())
102     current_state_.set_can_go_forward(change.can_go_forward());
103   if (change.has_is_main_document_loaded())
104     current_state_.set_is_main_document_loaded(
105         change.is_main_document_loaded());
106 
107   if (VLOG_IS_ON(1)) {
108     std::string state_string;
109     state_string.reserve(100);
110 
111     if (current_state_.has_url())
112       state_string.append(
113           base::StringPrintf(" url=%s ", current_state_.url().c_str()));
114 
115     if (current_state_.has_title())
116       state_string.append(
117           base::StringPrintf(" title='%s' ", current_state_.title().c_str()));
118 
119     if (current_state_.has_can_go_back())
120       state_string.append(
121           base::StringPrintf(" can_go_back=%d ", current_state_.can_go_back()));
122 
123     if (current_state_.has_can_go_forward())
124       state_string.append(base::StringPrintf(" can_go_forward=%d ",
125                                              current_state_.can_go_forward()));
126 
127     if (current_state_.has_is_main_document_loaded())
128       state_string.append(
129           base::StringPrintf(" is_main_document_loaded=%d ",
130                              current_state_.is_main_document_loaded()));
131     VLOG(1) << "Navigation state changed: " << state_string;
132   }
133 
134   // Signal readiness for the next navigation event.
135   before_ack_.Run(change, std::move(callback));
136 }
137 
SetBeforeAckHook(BeforeAckCallback send_ack_cb)138 void TestNavigationListener::SetBeforeAckHook(BeforeAckCallback send_ack_cb) {
139   if (send_ack_cb) {
140     before_ack_ = send_ack_cb;
141   } else {
142     before_ack_ = base::BindRepeating(
143         [](const fuchsia::web::NavigationState&,
144            OnNavigationStateChangedCallback callback) { callback(); });
145   }
146 }
147 
AllFieldsMatch(const fuchsia::web::NavigationState & expected)148 bool TestNavigationListener::AllFieldsMatch(
149     const fuchsia::web::NavigationState& expected) {
150   if (expected.has_url() &&
151       (!current_state_.has_url() || expected.url() != current_state_.url())) {
152     return false;
153   }
154 
155   if (expected.has_title() && (!current_state_.has_title() ||
156                                expected.title() != current_state_.title())) {
157     return false;
158   }
159 
160   if (expected.has_can_go_forward() &&
161       (!current_state_.has_can_go_forward() ||
162        expected.can_go_forward() != current_state_.can_go_forward())) {
163     return false;
164   }
165 
166   if (expected.has_can_go_back() &&
167       (!current_state_.has_can_go_back() ||
168        expected.can_go_back() != current_state_.can_go_back())) {
169     return false;
170   }
171 
172   if (expected.has_is_main_document_loaded() &&
173       (!current_state_.has_is_main_document_loaded() ||
174        expected.is_main_document_loaded() !=
175            current_state_.is_main_document_loaded())) {
176     return false;
177   }
178 
179   return true;
180 }
181 
182 }  // namespace cr_fuchsia
183