1// Copyright (c) 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// Implements the Chrome Extensions WebNavigation API.
6
7#include "chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h"
8
9#include "base/json/json_writer.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/time/time.h"
12#include "base/values.h"
13#include "chrome/browser/extensions/api/web_navigation/web_navigation_api_constants.h"
14#include "chrome/browser/extensions/extension_tab_util.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/common/extensions/api/web_navigation.h"
17#include "content/public/browser/render_frame_host.h"
18#include "content/public/browser/render_process_host.h"
19#include "content/public/browser/render_view_host.h"
20#include "content/public/browser/web_contents.h"
21#include "extensions/browser/event_router.h"
22#include "extensions/common/event_filtering_info.h"
23#include "net/base/net_errors.h"
24#include "ui/base/page_transition_types.h"
25
26namespace extensions {
27
28namespace keys = web_navigation_api_constants;
29namespace web_navigation = api::web_navigation;
30
31namespace web_navigation_api_helpers {
32
33namespace {
34
35// Returns |time| as milliseconds since the epoch.
36double MilliSecondsFromTime(const base::Time& time) {
37  return 1000 * time.ToDoubleT();
38}
39
40// Dispatches events to the extension message service.
41void DispatchEvent(content::BrowserContext* browser_context,
42                   const std::string& event_name,
43                   scoped_ptr<base::ListValue> args,
44                   const GURL& url) {
45  EventFilteringInfo info;
46  info.SetURL(url);
47
48  Profile* profile = Profile::FromBrowserContext(browser_context);
49  EventRouter* event_router = EventRouter::Get(profile);
50  if (profile && event_router) {
51    scoped_ptr<Event> event(new Event(event_name, args.Pass()));
52    event->restrict_to_browser_context = profile;
53    event->filter_info = info;
54    event_router->BroadcastEvent(event.Pass());
55  }
56}
57
58}  // namespace
59
60int GetFrameId(content::RenderFrameHost* frame_host) {
61  if (!frame_host)
62    return -1;
63  return !frame_host->GetParent() ? 0 : frame_host->GetRoutingID();
64}
65
66// Constructs and dispatches an onBeforeNavigate event.
67// TODO(dcheng): Is the parent process ID needed here? http://crbug.com/393640
68// Collisions are probably possible... but maybe this won't ever happen because
69// of the SiteInstance grouping policies.
70void DispatchOnBeforeNavigate(content::WebContents* web_contents,
71                              content::RenderFrameHost* frame_host,
72                              const GURL& validated_url) {
73  scoped_ptr<base::ListValue> args(new base::ListValue());
74  base::DictionaryValue* dict = new base::DictionaryValue();
75  dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(web_contents));
76  dict->SetString(keys::kUrlKey, validated_url.spec());
77  dict->SetInteger(keys::kProcessIdKey, frame_host->GetProcess()->GetID());
78  dict->SetInteger(keys::kFrameIdKey, GetFrameId(frame_host));
79  dict->SetInteger(keys::kParentFrameIdKey,
80                   GetFrameId(frame_host->GetParent()));
81  dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
82  args->Append(dict);
83
84  DispatchEvent(web_contents->GetBrowserContext(),
85                web_navigation::OnBeforeNavigate::kEventName,
86                args.Pass(),
87                validated_url);
88}
89
90// Constructs and dispatches an onCommitted or onReferenceFragmentUpdated
91// event.
92void DispatchOnCommitted(const std::string& event_name,
93                         content::WebContents* web_contents,
94                         content::RenderFrameHost* frame_host,
95                         const GURL& url,
96                         ui::PageTransition transition_type) {
97  scoped_ptr<base::ListValue> args(new base::ListValue());
98  base::DictionaryValue* dict = new base::DictionaryValue();
99  dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(web_contents));
100  dict->SetString(keys::kUrlKey, url.spec());
101  dict->SetInteger(keys::kProcessIdKey, frame_host->GetProcess()->GetID());
102  dict->SetInteger(keys::kFrameIdKey, GetFrameId(frame_host));
103  std::string transition_type_string =
104      ui::PageTransitionGetCoreTransitionString(transition_type);
105  // For webNavigation API backward compatibility, keep "start_page" even after
106  // renamed to "auto_toplevel".
107  if (ui::PageTransitionStripQualifier(transition_type) ==
108          ui::PAGE_TRANSITION_AUTO_TOPLEVEL)
109    transition_type_string = "start_page";
110  dict->SetString(keys::kTransitionTypeKey, transition_type_string);
111  base::ListValue* qualifiers = new base::ListValue();
112  if (transition_type & ui::PAGE_TRANSITION_CLIENT_REDIRECT)
113    qualifiers->Append(new base::StringValue("client_redirect"));
114  if (transition_type & ui::PAGE_TRANSITION_SERVER_REDIRECT)
115    qualifiers->Append(new base::StringValue("server_redirect"));
116  if (transition_type & ui::PAGE_TRANSITION_FORWARD_BACK)
117    qualifiers->Append(new base::StringValue("forward_back"));
118  if (transition_type & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)
119    qualifiers->Append(new base::StringValue("from_address_bar"));
120  dict->Set(keys::kTransitionQualifiersKey, qualifiers);
121  dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
122  args->Append(dict);
123
124  DispatchEvent(web_contents->GetBrowserContext(), event_name, args.Pass(),
125                url);
126}
127
128// Constructs and dispatches an onDOMContentLoaded event.
129void DispatchOnDOMContentLoaded(content::WebContents* web_contents,
130                                content::RenderFrameHost* frame_host,
131                                const GURL& url) {
132  scoped_ptr<base::ListValue> args(new base::ListValue());
133  base::DictionaryValue* dict = new base::DictionaryValue();
134  dict->SetInteger(keys::kTabIdKey,
135                   ExtensionTabUtil::GetTabId(web_contents));
136  dict->SetString(keys::kUrlKey, url.spec());
137  dict->SetInteger(keys::kProcessIdKey, frame_host->GetProcess()->GetID());
138  dict->SetInteger(keys::kFrameIdKey, GetFrameId(frame_host));
139  dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
140  args->Append(dict);
141
142  DispatchEvent(web_contents->GetBrowserContext(),
143                web_navigation::OnDOMContentLoaded::kEventName,
144                args.Pass(),
145                url);
146}
147
148// Constructs and dispatches an onCompleted event.
149void DispatchOnCompleted(content::WebContents* web_contents,
150                         content::RenderFrameHost* frame_host,
151                         const GURL& url) {
152  scoped_ptr<base::ListValue> args(new base::ListValue());
153  base::DictionaryValue* dict = new base::DictionaryValue();
154  dict->SetInteger(keys::kTabIdKey,
155                   ExtensionTabUtil::GetTabId(web_contents));
156  dict->SetString(keys::kUrlKey, url.spec());
157  dict->SetInteger(keys::kProcessIdKey, frame_host->GetProcess()->GetID());
158  dict->SetInteger(keys::kFrameIdKey, GetFrameId(frame_host));
159  dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
160  args->Append(dict);
161
162  DispatchEvent(web_contents->GetBrowserContext(),
163                web_navigation::OnCompleted::kEventName,
164                args.Pass(), url);
165}
166
167// Constructs and dispatches an onCreatedNavigationTarget event.
168void DispatchOnCreatedNavigationTarget(
169    content::WebContents* web_contents,
170    content::BrowserContext* browser_context,
171    content::RenderFrameHost* source_frame_host,
172    content::WebContents* target_web_contents,
173    const GURL& target_url) {
174  // Check that the tab is already inserted into a tab strip model. This code
175  // path is exercised by ExtensionApiTest.WebNavigationRequestOpenTab.
176  DCHECK(ExtensionTabUtil::GetTabById(
177      ExtensionTabUtil::GetTabId(target_web_contents),
178      Profile::FromBrowserContext(target_web_contents->GetBrowserContext()),
179      false, NULL, NULL, NULL, NULL));
180
181  scoped_ptr<base::ListValue> args(new base::ListValue());
182  base::DictionaryValue* dict = new base::DictionaryValue();
183  dict->SetInteger(keys::kSourceTabIdKey,
184                   ExtensionTabUtil::GetTabId(web_contents));
185  dict->SetInteger(keys::kSourceProcessIdKey,
186                   source_frame_host->GetProcess()->GetID());
187  dict->SetInteger(keys::kSourceFrameIdKey, GetFrameId(source_frame_host));
188  dict->SetString(keys::kUrlKey, target_url.possibly_invalid_spec());
189  dict->SetInteger(keys::kTabIdKey,
190                   ExtensionTabUtil::GetTabId(target_web_contents));
191  dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
192  args->Append(dict);
193
194  DispatchEvent(browser_context,
195                web_navigation::OnCreatedNavigationTarget::kEventName,
196                args.Pass(),
197                target_url);
198}
199
200// Constructs and dispatches an onErrorOccurred event.
201void DispatchOnErrorOccurred(content::WebContents* web_contents,
202                             content::RenderFrameHost* frame_host,
203                             const GURL& url,
204                             int error_code) {
205  scoped_ptr<base::ListValue> args(new base::ListValue());
206  base::DictionaryValue* dict = new base::DictionaryValue();
207  dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(web_contents));
208  dict->SetString(keys::kUrlKey, url.spec());
209  dict->SetInteger(keys::kProcessIdKey, frame_host->GetProcess()->GetID());
210  dict->SetInteger(keys::kFrameIdKey, GetFrameId(frame_host));
211  dict->SetString(keys::kErrorKey, net::ErrorToString(error_code));
212  dict->SetDouble(keys::kTimeStampKey,
213      MilliSecondsFromTime(base::Time::Now()));
214  args->Append(dict);
215
216  DispatchEvent(web_contents->GetBrowserContext(),
217                web_navigation::OnErrorOccurred::kEventName,
218                args.Pass(), url);
219}
220
221// Constructs and dispatches an onTabReplaced event.
222void DispatchOnTabReplaced(
223    content::WebContents* old_web_contents,
224    content::BrowserContext* browser_context,
225    content::WebContents* new_web_contents) {
226  scoped_ptr<base::ListValue> args(new base::ListValue());
227  base::DictionaryValue* dict = new base::DictionaryValue();
228  dict->SetInteger(keys::kReplacedTabIdKey,
229                   ExtensionTabUtil::GetTabId(old_web_contents));
230  dict->SetInteger(
231      keys::kTabIdKey,
232      ExtensionTabUtil::GetTabId(new_web_contents));
233  dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
234  args->Append(dict);
235
236  DispatchEvent(browser_context,
237                web_navigation::OnTabReplaced::kEventName,
238                args.Pass(),
239                GURL());
240}
241
242}  // namespace web_navigation_api_helpers
243
244}  // namespace extensions
245