• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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