aa9b8240daddf54935fa17d8f4d35a498e26f73a
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome / navigation_tracker.cc
1 // Copyright (c) 2013 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 "chrome/test/chromedriver/chrome/navigation_tracker.h"
6
7 #include "base/strings/stringprintf.h"
8 #include "base/values.h"
9 #include "chrome/test/chromedriver/chrome/browser_info.h"
10 #include "chrome/test/chromedriver/chrome/devtools_client.h"
11 #include "chrome/test/chromedriver/chrome/status.h"
12
13 NavigationTracker::NavigationTracker(DevToolsClient* client,
14                                      const BrowserInfo* browser_info)
15     : client_(client),
16       loading_state_(kUnknown),
17       browser_info_(browser_info) {
18   client_->AddListener(this);
19 }
20
21 NavigationTracker::NavigationTracker(DevToolsClient* client,
22                                      LoadingState known_state,
23                                      const BrowserInfo* browser_info)
24     : client_(client),
25       loading_state_(known_state),
26       browser_info_(browser_info) {
27   client_->AddListener(this);
28 }
29
30 NavigationTracker::~NavigationTracker() {}
31
32 Status NavigationTracker::IsPendingNavigation(const std::string& frame_id,
33                                               bool* is_pending) {
34   if (loading_state_ == kUnknown) {
35     // If the loading state is unknown (which happens after first connecting),
36     // force loading to start and set the state to loading. This will
37     // cause a frame start event to be received, and the frame stop event
38     // will not be received until all frames are loaded.
39     // Loading is forced to start by attaching a temporary iframe.
40     // Forcing loading to start is not necessary if the main frame is not yet
41     // loaded.
42     const char kStartLoadingIfMainFrameNotLoading[] =
43        "var isLoaded = document.readyState == 'complete' ||"
44        "    document.readyState == 'interactive';"
45        "if (isLoaded) {"
46        "  var frame = document.createElement('iframe');"
47        "  frame.src = 'about:blank';"
48        "  document.body.appendChild(frame);"
49        "  window.setTimeout(function() {"
50        "    document.body.removeChild(frame);"
51        "  }, 0);"
52        "}";
53     base::DictionaryValue params;
54     params.SetString("expression", kStartLoadingIfMainFrameNotLoading);
55     scoped_ptr<base::DictionaryValue> result;
56     Status status = client_->SendCommandAndGetResult(
57         "Runtime.evaluate", params, &result);
58     if (status.IsError())
59       return Status(kUnknownError, "cannot determine loading status", status);
60
61     // Between the time the JavaScript is evaluated and SendCommandAndGetResult
62     // returns, OnEvent may have received info about the loading state.
63     // This is only possible during a nested command. Only set the loading state
64     // if the loading state is still unknown.
65     if (loading_state_ == kUnknown)
66       loading_state_ = kLoading;
67   }
68   *is_pending = loading_state_ == kLoading;
69   if (frame_id.empty())
70     *is_pending |= scheduled_frame_set_.size() > 0;
71   else
72     *is_pending |= scheduled_frame_set_.count(frame_id) > 0;
73   return Status(kOk);
74 }
75
76 Status NavigationTracker::OnConnected(DevToolsClient* client) {
77   ResetLoadingState(kUnknown);
78
79   // Enable page domain notifications to allow tracking navigation state.
80   base::DictionaryValue empty_params;
81   return client_->SendCommand("Page.enable", empty_params);
82 }
83
84 Status NavigationTracker::OnEvent(DevToolsClient* client,
85                                   const std::string& method,
86                                   const base::DictionaryValue& params) {
87   if (method == "Page.frameStartedLoading") {
88     std::string frame_id;
89     if (!params.GetString("frameId", &frame_id))
90       return Status(kUnknownError, "missing or invalid 'frameId'");
91     pending_frame_set_.insert(frame_id);
92     loading_state_ = kLoading;
93   } else if (method == "Page.frameStoppedLoading") {
94     // Versions of Blink before revision 170248 sent a single
95     // Page.frameStoppedLoading event per page, but 170248 and newer revisions
96     // only send one event for each frame on the page.
97     //
98     // This change was rolled into the Chromium tree in revision 260203.
99     // Versions of Chrome with build number 1916 and earlier do not contain this
100     // change.
101     bool expecting_single_stop_event = false;
102
103     if (browser_info_->browser_name == "chrome") {
104       // If we're talking to a version of Chrome with an old build number, we
105       // are using a branched version of Blink which does not contain 170248
106       // (even if blink_revision > 170248).
107       expecting_single_stop_event = browser_info_->build_no <= 1916;
108     } else {
109       // If we're talking to a non-Chrome embedder (e.g. Content Shell, Android
110       // WebView), assume that the browser does not use a branched version of
111       // Blink.
112       expecting_single_stop_event = browser_info_->blink_revision < 170248;
113     }
114
115     std::string frame_id;
116     if (!params.GetString("frameId", &frame_id))
117       return Status(kUnknownError, "missing or invalid 'frameId'");
118
119     pending_frame_set_.erase(frame_id);
120
121     if (pending_frame_set_.empty() || expecting_single_stop_event) {
122       pending_frame_set_.clear();
123       loading_state_ = kNotLoading;
124     }
125   } else if (method == "Page.frameScheduledNavigation") {
126     double delay;
127     if (!params.GetDouble("delay", &delay))
128       return Status(kUnknownError, "missing or invalid 'delay'");
129
130     std::string frame_id;
131     if (!params.GetString("frameId", &frame_id))
132       return Status(kUnknownError, "missing or invalid 'frameId'");
133
134     // WebDriver spec says to ignore redirects over 1s.
135     if (delay > 1)
136       return Status(kOk);
137     scheduled_frame_set_.insert(frame_id);
138   } else if (method == "Page.frameClearedScheduledNavigation") {
139     std::string frame_id;
140     if (!params.GetString("frameId", &frame_id))
141       return Status(kUnknownError, "missing or invalid 'frameId'");
142
143     scheduled_frame_set_.erase(frame_id);
144   } else if (method == "Page.frameNavigated") {
145     // Note: in some cases Page.frameNavigated may be received for subframes
146     // without a frameStoppedLoading (for example cnn.com).
147
148     // If the main frame just navigated, discard any pending scheduled
149     // navigations. For some reasons at times the cleared event is not
150     // received when navigating.
151     // See crbug.com/180742.
152     const base::Value* unused_value;
153     if (!params.Get("frame.parentId", &unused_value)) {
154       pending_frame_set_.clear();
155       scheduled_frame_set_.clear();
156     }
157   } else if (method == "Inspector.targetCrashed") {
158     ResetLoadingState(kNotLoading);
159   }
160   return Status(kOk);
161 }
162
163 Status NavigationTracker::OnCommandSuccess(DevToolsClient* client,
164                                            const std::string& method) {
165   if (method == "Page.navigate" && loading_state_ != kLoading) {
166     // At this point the browser has initiated the navigation, but besides that,
167     // it is unknown what will happen.
168     //
169     // There are a few cases (perhaps more):
170     // 1 The RenderFrameHost has already queued FrameMsg_Navigate and loading
171     //   will start shortly.
172     // 2 The RenderFrameHost has already queued FrameMsg_Navigate and loading
173     //   will never start because it is just an in-page fragment navigation.
174     // 3 The RenderFrameHost is suspended and hasn't queued FrameMsg_Navigate
175     //   yet. This happens for cross-site navigations. The RenderFrameHost
176     //   will not queue FrameMsg_Navigate until it is ready to unload the
177     //   previous page (after running unload handlers and such).
178     // TODO(nasko): Revisit case 3, since now unload handlers are run in the
179     // background. http://crbug.com/323528.
180     //
181     // To determine whether a load is expected, do a round trip to the
182     // renderer to ask what the URL is.
183     // If case #1, by the time the command returns, the frame started to load
184     // event will also have been received, since the DevTools command will
185     // be queued behind FrameMsg_Navigate.
186     // If case #2, by the time the command returns, the navigation will
187     // have already happened, although no frame start/stop events will have
188     // been received.
189     // If case #3, the URL will be blank if the navigation hasn't been started
190     // yet. In that case, expect a load to happen in the future.
191     loading_state_ = kUnknown;
192     base::DictionaryValue params;
193     params.SetString("expression", "document.URL");
194     scoped_ptr<base::DictionaryValue> result;
195     Status status = client_->SendCommandAndGetResult(
196         "Runtime.evaluate", params, &result);
197     std::string url;
198     if (status.IsError() || !result->GetString("result.value", &url))
199       return Status(kUnknownError, "cannot determine loading status", status);
200     if (loading_state_ == kUnknown && url.empty())
201       loading_state_ = kLoading;
202   }
203   return Status(kOk);
204 }
205
206 void NavigationTracker::ResetLoadingState(LoadingState loading_state) {
207   loading_state_ = loading_state;
208   pending_frame_set_.clear();
209   scheduled_frame_set_.clear();
210 }