1 // Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include <algorithm>
6 #include <sstream>
7
8 #include "include/base/cef_callback.h"
9 #include "include/base/cef_callback_helpers.h"
10 #include "include/cef_callback.h"
11 #include "include/cef_devtools_message_observer.h"
12 #include "include/cef_parser.h"
13 #include "include/wrapper/cef_closure_task.h"
14
15 #include "tests/ceftests/test_handler.h"
16 #include "tests/gtest/include/gtest/gtest.h"
17
18 namespace {
19
20 const char kTestUrl1[] = "http://tests/DevToolsMessage1";
21 const char kTestUrl2[] = "http://tests/DevToolsMessage2";
22
23 class DevToolsMessageTestHandler : public TestHandler {
24 public:
DevToolsMessageTestHandler()25 DevToolsMessageTestHandler() {}
26
RunTest()27 void RunTest() override {
28 // Add HTML resources.
29 AddResource(kTestUrl1, "<html><body>Test1</body></html>", "text/html");
30 AddResource(kTestUrl2, "<html><body>Test2</body></html>", "text/html");
31
32 // Create the browser.
33 CreateBrowser(kTestUrl1);
34
35 // Time out the test after a reasonable period of time.
36 SetTestTimeout();
37 }
38
OnAfterCreated(CefRefPtr<CefBrowser> browser)39 void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
40 TestHandler::OnAfterCreated(browser);
41
42 // STEP 1: Add the DevTools observer. Wait for the 1st load.
43 registration_ = browser->GetHost()->AddDevToolsMessageObserver(
44 new TestMessageObserver(this));
45 EXPECT_TRUE(registration_);
46 }
47
OnLoadingStateChange(CefRefPtr<CefBrowser> browser,bool isLoading,bool canGoBack,bool canGoForward)48 void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
49 bool isLoading,
50 bool canGoBack,
51 bool canGoForward) override {
52 if (!isLoading) {
53 load_ct_++;
54 if (load_ct_ == 1) {
55 // STEP 2: 1st load has completed. Now enable page domain notifications
56 // and wait for the method result.
57 ExecuteMethod(
58 "Page.enable", "",
59 base::BindOnce(&DevToolsMessageTestHandler::Navigate, this));
60 } else if (load_ct_ == 2) {
61 MaybeDestroyTest();
62 }
63 }
64 }
65
DestroyTest()66 void DestroyTest() override {
67 // STEP 7: Remove the DevTools observer. This should result in the observer
68 // object being destroyed.
69 EXPECT_TRUE(registration_);
70 registration_ = nullptr;
71 EXPECT_TRUE(observer_destroyed_);
72
73 // Each send message variant should be called at least a single time.
74 EXPECT_GE(method_send_ct_, 1);
75 EXPECT_GE(method_execute_ct_, 1);
76
77 // All sent messages should receive a result callback.
78 EXPECT_EQ(expected_method_ct_, method_send_ct_ + method_execute_ct_);
79 EXPECT_EQ(expected_method_ct_, result_ct_);
80 EXPECT_EQ(expected_method_ct_, last_result_id_);
81
82 // Every received message should parse successfully to a result or event
83 // callback.
84 EXPECT_EQ(message_ct_, result_ct_ + event_ct_);
85
86 // Should receive 1 or more events (probably just 1, but who knows?).
87 EXPECT_GE(event_ct_, 1);
88
89 // OnLoadingStateChange(isLoading=false) should be called twice.
90 EXPECT_EQ(expected_load_ct_, load_ct_);
91
92 // Should get callbacks for agent attached but not detached.
93 EXPECT_EQ(1, attached_ct_);
94 EXPECT_EQ(0, detached_ct_);
95
96 TestHandler::DestroyTest();
97 }
98
99 private:
100 struct MethodResult {
101 int message_id;
102 bool success;
103 std::string result;
104 };
105
106 struct Event {
107 std::string method;
108 std::string params;
109 };
110
111 class TestMessageObserver : public CefDevToolsMessageObserver {
112 public:
TestMessageObserver(DevToolsMessageTestHandler * handler)113 explicit TestMessageObserver(DevToolsMessageTestHandler* handler)
114 : handler_(handler) {}
115
~TestMessageObserver()116 virtual ~TestMessageObserver() { handler_->observer_destroyed_.yes(); }
117
OnDevToolsMessage(CefRefPtr<CefBrowser> browser,const void * message,size_t message_size)118 bool OnDevToolsMessage(CefRefPtr<CefBrowser> browser,
119 const void* message,
120 size_t message_size) override {
121 EXPECT_TRUE(browser.get());
122 EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier());
123 handler_->message_ct_++;
124 return false;
125 }
126
OnDevToolsMethodResult(CefRefPtr<CefBrowser> browser,int message_id,bool success,const void * result,size_t result_size)127 void OnDevToolsMethodResult(CefRefPtr<CefBrowser> browser,
128 int message_id,
129 bool success,
130 const void* result,
131 size_t result_size) override {
132 EXPECT_TRUE(browser.get());
133 EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier());
134 handler_->result_ct_++;
135
136 MethodResult mr;
137 mr.message_id = message_id;
138 mr.success = success;
139 if (result) {
140 // Intentionally truncating at small size.
141 mr.result = std::string(static_cast<const char*>(result),
142 std::min(static_cast<int>(result_size), 80));
143 }
144 handler_->OnMethodResult(mr);
145 }
146
OnDevToolsEvent(CefRefPtr<CefBrowser> browser,const CefString & method,const void * params,size_t params_size)147 void OnDevToolsEvent(CefRefPtr<CefBrowser> browser,
148 const CefString& method,
149 const void* params,
150 size_t params_size) override {
151 EXPECT_TRUE(browser.get());
152 EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier());
153 handler_->event_ct_++;
154
155 Event ev;
156 ev.method = method;
157 if (params) {
158 // Intentionally truncating at small size.
159 ev.params = std::string(static_cast<const char*>(params),
160 std::min(static_cast<int>(params_size), 80));
161 }
162 handler_->OnEvent(ev);
163 }
164
OnDevToolsAgentAttached(CefRefPtr<CefBrowser> browser)165 void OnDevToolsAgentAttached(CefRefPtr<CefBrowser> browser) override {
166 EXPECT_TRUE(browser.get());
167 EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier());
168 handler_->attached_ct_++;
169 }
170
OnDevToolsAgentDetached(CefRefPtr<CefBrowser> browser)171 void OnDevToolsAgentDetached(CefRefPtr<CefBrowser> browser) override {
172 EXPECT_TRUE(browser.get());
173 EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier());
174 handler_->detached_ct_++;
175 }
176
177 private:
178 DevToolsMessageTestHandler* handler_;
179
180 IMPLEMENT_REFCOUNTING(TestMessageObserver);
181 DISALLOW_COPY_AND_ASSIGN(TestMessageObserver);
182 };
183
184 // Execute a DevTools method. Expected results will be verified in
185 // OnMethodResult, and |next_step| will then be executed.
186 // |expected_result| can be a fragment that the result should start with.
ExecuteMethod(const std::string & method,const std::string & params,base::OnceClosure next_step,const std::string & expected_result="{}",bool expected_success=true)187 void ExecuteMethod(const std::string& method,
188 const std::string& params,
189 base::OnceClosure next_step,
190 const std::string& expected_result = "{}",
191 bool expected_success = true) {
192 CHECK(!method.empty());
193 CHECK(!next_step.is_null());
194
195 int message_id = next_message_id_++;
196
197 std::stringstream message;
198 message << "{\"id\":" << message_id << ",\"method\":\"" << method << "\"";
199 if (!params.empty()) {
200 message << ",\"params\":" << params;
201 }
202 message << "}";
203
204 // Set expected result state.
205 pending_message_ = message.str();
206 pending_result_next_ = std::move(next_step);
207 pending_result_ = {message_id, expected_success, expected_result};
208
209 if (message_id % 2 == 0) {
210 // Use the less structured method.
211 method_send_ct_++;
212 GetBrowser()->GetHost()->SendDevToolsMessage(pending_message_.data(),
213 pending_message_.size());
214 } else {
215 // Use the more structured method.
216 method_execute_ct_++;
217 CefRefPtr<CefDictionaryValue> dict;
218 if (!params.empty()) {
219 CefRefPtr<CefValue> value =
220 CefParseJSON(params.data(), params.size(), JSON_PARSER_RFC);
221 EXPECT_TRUE(value && value->GetType() == VTYPE_DICTIONARY)
222 << "failed to parse: " << params;
223 if (value && value->GetType() == VTYPE_DICTIONARY) {
224 dict = value->GetDictionary();
225 }
226 }
227 GetBrowser()->GetHost()->ExecuteDevToolsMethod(message_id, method, dict);
228 }
229 }
230
231 // Every call to ExecuteMethod should result in a single call to this method
232 // with the same message_id.
OnMethodResult(const MethodResult & result)233 void OnMethodResult(const MethodResult& result) {
234 EXPECT_EQ(pending_result_.message_id, result.message_id)
235 << "with message=" << pending_message_;
236 if (result.message_id != pending_result_.message_id)
237 return;
238
239 EXPECT_EQ(pending_result_.success, result.success)
240 << "with message=" << pending_message_;
241
242 EXPECT_TRUE(result.result.find(pending_result_.result) == 0)
243 << "with message=" << pending_message_
244 << "\nand actual result=" << result.result
245 << "\nand expected result=" << pending_result_.result;
246
247 last_result_id_ = result.message_id;
248
249 // Continue asynchronously to allow the callstack to unwind.
250 CefPostTask(TID_UI, std::move(pending_result_next_));
251
252 // Clear expected result state.
253 pending_message_.clear();
254 pending_result_ = {};
255 }
256
OnEvent(const Event & event)257 void OnEvent(const Event& event) {
258 if (event.method != pending_event_.method)
259 return;
260
261 EXPECT_TRUE(event.params.find(pending_event_.params) == 0)
262 << "with method=" << event.method
263 << "\nand actual params=" << event.params
264 << "\nand expected params=" << pending_event_.params;
265
266 // Continue asynchronously to allow the callstack to unwind.
267 CefPostTask(TID_UI, std::move(pending_event_next_));
268
269 // Clear expected result state.
270 pending_event_ = {};
271 }
272
Navigate()273 void Navigate() {
274 pending_event_ = {"Page.frameNavigated", "{\"frame\":"};
275 pending_event_next_ =
276 base::BindOnce(&DevToolsMessageTestHandler::AfterNavigate, this);
277
278 std::stringstream params;
279 params << "{\"url\":\"" << kTestUrl2 << "\"}";
280
281 // STEP 3: Page domain notifications are enabled. Now start a new
282 // navigation (but do nothing on method result) and wait for the
283 // "Page.frameNavigated" event.
284 ExecuteMethod("Page.navigate", params.str(), base::DoNothing(),
285 /*expected_result=*/"{\"frameId\":");
286 }
287
AfterNavigate()288 void AfterNavigate() {
289 // STEP 4: Got the "Page.frameNavigated" event. Now disable page domain
290 // notifications.
291 ExecuteMethod(
292 "Page.disable", "",
293 base::BindOnce(&DevToolsMessageTestHandler::AfterPageDisabled, this));
294 }
295
AfterPageDisabled()296 void AfterPageDisabled() {
297 // STEP 5: Got the the "Page.disable" method result. Now call a non-existant
298 // method to verify an error result, and then destroy the test when done.
299 ExecuteMethod(
300 "Foo.doesNotExist", "",
301 base::BindOnce(&DevToolsMessageTestHandler::MaybeDestroyTest, this),
302 /*expected_result=*/
303 "{\"code\":-32601,\"message\":\"'Foo.doesNotExist' wasn't found\"}",
304 /*expected_success=*/false);
305 }
306
MaybeDestroyTest()307 void MaybeDestroyTest() {
308 if (result_ct_ == expected_method_ct_ && load_ct_ == expected_load_ct_) {
309 // STEP 6: Got confirmation of all expected method results and load
310 // events. Now destroy the test.
311 DestroyTest();
312 }
313 }
314
315 // Total # of times we're planning to call ExecuteMethod.
316 const int expected_method_ct_ = 4;
317
318 // Total # of times we're expecting OnLoadingStateChange(isLoading=false) to
319 // be called.
320 const int expected_load_ct_ = 2;
321
322 // In ExecuteMethod:
323 // Next message ID to use.
324 int next_message_id_ = 1;
325 // Last message that was sent (used for debug messages only).
326 std::string pending_message_;
327 // SendDevToolsMessage call count.
328 int method_send_ct_ = 0;
329 // ExecuteDevToolsMethod call count.
330 int method_execute_ct_ = 0;
331
332 // Expect |pending_result_.message_id| in OnMethodResult.
333 // The result should start with the |pending_result_.result| fragment.
334 MethodResult pending_result_;
335 // Tracks the last message ID received.
336 int last_result_id_ = -1;
337 // When received, execute this callback.
338 base::OnceClosure pending_result_next_;
339
340 // Wait for |pending_event_.method| in OnEvent.
341 // The params should start with the |pending_event_.params| fragment.
342 Event pending_event_;
343 // When received, execute this callback.
344 base::OnceClosure pending_event_next_;
345
346 CefRefPtr<CefRegistration> registration_;
347
348 // Observer callback count.
349 int message_ct_ = 0;
350 int result_ct_ = 0;
351 int event_ct_ = 0;
352 int attached_ct_ = 0;
353 int detached_ct_ = 0;
354
355 // OnLoadingStateChange(isLoading=false) count.
356 int load_ct_ = 0;
357
358 TrackCallback observer_destroyed_;
359
360 IMPLEMENT_REFCOUNTING(DevToolsMessageTestHandler);
361 DISALLOW_COPY_AND_ASSIGN(DevToolsMessageTestHandler);
362 };
363
364 } // namespace
365
366 // Test everything related to DevTools messages:
367 // - CefDevToolsMessageObserver registration and life span.
368 // - SendDevToolsMessage/ExecuteDevToolsMethod calls.
369 // - CefDevToolsMessageObserver callbacks for method results and events.
TEST(DevToolsMessageTest,Messages)370 TEST(DevToolsMessageTest, Messages) {
371 CefRefPtr<DevToolsMessageTestHandler> handler =
372 new DevToolsMessageTestHandler();
373 handler->ExecuteTest();
374 ReleaseAndWaitForDestructor(handler);
375 }
376