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
23namespace keys = extension_webnavigation_api_constants;
24
25namespace {
26
27// URL schemes for which we'll send events.
28const 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.
37int 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.
42double MilliSecondsFromTime(const base::Time& time) {
43  return 1000 * time.ToDoubleT();
44}
45
46// Dispatches events to the extension message service.
47void 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.
57void 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.
79void 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.
109void 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.
129void 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.
149void 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
173bool FrameNavigationState::allow_extension_scheme_ = false;
174
175FrameNavigationState::FrameNavigationState() {}
176
177FrameNavigationState::~FrameNavigationState() {}
178
179bool 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
196void 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
210GURL 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
220bool 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
230void 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
235void 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
250ExtensionWebNavigationEventRouter::ExtensionWebNavigationEventRouter() {}
251
252ExtensionWebNavigationEventRouter::~ExtensionWebNavigationEventRouter() {}
253
254// static
255ExtensionWebNavigationEventRouter*
256ExtensionWebNavigationEventRouter::GetInstance() {
257  return Singleton<ExtensionWebNavigationEventRouter>::get();
258}
259
260void ExtensionWebNavigationEventRouter::Init() {
261  if (registrar_.IsEmpty()) {
262    registrar_.Add(this,
263                   NotificationType::CREATING_NEW_WINDOW,
264                   NotificationService::AllSources());
265  }
266}
267
268void 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
284void 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
296ExtensionWebNavigationTabObserver::ExtensionWebNavigationTabObserver(
297    TabContents* tab_contents)
298    : TabContentsObserver(tab_contents) {}
299
300ExtensionWebNavigationTabObserver::~ExtensionWebNavigationTabObserver() {}
301
302void 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
318void 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
336void 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
360void 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
370void 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
380void ExtensionWebNavigationTabObserver::TabContentsDestroyed(
381    TabContents* tab) {
382  navigation_state_.RemoveTabContentsState(tab);
383}
384
385void 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.
411bool 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
424void 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