// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Implements the Chrome Extensions WebNavigation API.

#include "chrome/browser/extensions/api/web_navigation/web_navigation_api.h"

#include "base/lazy_instance.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/web_navigation/web_navigation_api_constants.h"
#include "chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/retargeting_details.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/extensions/api/web_navigation.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_request_details.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/view_type_utils.h"
#include "net/base/net_errors.h"

using content::ResourceType;

namespace GetFrame = extensions::api::web_navigation::GetFrame;
namespace GetAllFrames = extensions::api::web_navigation::GetAllFrames;

DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::WebNavigationTabObserver);

namespace extensions {

namespace helpers = web_navigation_api_helpers;
namespace keys = web_navigation_api_constants;
namespace web_navigation = api::web_navigation;

namespace {

typedef std::map<content::WebContents*, WebNavigationTabObserver*>
    TabObserverMap;
static base::LazyInstance<TabObserverMap> g_tab_observer =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

// WebNavigtionEventRouter -------------------------------------------

WebNavigationEventRouter::PendingWebContents::PendingWebContents()
    : source_web_contents(NULL),
      source_frame_host(NULL),
      target_web_contents(NULL),
      target_url() {
}

WebNavigationEventRouter::PendingWebContents::PendingWebContents(
    content::WebContents* source_web_contents,
    content::RenderFrameHost* source_frame_host,
    content::WebContents* target_web_contents,
    const GURL& target_url)
    : source_web_contents(source_web_contents),
      source_frame_host(source_frame_host),
      target_web_contents(target_web_contents),
      target_url(target_url) {
}

WebNavigationEventRouter::PendingWebContents::~PendingWebContents() {}

WebNavigationEventRouter::WebNavigationEventRouter(Profile* profile)
    : profile_(profile) {
  CHECK(registrar_.IsEmpty());
  registrar_.Add(this,
                 chrome::NOTIFICATION_RETARGETING,
                 content::NotificationService::AllSources());
  registrar_.Add(this,
                 chrome::NOTIFICATION_TAB_ADDED,
                 content::NotificationService::AllSources());
  registrar_.Add(this,
                 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
                 content::NotificationService::AllSources());

  BrowserList::AddObserver(this);
  for (chrome::BrowserIterator it; !it.done(); it.Next())
    OnBrowserAdded(*it);
}

WebNavigationEventRouter::~WebNavigationEventRouter() {
  for (chrome::BrowserIterator it; !it.done(); it.Next())
    OnBrowserRemoved(*it);
  BrowserList::RemoveObserver(this);
}

void WebNavigationEventRouter::OnBrowserAdded(Browser* browser) {
  if (!profile_->IsSameProfile(browser->profile()))
    return;
  browser->tab_strip_model()->AddObserver(this);
}

void WebNavigationEventRouter::OnBrowserRemoved(Browser* browser) {
  if (!profile_->IsSameProfile(browser->profile()))
    return;
  browser->tab_strip_model()->RemoveObserver(this);
}

void WebNavigationEventRouter::TabReplacedAt(
    TabStripModel* tab_strip_model,
    content::WebContents* old_contents,
    content::WebContents* new_contents,
    int index) {
  WebNavigationTabObserver* tab_observer =
      WebNavigationTabObserver::Get(old_contents);
  if (!tab_observer) {
    // If you hit this DCHECK(), please add reproduction steps to
    // http://crbug.com/109464.
    DCHECK(GetViewType(old_contents) != VIEW_TYPE_TAB_CONTENTS);
    return;
  }
  const FrameNavigationState& frame_navigation_state =
      tab_observer->frame_navigation_state();

  if (!frame_navigation_state.IsValidUrl(old_contents->GetURL()) ||
      !frame_navigation_state.IsValidUrl(new_contents->GetURL()))
    return;

  helpers::DispatchOnTabReplaced(old_contents, profile_, new_contents);
}

void WebNavigationEventRouter::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  switch (type) {
    case chrome::NOTIFICATION_RETARGETING: {
      Profile* profile = content::Source<Profile>(source).ptr();
      if (profile->GetOriginalProfile() == profile_) {
        Retargeting(
            content::Details<const RetargetingDetails>(details).ptr());
      }
      break;
    }

    case chrome::NOTIFICATION_TAB_ADDED:
      TabAdded(content::Details<content::WebContents>(details).ptr());
      break;

    case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
      TabDestroyed(content::Source<content::WebContents>(source).ptr());
      break;

    default:
      NOTREACHED();
  }
}

void WebNavigationEventRouter::Retargeting(const RetargetingDetails* details) {
  if (details->source_render_frame_id == 0)
    return;
  WebNavigationTabObserver* tab_observer =
      WebNavigationTabObserver::Get(details->source_web_contents);
  if (!tab_observer) {
    // If you hit this DCHECK(), please add reproduction steps to
    // http://crbug.com/109464.
    DCHECK(GetViewType(details->source_web_contents) != VIEW_TYPE_TAB_CONTENTS);
    return;
  }
  const FrameNavigationState& frame_navigation_state =
      tab_observer->frame_navigation_state();

  content::RenderFrameHost* frame_host = content::RenderFrameHost::FromID(
      details->source_web_contents->GetRenderProcessHost()->GetID(),
      details->source_render_frame_id);
  if (!frame_navigation_state.CanSendEvents(frame_host))
    return;

  // If the WebContents isn't yet inserted into a tab strip, we need to delay
  // the extension event until the WebContents is fully initialized.
  if (details->not_yet_in_tabstrip) {
    pending_web_contents_[details->target_web_contents] =
        PendingWebContents(details->source_web_contents,
                           frame_host,
                           details->target_web_contents,
                           details->target_url);
  } else {
    helpers::DispatchOnCreatedNavigationTarget(
        details->source_web_contents,
        details->target_web_contents->GetBrowserContext(),
        frame_host,
        details->target_web_contents,
        details->target_url);
  }
}

void WebNavigationEventRouter::TabAdded(content::WebContents* tab) {
  std::map<content::WebContents*, PendingWebContents>::iterator iter =
      pending_web_contents_.find(tab);
  if (iter == pending_web_contents_.end())
    return;

  WebNavigationTabObserver* tab_observer =
      WebNavigationTabObserver::Get(iter->second.source_web_contents);
  if (!tab_observer) {
    NOTREACHED();
    return;
  }
  const FrameNavigationState& frame_navigation_state =
      tab_observer->frame_navigation_state();

  if (frame_navigation_state.CanSendEvents(iter->second.source_frame_host)) {
    helpers::DispatchOnCreatedNavigationTarget(
        iter->second.source_web_contents,
        iter->second.target_web_contents->GetBrowserContext(),
        iter->second.source_frame_host,
        iter->second.target_web_contents,
        iter->second.target_url);
  }
  pending_web_contents_.erase(iter);
}

void WebNavigationEventRouter::TabDestroyed(content::WebContents* tab) {
  pending_web_contents_.erase(tab);
  for (std::map<content::WebContents*, PendingWebContents>::iterator i =
           pending_web_contents_.begin(); i != pending_web_contents_.end(); ) {
    if (i->second.source_web_contents == tab)
      pending_web_contents_.erase(i++);
    else
      ++i;
  }
}

// WebNavigationTabObserver ------------------------------------------

WebNavigationTabObserver::WebNavigationTabObserver(
    content::WebContents* web_contents)
    : WebContentsObserver(web_contents),
      render_view_host_(NULL),
      pending_render_view_host_(NULL) {
  g_tab_observer.Get().insert(TabObserverMap::value_type(web_contents, this));
  registrar_.Add(this,
                 content::NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW,
                 content::NotificationService::AllSources());
}

WebNavigationTabObserver::~WebNavigationTabObserver() {}

// static
WebNavigationTabObserver* WebNavigationTabObserver::Get(
    content::WebContents* web_contents) {
  TabObserverMap::iterator i = g_tab_observer.Get().find(web_contents);
  return i == g_tab_observer.Get().end() ? NULL : i->second;
}

content::RenderViewHost* WebNavigationTabObserver::GetRenderViewHostInProcess(
    int process_id) const {
  if (render_view_host_ &&
      render_view_host_->GetProcess()->GetID() == process_id) {
    return render_view_host_;
  }
  if (pending_render_view_host_ &&
      pending_render_view_host_->GetProcess()->GetID() == process_id) {
    return pending_render_view_host_;
  }
  return NULL;
}

void WebNavigationTabObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  switch (type) {
    case content::NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW: {
      // The RenderView is technically not yet deleted, but the RenderViewHost
      // already starts to filter out some IPCs. In order to not get confused,
      // we consider the RenderView dead already now.
      RenderViewDeleted(content::Source<content::RenderViewHost>(source).ptr());
      break;
    }

    default:
      NOTREACHED();
  }
}

void WebNavigationTabObserver::RenderFrameDeleted(
    content::RenderFrameHost* render_frame_host) {
  content::RenderViewHost* render_view_host =
      render_frame_host->GetRenderViewHost();
  if (render_view_host != render_view_host_ &&
      render_view_host != pending_render_view_host_) {
    return;
  }
  if (navigation_state_.CanSendEvents(render_frame_host) &&
      !navigation_state_.GetNavigationCompleted(render_frame_host)) {
    helpers::DispatchOnErrorOccurred(
        web_contents(),
        render_frame_host,
        navigation_state_.GetUrl(render_frame_host),
        net::ERR_ABORTED);
  }
  navigation_state_.FrameDetached(render_frame_host);
}

void WebNavigationTabObserver::RenderViewDeleted(
    content::RenderViewHost* render_view_host) {
  if (render_view_host == render_view_host_) {
    render_view_host_ = NULL;
    if (pending_render_view_host_) {
      render_view_host_ = pending_render_view_host_;
      pending_render_view_host_ = NULL;
    }
  } else if (render_view_host == pending_render_view_host_) {
    pending_render_view_host_ = NULL;
  } else {
    return;
  }
  SendErrorEvents(web_contents(), render_view_host, NULL);
}

void WebNavigationTabObserver::AboutToNavigateRenderView(
    content::RenderViewHost* render_view_host) {
  if (!render_view_host_) {
    render_view_host_ = render_view_host;
  } else if (render_view_host != render_view_host_) {
    if (pending_render_view_host_) {
      SendErrorEvents(web_contents(), pending_render_view_host_, NULL);
    }
    pending_render_view_host_ = render_view_host;
  }
}

void WebNavigationTabObserver::DidStartProvisionalLoadForFrame(
    content::RenderFrameHost* render_frame_host,
    const GURL& validated_url,
    bool is_error_page,
    bool is_iframe_srcdoc) {
  content::RenderViewHost* render_view_host =
      render_frame_host->GetRenderViewHost();
  DVLOG(2) << "DidStartProvisionalLoad("
           << "render_view_host=" << render_view_host
           << ", frame_num=" << render_frame_host->GetRoutingID()
           << ", url=" << validated_url << ")";
  if (!render_view_host_)
    render_view_host_ = render_view_host;
  if (render_view_host != render_view_host_ &&
      render_view_host != pending_render_view_host_)
    return;

  navigation_state_.TrackFrame(
      render_frame_host, validated_url, is_error_page, is_iframe_srcdoc);

  if (!navigation_state_.CanSendEvents(render_frame_host))
    return;

  helpers::DispatchOnBeforeNavigate(
      web_contents(),
      render_frame_host,
      navigation_state_.GetUrl(render_frame_host));
}

void WebNavigationTabObserver::DidCommitProvisionalLoadForFrame(
    content::RenderFrameHost* render_frame_host,
    const GURL& url,
    ui::PageTransition transition_type) {
  content::RenderViewHost* render_view_host =
      render_frame_host->GetRenderViewHost();
  DVLOG(2) << "DidCommitProvisionalLoad("
           << "render_view_host=" << render_view_host
           << ", frame_num=" << render_frame_host->GetRoutingID()
           << ", url=" << url << ")";
  if (render_view_host != render_view_host_ &&
      render_view_host != pending_render_view_host_)
    return;

  bool is_reference_fragment_navigation =
      IsReferenceFragmentNavigation(render_frame_host, url);
  bool is_history_state_modification =
      navigation_state_.GetNavigationCommitted(render_frame_host);

  if (!render_frame_host->GetParent() &&
      render_view_host_ == render_view_host) {
    // Changing the reference fragment or the history state using
    // history.pushState or history.replaceState does not cancel on-going
    // iframe navigations.
    if (!is_reference_fragment_navigation && !is_history_state_modification)
      SendErrorEvents(web_contents(), render_view_host_, render_frame_host);
    if (pending_render_view_host_) {
      SendErrorEvents(web_contents(), pending_render_view_host_, NULL);
      pending_render_view_host_ = NULL;
    }
  } else if (pending_render_view_host_ == render_view_host) {
    SendErrorEvents(web_contents(), render_view_host_, NULL);
    render_view_host_ = pending_render_view_host_;
    pending_render_view_host_ = NULL;
  }

  // Update the URL as it might have changed.
  navigation_state_.UpdateFrame(render_frame_host, url);
  navigation_state_.SetNavigationCommitted(render_frame_host);

  if (!navigation_state_.CanSendEvents(render_frame_host))
    return;

  if (is_reference_fragment_navigation) {
    helpers::DispatchOnCommitted(
        web_navigation::OnReferenceFragmentUpdated::kEventName,
        web_contents(),
        render_frame_host,
        navigation_state_.GetUrl(render_frame_host),
        transition_type);
  } else if (is_history_state_modification) {
    helpers::DispatchOnCommitted(
        web_navigation::OnHistoryStateUpdated::kEventName,
        web_contents(),
        render_frame_host,
        navigation_state_.GetUrl(render_frame_host),
        transition_type);
  } else {
    if (navigation_state_.GetIsServerRedirected(render_frame_host)) {
      transition_type = ui::PageTransitionFromInt(
          transition_type | ui::PAGE_TRANSITION_SERVER_REDIRECT);
    }
    helpers::DispatchOnCommitted(web_navigation::OnCommitted::kEventName,
                                 web_contents(),
                                 render_frame_host,
                                 navigation_state_.GetUrl(render_frame_host),
                                 transition_type);
  }
}

void WebNavigationTabObserver::DidFailProvisionalLoad(
    content::RenderFrameHost* render_frame_host,
    const GURL& validated_url,
    int error_code,
    const base::string16& error_description) {
  content::RenderViewHost* render_view_host =
      render_frame_host->GetRenderViewHost();
  DVLOG(2) << "DidFailProvisionalLoad("
           << "render_view_host=" << render_view_host
           << ", frame_num=" << render_frame_host->GetRoutingID()
           << ", url=" << validated_url << ")";
  if (render_view_host != render_view_host_ &&
      render_view_host != pending_render_view_host_)
    return;
  bool stop_tracking_frames = false;
  if (render_view_host == pending_render_view_host_) {
    pending_render_view_host_ = NULL;
    stop_tracking_frames = true;
  }

  if (navigation_state_.CanSendEvents(render_frame_host)) {
    helpers::DispatchOnErrorOccurred(
        web_contents(),
        render_frame_host,
        navigation_state_.GetUrl(render_frame_host),
        error_code);
  }
  navigation_state_.SetErrorOccurredInFrame(render_frame_host);
  if (stop_tracking_frames) {
    navigation_state_.StopTrackingFramesInRVH(render_view_host, NULL);
  }
}

void WebNavigationTabObserver::DocumentLoadedInFrame(
    content::RenderFrameHost* render_frame_host) {
  content::RenderViewHost* render_view_host =
      render_frame_host->GetRenderViewHost();
  DVLOG(2) << "DocumentLoadedInFrame("
           << "render_view_host=" << render_view_host
           << ", frame_num=" << render_frame_host->GetRoutingID() << ")";
  if (render_view_host != render_view_host_)
    return;
  if (!navigation_state_.CanSendEvents(render_frame_host))
    return;
  navigation_state_.SetParsingFinished(render_frame_host);
  helpers::DispatchOnDOMContentLoaded(
      web_contents(),
      render_frame_host,
      navigation_state_.GetUrl(render_frame_host));

  if (!navigation_state_.GetNavigationCompleted(render_frame_host))
    return;

  // The load might already have finished by the time we finished parsing. For
  // compatibility reasons, we artifically delay the load completed signal until
  // after parsing was completed.
  helpers::DispatchOnCompleted(web_contents(),
                               render_frame_host,
                               navigation_state_.GetUrl(render_frame_host));
}

void WebNavigationTabObserver::DidFinishLoad(
    content::RenderFrameHost* render_frame_host,
    const GURL& validated_url) {
  content::RenderViewHost* render_view_host =
      render_frame_host->GetRenderViewHost();
  DVLOG(2) << "DidFinishLoad("
           << "render_view_host=" << render_view_host
           << ", frame_num=" << render_frame_host->GetRoutingID()
           << ", url=" << validated_url << ")";
  if (render_view_host != render_view_host_)
    return;
  // When showing replacement content, we might get load signals for frames
  // that weren't reguarly loaded.
  if (!navigation_state_.IsValidFrame(render_frame_host))
    return;
  navigation_state_.SetNavigationCompleted(render_frame_host);
  if (!navigation_state_.CanSendEvents(render_frame_host))
    return;
  DCHECK(navigation_state_.GetUrl(render_frame_host) == validated_url ||
         (navigation_state_.GetUrl(render_frame_host) ==
              GURL(content::kAboutSrcDocURL) &&
          validated_url == GURL(url::kAboutBlankURL)))
      << "validated URL is " << validated_url << " but we expected "
      << navigation_state_.GetUrl(render_frame_host);

  // The load might already have finished by the time we finished parsing. For
  // compatibility reasons, we artifically delay the load completed signal until
  // after parsing was completed.
  if (!navigation_state_.GetParsingFinished(render_frame_host))
    return;
  helpers::DispatchOnCompleted(web_contents(),
                               render_frame_host,
                               navigation_state_.GetUrl(render_frame_host));
}

void WebNavigationTabObserver::DidFailLoad(
    content::RenderFrameHost* render_frame_host,
    const GURL& validated_url,
    int error_code,
    const base::string16& error_description) {
  content::RenderViewHost* render_view_host =
      render_frame_host->GetRenderViewHost();
  DVLOG(2) << "DidFailLoad("
           << "render_view_host=" << render_view_host
           << ", frame_num=" << render_frame_host->GetRoutingID()
           << ", url=" << validated_url << ")";
  if (render_view_host != render_view_host_)
    return;
  // When showing replacement content, we might get load signals for frames
  // that weren't reguarly loaded.
  if (!navigation_state_.IsValidFrame(render_frame_host))
    return;
  if (navigation_state_.CanSendEvents(render_frame_host)) {
    helpers::DispatchOnErrorOccurred(
        web_contents(),
        render_frame_host,
        navigation_state_.GetUrl(render_frame_host),
        error_code);
  }
  navigation_state_.SetErrorOccurredInFrame(render_frame_host);
}

void WebNavigationTabObserver::DidGetRedirectForResourceRequest(
    content::RenderViewHost* render_view_host,
    const content::ResourceRedirectDetails& details) {
  if (details.resource_type != content::RESOURCE_TYPE_MAIN_FRAME &&
      details.resource_type != content::RESOURCE_TYPE_SUB_FRAME) {
    return;
  }
  content::RenderFrameHost* render_frame_host =
      content::RenderFrameHost::FromID(render_view_host->GetProcess()->GetID(),
                                       details.render_frame_id);
  navigation_state_.SetIsServerRedirected(render_frame_host);
}

void WebNavigationTabObserver::DidOpenRequestedURL(
    content::WebContents* new_contents,
    const GURL& url,
    const content::Referrer& referrer,
    WindowOpenDisposition disposition,
    ui::PageTransition transition,
    int64 source_frame_num) {
  content::RenderFrameHost* render_frame_host =
      content::RenderFrameHost::FromID(render_view_host_->GetProcess()->GetID(),
                                       source_frame_num);
  if (!navigation_state_.CanSendEvents(render_frame_host))
    return;

  // We only send the onCreatedNavigationTarget if we end up creating a new
  // window.
  if (disposition != SINGLETON_TAB &&
      disposition != NEW_FOREGROUND_TAB &&
      disposition != NEW_BACKGROUND_TAB &&
      disposition != NEW_POPUP &&
      disposition != NEW_WINDOW &&
      disposition != OFF_THE_RECORD)
    return;

  helpers::DispatchOnCreatedNavigationTarget(web_contents(),
                                             new_contents->GetBrowserContext(),
                                             render_frame_host,
                                             new_contents,
                                             url);
}

void WebNavigationTabObserver::WebContentsDestroyed() {
  g_tab_observer.Get().erase(web_contents());
  registrar_.RemoveAll();
  SendErrorEvents(web_contents(), NULL, NULL);
}

void WebNavigationTabObserver::SendErrorEvents(
    content::WebContents* web_contents,
    content::RenderViewHost* render_view_host,
    content::RenderFrameHost* frame_host_to_skip) {
  for (FrameNavigationState::const_iterator it = navigation_state_.begin();
       it != navigation_state_.end();
       ++it) {
    if (!navigation_state_.GetNavigationCompleted(*it) &&
        navigation_state_.CanSendEvents(*it) && *it != frame_host_to_skip &&
        (!render_view_host || (*it)->GetRenderViewHost() == render_view_host)) {
      navigation_state_.SetErrorOccurredInFrame(*it);
      helpers::DispatchOnErrorOccurred(
          web_contents, *it, navigation_state_.GetUrl(*it), net::ERR_ABORTED);
    }
  }
  if (render_view_host)
    navigation_state_.StopTrackingFramesInRVH(render_view_host,
                                              frame_host_to_skip);
}

// See also NavigationController::IsURLInPageNavigation.
bool WebNavigationTabObserver::IsReferenceFragmentNavigation(
    content::RenderFrameHost* render_frame_host,
    const GURL& url) {
  GURL existing_url = navigation_state_.GetUrl(render_frame_host);
  if (existing_url == url)
    return false;

  url::Replacements<char> replacements;
  replacements.ClearRef();
  return existing_url.ReplaceComponents(replacements) ==
      url.ReplaceComponents(replacements);
}

bool WebNavigationGetFrameFunction::RunSync() {
  scoped_ptr<GetFrame::Params> params(GetFrame::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  int tab_id = params->details.tab_id;
  int frame_id = params->details.frame_id;
  int process_id = params->details.process_id;

  SetResult(base::Value::CreateNullValue());

  content::WebContents* web_contents;
  if (!ExtensionTabUtil::GetTabById(tab_id,
                                    GetProfile(),
                                    include_incognito(),
                                    NULL,
                                    NULL,
                                    &web_contents,
                                    NULL) ||
      !web_contents) {
    return true;
  }

  WebNavigationTabObserver* observer =
      WebNavigationTabObserver::Get(web_contents);
  DCHECK(observer);

  const FrameNavigationState& frame_navigation_state =
      observer->frame_navigation_state();

  content::RenderFrameHost* render_frame_host =
      frame_id == 0 ? frame_navigation_state.GetLastCommittedMainFrameHost()
                    : content::RenderFrameHost::FromID(process_id, frame_id);
  if (!frame_navigation_state.IsValidFrame(render_frame_host))
    return true;

  GURL frame_url = frame_navigation_state.GetUrl(render_frame_host);
  if (!frame_navigation_state.IsValidUrl(frame_url))
    return true;

  GetFrame::Results::Details frame_details;
  frame_details.url = frame_url.spec();
  frame_details.error_occurred =
      frame_navigation_state.GetErrorOccurredInFrame(render_frame_host);
  frame_details.parent_frame_id =
      helpers::GetFrameId(render_frame_host->GetParent());
  results_ = GetFrame::Results::Create(frame_details);
  return true;
}

bool WebNavigationGetAllFramesFunction::RunSync() {
  scoped_ptr<GetAllFrames::Params> params(GetAllFrames::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  int tab_id = params->details.tab_id;

  SetResult(base::Value::CreateNullValue());

  content::WebContents* web_contents;
  if (!ExtensionTabUtil::GetTabById(tab_id,
                                    GetProfile(),
                                    include_incognito(),
                                    NULL,
                                    NULL,
                                    &web_contents,
                                    NULL) ||
      !web_contents) {
    return true;
  }

  WebNavigationTabObserver* observer =
      WebNavigationTabObserver::Get(web_contents);
  DCHECK(observer);

  const FrameNavigationState& navigation_state =
      observer->frame_navigation_state();

  std::vector<linked_ptr<GetAllFrames::Results::DetailsType> > result_list;
  for (FrameNavigationState::const_iterator it = navigation_state.begin();
       it != navigation_state.end(); ++it) {
    GURL frame_url = navigation_state.GetUrl(*it);
    if (!navigation_state.IsValidUrl(frame_url))
      continue;
    linked_ptr<GetAllFrames::Results::DetailsType> frame(
        new GetAllFrames::Results::DetailsType());
    frame->url = frame_url.spec();
    frame->frame_id = helpers::GetFrameId(*it);
    frame->parent_frame_id = helpers::GetFrameId((*it)->GetParent());
    frame->process_id = (*it)->GetRenderViewHost()->GetProcess()->GetID();
    frame->error_occurred = navigation_state.GetErrorOccurredInFrame(*it);
    result_list.push_back(frame);
  }
  results_ = GetAllFrames::Results::Create(result_list);
  return true;
}

WebNavigationAPI::WebNavigationAPI(content::BrowserContext* context)
    : browser_context_(context) {
  EventRouter* event_router = EventRouter::Get(browser_context_);
  event_router->RegisterObserver(this,
                                 web_navigation::OnBeforeNavigate::kEventName);
  event_router->RegisterObserver(this, web_navigation::OnCommitted::kEventName);
  event_router->RegisterObserver(this, web_navigation::OnCompleted::kEventName);
  event_router->RegisterObserver(
      this, web_navigation::OnCreatedNavigationTarget::kEventName);
  event_router->RegisterObserver(
      this, web_navigation::OnDOMContentLoaded::kEventName);
  event_router->RegisterObserver(
      this, web_navigation::OnHistoryStateUpdated::kEventName);
  event_router->RegisterObserver(this,
                                 web_navigation::OnErrorOccurred::kEventName);
  event_router->RegisterObserver(
      this, web_navigation::OnReferenceFragmentUpdated::kEventName);
  event_router->RegisterObserver(this,
                                 web_navigation::OnTabReplaced::kEventName);
}

WebNavigationAPI::~WebNavigationAPI() {
}

void WebNavigationAPI::Shutdown() {
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<WebNavigationAPI> >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<WebNavigationAPI>*
WebNavigationAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

void WebNavigationAPI::OnListenerAdded(const EventListenerInfo& details) {
  web_navigation_event_router_.reset(new WebNavigationEventRouter(
      Profile::FromBrowserContext(browser_context_)));
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

}  // namespace extensions