1 // Copyright (c) 2011 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/extension_webnavigation_api.h"
8
9 #include "base/json/json_writer.h"
10 #include "base/string_number_conversions.h"
11 #include "base/time.h"
12 #include "base/values.h"
13 #include "chrome/browser/extensions/extension_event_router.h"
14 #include "chrome/browser/extensions/extension_tabs_module.h"
15 #include "chrome/browser/extensions/extension_webnavigation_api_constants.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/url_constants.h"
18 #include "content/browser/tab_contents/tab_contents.h"
19 #include "content/common/notification_service.h"
20 #include "content/common/view_messages.h"
21 #include "net/base/net_errors.h"
22
23 namespace keys = extension_webnavigation_api_constants;
24
25 namespace {
26
27 // URL schemes for which we'll send events.
28 const char* kValidSchemes[] = {
29 chrome::kHttpScheme,
30 chrome::kHttpsScheme,
31 chrome::kFileScheme,
32 chrome::kFtpScheme,
33 };
34
35 // Returns 0 if the navigation happens in the main frame, or the frame ID
36 // modulo 32 bits otherwise.
GetFrameId(bool is_main_frame,int64 frame_id)37 int GetFrameId(bool is_main_frame, int64 frame_id) {
38 return is_main_frame ? 0 : static_cast<int>(frame_id);
39 }
40
41 // Returns |time| as milliseconds since the epoch.
MilliSecondsFromTime(const base::Time & time)42 double MilliSecondsFromTime(const base::Time& time) {
43 return 1000 * time.ToDoubleT();
44 }
45
46 // Dispatches events to the extension message service.
DispatchEvent(Profile * profile,const char * event_name,const std::string & json_args)47 void DispatchEvent(Profile* profile,
48 const char* event_name,
49 const std::string& json_args) {
50 if (profile && profile->GetExtensionEventRouter()) {
51 profile->GetExtensionEventRouter()->DispatchEventToRenderers(
52 event_name, json_args, profile, GURL());
53 }
54 }
55
56 // Constructs and dispatches an onBeforeNavigate event.
DispatchOnBeforeNavigate(TabContents * tab_contents,int64 frame_id,bool is_main_frame,const GURL & validated_url,uint64 request_id)57 void DispatchOnBeforeNavigate(TabContents* tab_contents,
58 int64 frame_id,
59 bool is_main_frame,
60 const GURL& validated_url,
61 uint64 request_id) {
62 ListValue args;
63 DictionaryValue* dict = new DictionaryValue();
64 dict->SetInteger(keys::kTabIdKey,
65 ExtensionTabUtil::GetTabId(tab_contents));
66 dict->SetString(keys::kUrlKey, validated_url.spec());
67 dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id));
68 dict->SetString(keys::kRequestIdKey,
69 base::Uint64ToString(request_id));
70 dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
71 args.Append(dict);
72
73 std::string json_args;
74 base::JSONWriter::Write(&args, false, &json_args);
75 DispatchEvent(tab_contents->profile(), keys::kOnBeforeNavigate, json_args);
76 }
77
78 // Constructs and dispatches an onCommitted event.
DispatchOnCommitted(TabContents * tab_contents,int64 frame_id,bool is_main_frame,const GURL & url,PageTransition::Type transition_type)79 void DispatchOnCommitted(TabContents* tab_contents,
80 int64 frame_id,
81 bool is_main_frame,
82 const GURL& url,
83 PageTransition::Type transition_type) {
84 ListValue args;
85 DictionaryValue* dict = new DictionaryValue();
86 dict->SetInteger(keys::kTabIdKey,
87 ExtensionTabUtil::GetTabId(tab_contents));
88 dict->SetString(keys::kUrlKey, url.spec());
89 dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id));
90 dict->SetString(keys::kTransitionTypeKey,
91 PageTransition::CoreTransitionString(transition_type));
92 ListValue* qualifiers = new ListValue();
93 if (transition_type & PageTransition::CLIENT_REDIRECT)
94 qualifiers->Append(Value::CreateStringValue("client_redirect"));
95 if (transition_type & PageTransition::SERVER_REDIRECT)
96 qualifiers->Append(Value::CreateStringValue("server_redirect"));
97 if (transition_type & PageTransition::FORWARD_BACK)
98 qualifiers->Append(Value::CreateStringValue("forward_back"));
99 dict->Set(keys::kTransitionQualifiersKey, qualifiers);
100 dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
101 args.Append(dict);
102
103 std::string json_args;
104 base::JSONWriter::Write(&args, false, &json_args);
105 DispatchEvent(tab_contents->profile(), keys::kOnCommitted, json_args);
106 }
107
108 // Constructs and dispatches an onDOMContentLoaded event.
DispatchOnDOMContentLoaded(TabContents * tab_contents,const GURL & url,bool is_main_frame,int64 frame_id)109 void DispatchOnDOMContentLoaded(TabContents* tab_contents,
110 const GURL& url,
111 bool is_main_frame,
112 int64 frame_id) {
113 ListValue args;
114 DictionaryValue* dict = new DictionaryValue();
115 dict->SetInteger(keys::kTabIdKey,
116 ExtensionTabUtil::GetTabId(tab_contents));
117 dict->SetString(keys::kUrlKey, url.spec());
118 dict->SetInteger(keys::kFrameIdKey,
119 is_main_frame ? 0 : static_cast<int>(frame_id));
120 dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
121 args.Append(dict);
122
123 std::string json_args;
124 base::JSONWriter::Write(&args, false, &json_args);
125 DispatchEvent(tab_contents->profile(), keys::kOnDOMContentLoaded, json_args);
126 }
127
128 // Constructs and dispatches an onCompleted event.
DispatchOnCompleted(TabContents * tab_contents,const GURL & url,bool is_main_frame,int64 frame_id)129 void DispatchOnCompleted(TabContents* tab_contents,
130 const GURL& url,
131 bool is_main_frame,
132 int64 frame_id) {
133 ListValue args;
134 DictionaryValue* dict = new DictionaryValue();
135 dict->SetInteger(keys::kTabIdKey,
136 ExtensionTabUtil::GetTabId(tab_contents));
137 dict->SetString(keys::kUrlKey, url.spec());
138 dict->SetInteger(keys::kFrameIdKey,
139 is_main_frame ? 0 : static_cast<int>(frame_id));
140 dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
141 args.Append(dict);
142
143 std::string json_args;
144 base::JSONWriter::Write(&args, false, &json_args);
145 DispatchEvent(tab_contents->profile(), keys::kOnCompleted, json_args);
146 }
147
148 // Constructs and dispatches an onBeforeRetarget event.
DispatchOnBeforeRetarget(TabContents * tab_contents,Profile * profile,const GURL & opener_url,const GURL & target_url)149 void DispatchOnBeforeRetarget(TabContents* tab_contents,
150 Profile* profile,
151 const GURL& opener_url,
152 const GURL& target_url) {
153 ListValue args;
154 DictionaryValue* dict = new DictionaryValue();
155 dict->SetInteger(keys::kSourceTabIdKey,
156 ExtensionTabUtil::GetTabId(tab_contents));
157 dict->SetString(keys::kSourceUrlKey, opener_url.spec());
158 dict->SetString(keys::kUrlKey, target_url.possibly_invalid_spec());
159 dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
160 args.Append(dict);
161
162 std::string json_args;
163 base::JSONWriter::Write(&args, false, &json_args);
164 DispatchEvent(profile, keys::kOnBeforeRetarget, json_args);
165 }
166
167 } // namespace
168
169
170 // FrameNavigationState -------------------------------------------------------
171
172 // static
173 bool FrameNavigationState::allow_extension_scheme_ = false;
174
FrameNavigationState()175 FrameNavigationState::FrameNavigationState() {}
176
~FrameNavigationState()177 FrameNavigationState::~FrameNavigationState() {}
178
CanSendEvents(int64 frame_id) const179 bool FrameNavigationState::CanSendEvents(int64 frame_id) const {
180 FrameIdToStateMap::const_iterator frame_state =
181 frame_state_map_.find(frame_id);
182 if (frame_state == frame_state_map_.end() ||
183 frame_state->second.error_occurred) {
184 return false;
185 }
186 const std::string& scheme = frame_state->second.url.scheme();
187 for (unsigned i = 0; i < arraysize(kValidSchemes); ++i) {
188 if (scheme == kValidSchemes[i])
189 return true;
190 }
191 if (allow_extension_scheme_ && scheme == chrome::kExtensionScheme)
192 return true;
193 return false;
194 }
195
TrackFrame(int64 frame_id,const GURL & url,bool is_main_frame,bool is_error_page,const TabContents * tab_contents)196 void FrameNavigationState::TrackFrame(int64 frame_id,
197 const GURL& url,
198 bool is_main_frame,
199 bool is_error_page,
200 const TabContents* tab_contents) {
201 if (is_main_frame)
202 RemoveTabContentsState(tab_contents);
203 tab_contents_map_.insert(std::make_pair(tab_contents, frame_id));
204 FrameState& frame_state = frame_state_map_[frame_id];
205 frame_state.error_occurred = is_error_page;
206 frame_state.url = url;
207 frame_state.is_main_frame = is_main_frame;
208 }
209
GetUrl(int64 frame_id) const210 GURL FrameNavigationState::GetUrl(int64 frame_id) const {
211 FrameIdToStateMap::const_iterator frame_state =
212 frame_state_map_.find(frame_id);
213 if (frame_state == frame_state_map_.end()) {
214 NOTREACHED();
215 return GURL();
216 }
217 return frame_state->second.url;
218 }
219
IsMainFrame(int64 frame_id) const220 bool FrameNavigationState::IsMainFrame(int64 frame_id) const {
221 FrameIdToStateMap::const_iterator frame_state =
222 frame_state_map_.find(frame_id);
223 if (frame_state == frame_state_map_.end()) {
224 NOTREACHED();
225 return false;
226 }
227 return frame_state->second.is_main_frame;
228 }
229
ErrorOccurredInFrame(int64 frame_id)230 void FrameNavigationState::ErrorOccurredInFrame(int64 frame_id) {
231 DCHECK(frame_state_map_.find(frame_id) != frame_state_map_.end());
232 frame_state_map_[frame_id].error_occurred = true;
233 }
234
RemoveTabContentsState(const TabContents * tab_contents)235 void FrameNavigationState::RemoveTabContentsState(
236 const TabContents* tab_contents) {
237 typedef TabContentsToFrameIdMap::iterator FrameIdIterator;
238 std::pair<FrameIdIterator, FrameIdIterator> frame_ids =
239 tab_contents_map_.equal_range(tab_contents);
240 for (FrameIdIterator frame_id = frame_ids.first; frame_id != frame_ids.second;
241 ++frame_id) {
242 frame_state_map_.erase(frame_id->second);
243 }
244 tab_contents_map_.erase(tab_contents);
245 }
246
247
248 // ExtensionWebNavigtionEventRouter -------------------------------------------
249
ExtensionWebNavigationEventRouter()250 ExtensionWebNavigationEventRouter::ExtensionWebNavigationEventRouter() {}
251
~ExtensionWebNavigationEventRouter()252 ExtensionWebNavigationEventRouter::~ExtensionWebNavigationEventRouter() {}
253
254 // static
255 ExtensionWebNavigationEventRouter*
GetInstance()256 ExtensionWebNavigationEventRouter::GetInstance() {
257 return Singleton<ExtensionWebNavigationEventRouter>::get();
258 }
259
Init()260 void ExtensionWebNavigationEventRouter::Init() {
261 if (registrar_.IsEmpty()) {
262 registrar_.Add(this,
263 NotificationType::CREATING_NEW_WINDOW,
264 NotificationService::AllSources());
265 }
266 }
267
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)268 void ExtensionWebNavigationEventRouter::Observe(
269 NotificationType type,
270 const NotificationSource& source,
271 const NotificationDetails& details) {
272 switch (type.value) {
273 case NotificationType::CREATING_NEW_WINDOW:
274 CreatingNewWindow(
275 Source<TabContents>(source).ptr(),
276 Details<const ViewHostMsg_CreateWindow_Params>(details).ptr());
277 break;
278
279 default:
280 NOTREACHED();
281 }
282 }
283
CreatingNewWindow(TabContents * tab_contents,const ViewHostMsg_CreateWindow_Params * details)284 void ExtensionWebNavigationEventRouter::CreatingNewWindow(
285 TabContents* tab_contents,
286 const ViewHostMsg_CreateWindow_Params* details) {
287 DispatchOnBeforeRetarget(tab_contents,
288 tab_contents->profile(),
289 details->opener_url,
290 details->target_url);
291 }
292
293
294 // ExtensionWebNavigationTabObserver ------------------------------------------
295
ExtensionWebNavigationTabObserver(TabContents * tab_contents)296 ExtensionWebNavigationTabObserver::ExtensionWebNavigationTabObserver(
297 TabContents* tab_contents)
298 : TabContentsObserver(tab_contents) {}
299
~ExtensionWebNavigationTabObserver()300 ExtensionWebNavigationTabObserver::~ExtensionWebNavigationTabObserver() {}
301
DidStartProvisionalLoadForFrame(int64 frame_id,bool is_main_frame,const GURL & validated_url,bool is_error_page)302 void ExtensionWebNavigationTabObserver::DidStartProvisionalLoadForFrame(
303 int64 frame_id,
304 bool is_main_frame,
305 const GURL& validated_url,
306 bool is_error_page) {
307 navigation_state_.TrackFrame(frame_id,
308 validated_url,
309 is_main_frame,
310 is_error_page,
311 tab_contents());
312 if (!navigation_state_.CanSendEvents(frame_id))
313 return;
314 DispatchOnBeforeNavigate(
315 tab_contents(), frame_id, is_main_frame, validated_url, 0);
316 }
317
DidCommitProvisionalLoadForFrame(int64 frame_id,bool is_main_frame,const GURL & url,PageTransition::Type transition_type)318 void ExtensionWebNavigationTabObserver::DidCommitProvisionalLoadForFrame(
319 int64 frame_id,
320 bool is_main_frame,
321 const GURL& url,
322 PageTransition::Type transition_type) {
323 if (!navigation_state_.CanSendEvents(frame_id))
324 return;
325 // On reference fragment navigations, only a new navigation state is
326 // committed. We need to catch this case and generate a full sequence
327 // of events.
328 if (IsReferenceFragmentNavigation(frame_id, url)) {
329 NavigatedReferenceFragment(frame_id, is_main_frame, url, transition_type);
330 return;
331 }
332 DispatchOnCommitted(
333 tab_contents(), frame_id, is_main_frame, url, transition_type);
334 }
335
DidFailProvisionalLoad(int64 frame_id,bool is_main_frame,const GURL & validated_url,int error_code)336 void ExtensionWebNavigationTabObserver::DidFailProvisionalLoad(
337 int64 frame_id,
338 bool is_main_frame,
339 const GURL& validated_url,
340 int error_code) {
341 if (!navigation_state_.CanSendEvents(frame_id))
342 return;
343 ListValue args;
344 DictionaryValue* dict = new DictionaryValue();
345 dict->SetInteger(keys::kTabIdKey,
346 ExtensionTabUtil::GetTabId(tab_contents()));
347 dict->SetString(keys::kUrlKey, validated_url.spec());
348 dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id));
349 dict->SetString(keys::kErrorKey,
350 std::string(net::ErrorToString(error_code)));
351 dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
352 args.Append(dict);
353
354 std::string json_args;
355 base::JSONWriter::Write(&args, false, &json_args);
356 navigation_state_.ErrorOccurredInFrame(frame_id);
357 DispatchEvent(tab_contents()->profile(), keys::kOnErrorOccurred, json_args);
358 }
359
DocumentLoadedInFrame(int64 frame_id)360 void ExtensionWebNavigationTabObserver::DocumentLoadedInFrame(
361 int64 frame_id) {
362 if (!navigation_state_.CanSendEvents(frame_id))
363 return;
364 DispatchOnDOMContentLoaded(tab_contents(),
365 navigation_state_.GetUrl(frame_id),
366 navigation_state_.IsMainFrame(frame_id),
367 frame_id);
368 }
369
DidFinishLoad(int64 frame_id)370 void ExtensionWebNavigationTabObserver::DidFinishLoad(
371 int64 frame_id) {
372 if (!navigation_state_.CanSendEvents(frame_id))
373 return;
374 DispatchOnCompleted(tab_contents(),
375 navigation_state_.GetUrl(frame_id),
376 navigation_state_.IsMainFrame(frame_id),
377 frame_id);
378 }
379
TabContentsDestroyed(TabContents * tab)380 void ExtensionWebNavigationTabObserver::TabContentsDestroyed(
381 TabContents* tab) {
382 navigation_state_.RemoveTabContentsState(tab);
383 }
384
DidOpenURL(const GURL & url,const GURL & referrer,WindowOpenDisposition disposition,PageTransition::Type transition)385 void ExtensionWebNavigationTabObserver::DidOpenURL(
386 const GURL& url,
387 const GURL& referrer,
388 WindowOpenDisposition disposition,
389 PageTransition::Type transition) {
390 if (disposition != NEW_FOREGROUND_TAB &&
391 disposition != NEW_BACKGROUND_TAB &&
392 disposition != NEW_WINDOW &&
393 disposition != OFF_THE_RECORD) {
394 return;
395 }
396 Profile* profile = tab_contents()->profile();
397 if (disposition == OFF_THE_RECORD) {
398 if (!profile->HasOffTheRecordProfile()) {
399 NOTREACHED();
400 return;
401 }
402 profile = profile->GetOffTheRecordProfile();
403 }
404 DispatchOnBeforeRetarget(tab_contents(),
405 profile,
406 tab_contents()->GetURL(),
407 url);
408 }
409
410 // See also NavigationController::IsURLInPageNavigation.
IsReferenceFragmentNavigation(int64 frame_id,const GURL & url)411 bool ExtensionWebNavigationTabObserver::IsReferenceFragmentNavigation(
412 int64 frame_id,
413 const GURL& url) {
414 GURL existing_url = navigation_state_.GetUrl(frame_id);
415 if (existing_url == url)
416 return false;
417
418 url_canon::Replacements<char> replacements;
419 replacements.ClearRef();
420 return existing_url.ReplaceComponents(replacements) ==
421 url.ReplaceComponents(replacements);
422 }
423
NavigatedReferenceFragment(int64 frame_id,bool is_main_frame,const GURL & url,PageTransition::Type transition_type)424 void ExtensionWebNavigationTabObserver::NavigatedReferenceFragment(
425 int64 frame_id,
426 bool is_main_frame,
427 const GURL& url,
428 PageTransition::Type transition_type) {
429 navigation_state_.TrackFrame(frame_id,
430 url,
431 is_main_frame,
432 false,
433 tab_contents());
434
435 DispatchOnBeforeNavigate(tab_contents(),
436 frame_id,
437 is_main_frame,
438 url,
439 0);
440 DispatchOnCommitted(tab_contents(),
441 frame_id,
442 is_main_frame,
443 url,
444 transition_type);
445 DispatchOnDOMContentLoaded(tab_contents(),
446 url,
447 is_main_frame,
448 frame_id);
449 DispatchOnCompleted(tab_contents(),
450 url,
451 is_main_frame,
452 frame_id);
453 }
454