普通文本  |  549行  |  19.66 KB

// Copyright (c) 2011 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.

#include "chrome/browser/extensions/extension_browser_event_router.h"

#include "base/json/json_writer.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_event_names.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_page_actions_module_constants.h"
#include "chrome/browser/extensions/extension_tabs_module_constants.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_service.h"

namespace events = extension_event_names;
namespace tab_keys = extension_tabs_module_constants;
namespace page_action_keys = extension_page_actions_module_constants;

ExtensionBrowserEventRouter::TabEntry::TabEntry()
    : complete_waiting_on_load_(false),
      url_() {
}

DictionaryValue* ExtensionBrowserEventRouter::TabEntry::UpdateLoadState(
    const TabContents* contents) {
  // The tab may go in & out of loading (for instance if iframes navigate).
  // We only want to respond to the first change from loading to !loading after
  // the NAV_ENTRY_COMMITTED was fired.
  if (!complete_waiting_on_load_ || contents->is_loading())
    return NULL;

  // Send "complete" state change.
  complete_waiting_on_load_ = false;
  DictionaryValue* changed_properties = new DictionaryValue();
  changed_properties->SetString(tab_keys::kStatusKey,
      tab_keys::kStatusValueComplete);
  return changed_properties;
}

DictionaryValue* ExtensionBrowserEventRouter::TabEntry::DidNavigate(
    const TabContents* contents) {
  // Send "loading" state change.
  complete_waiting_on_load_ = true;
  DictionaryValue* changed_properties = new DictionaryValue();
  changed_properties->SetString(tab_keys::kStatusKey,
      tab_keys::kStatusValueLoading);

  if (contents->GetURL() != url_) {
    url_ = contents->GetURL();
    changed_properties->SetString(tab_keys::kUrlKey, url_.spec());
  }

  return changed_properties;
}

static void DispatchEvent(Profile* profile,
                          const char* event_name,
                          const std::string& json_args) {
  if (profile->GetExtensionEventRouter()) {
    profile->GetExtensionEventRouter()->DispatchEventToRenderers(
        event_name, json_args, profile, GURL());
  }
}

static void DispatchEventToExtension(Profile* profile,
                                     const std::string& extension_id,
                                     const char* event_name,
                                     const std::string& json_args) {
  if (profile->GetExtensionEventRouter()) {
    profile->GetExtensionEventRouter()->DispatchEventToExtension(
        extension_id, event_name, json_args, profile, GURL());
  }
}

static void DispatchEventWithTab(Profile* profile,
                                 const std::string& extension_id,
                                 const char* event_name,
                                 const TabContents* tab_contents) {
  ListValue args;
  args.Append(ExtensionTabUtil::CreateTabValue(tab_contents));
  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);
  if (!extension_id.empty()) {
    DispatchEventToExtension(profile, extension_id, event_name, json_args);
  } else {
    DispatchEvent(profile, event_name, json_args);
  }
}

static void DispatchSimpleBrowserEvent(Profile* profile,
                                       const int window_id,
                                       const char* event_name) {
  ListValue args;
  args.Append(Value::CreateIntegerValue(window_id));

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(profile, event_name, json_args);
}

void ExtensionBrowserEventRouter::Init() {
  if (initialized_)
    return;
  BrowserList::AddObserver(this);
#if defined(TOOLKIT_VIEWS)
  views::FocusManager::GetWidgetFocusManager()->AddFocusChangeListener(this);
#elif defined(TOOLKIT_GTK)
  ui::ActiveWindowWatcherX::AddObserver(this);
#elif defined(OS_MACOSX)
  // Needed for when no suitable window can be passed to an extension as the
  // currently focused window.
  registrar_.Add(this, NotificationType::NO_KEY_WINDOW,
                 NotificationService::AllSources());
#endif

  // Init() can happen after the browser is running, so catch up with any
  // windows that already exist.
  for (BrowserList::const_iterator iter = BrowserList::begin();
       iter != BrowserList::end(); ++iter) {
    RegisterForBrowserNotifications(*iter);

    // Also catch up our internal bookkeeping of tab entries.
    Browser* browser = *iter;
    if (browser->tabstrip_model()) {
      for (int i = 0; i < browser->tabstrip_model()->count(); ++i) {
        TabContents* contents = browser->GetTabContentsAt(i);
        int tab_id = ExtensionTabUtil::GetTabId(contents);
        tab_entries_[tab_id] = TabEntry();
      }
    }
  }

  initialized_ = true;
}

ExtensionBrowserEventRouter::ExtensionBrowserEventRouter(Profile* profile)
    : initialized_(false),
      focused_window_id_(extension_misc::kUnknownWindowId),
      profile_(profile) {
  DCHECK(!profile->IsOffTheRecord());
}

ExtensionBrowserEventRouter::~ExtensionBrowserEventRouter() {
  BrowserList::RemoveObserver(this);
#if defined(TOOLKIT_VIEWS)
  views::FocusManager::GetWidgetFocusManager()->RemoveFocusChangeListener(this);
#elif defined(TOOLKIT_GTK)
  ui::ActiveWindowWatcherX::RemoveObserver(this);
#endif
}

void ExtensionBrowserEventRouter::OnBrowserAdded(const Browser* browser) {
  RegisterForBrowserNotifications(browser);
}

void ExtensionBrowserEventRouter::RegisterForBrowserNotifications(
    const Browser* browser) {
  // Start listening to TabStripModel events for this browser.
  browser->tabstrip_model()->AddObserver(this);

  // If this is a new window, it isn't ready at this point, so we register to be
  // notified when it is. If this is an existing window, this is a no-op that we
  // just do to reduce code complexity.
  registrar_.Add(this, NotificationType::BROWSER_WINDOW_READY,
      Source<const Browser>(browser));

  if (browser->tabstrip_model()) {
    for (int i = 0; i < browser->tabstrip_model()->count(); ++i)
      RegisterForTabNotifications(browser->GetTabContentsAt(i));
  }
}

void ExtensionBrowserEventRouter::RegisterForTabNotifications(
    TabContents* contents) {
  registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
                 Source<NavigationController>(&contents->controller()));

  // Observing TAB_CONTENTS_DESTROYED is necessary because it's
  // possible for tabs to be created, detached and then destroyed without
  // ever having been re-attached and closed. This happens in the case of
  // a devtools TabContents that is opened in window, docked, then closed.
  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
                 Source<TabContents>(contents));
}

void ExtensionBrowserEventRouter::UnregisterForTabNotifications(
    TabContents* contents) {
  registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
      Source<NavigationController>(&contents->controller()));
  registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
      Source<TabContents>(contents));
}

void ExtensionBrowserEventRouter::OnBrowserWindowReady(const Browser* browser) {
  ListValue args;

  DictionaryValue* window_dictionary = ExtensionTabUtil::CreateWindowValue(
      browser, false);
  args.Append(window_dictionary);

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(browser->profile(), events::kOnWindowCreated, json_args);
}

void ExtensionBrowserEventRouter::OnBrowserRemoved(const Browser* browser) {
  // Stop listening to TabStripModel events for this browser.
  browser->tabstrip_model()->RemoveObserver(this);

  registrar_.Remove(this, NotificationType::BROWSER_WINDOW_READY,
      Source<const Browser>(browser));

  DispatchSimpleBrowserEvent(browser->profile(),
                             ExtensionTabUtil::GetWindowId(browser),
                             events::kOnWindowRemoved);
}

#if defined(TOOLKIT_VIEWS)
void ExtensionBrowserEventRouter::NativeFocusWillChange(
    gfx::NativeView focused_before,
    gfx::NativeView focused_now) {
  if (!focused_now)
    OnBrowserSetLastActive(NULL);
}
#elif defined(TOOLKIT_GTK)
void ExtensionBrowserEventRouter::ActiveWindowChanged(
    GdkWindow* active_window) {
  if (!active_window)
    OnBrowserSetLastActive(NULL);
}
#endif

void ExtensionBrowserEventRouter::OnBrowserSetLastActive(
    const Browser* browser) {
  int window_id = extension_misc::kUnknownWindowId;
  if (browser)
    window_id = ExtensionTabUtil::GetWindowId(browser);

  if (focused_window_id_ == window_id)
    return;

  focused_window_id_ = window_id;
  // Note: because we use the default profile when |browser| is NULL, it means
  // that all extensions hear about the event regardless of whether the browser
  // that lost focus was OTR or if the extension is OTR-enabled.
  // See crbug.com/46610.
  DispatchSimpleBrowserEvent(browser ? browser->profile() : profile_,
                             focused_window_id_,
                             events::kOnWindowFocusedChanged);
}

void ExtensionBrowserEventRouter::TabCreatedAt(TabContents* contents,
                                               int index,
                                               bool foreground) {
  DispatchEventWithTab(contents->profile(), "", events::kOnTabCreated,
                       contents);

  RegisterForTabNotifications(contents);
}

void ExtensionBrowserEventRouter::TabInsertedAt(TabContentsWrapper* contents,
                                                int index,
                                                bool foreground) {
  // If tab is new, send created event.
  int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents());
  if (!GetTabEntry(contents->tab_contents())) {
    tab_entries_[tab_id] = TabEntry();

    TabCreatedAt(contents->tab_contents(), index, foreground);
    return;
  }

  ListValue args;
  args.Append(Value::CreateIntegerValue(tab_id));

  DictionaryValue* object_args = new DictionaryValue();
  object_args->Set(tab_keys::kNewWindowIdKey, Value::CreateIntegerValue(
      ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
  object_args->Set(tab_keys::kNewPositionKey, Value::CreateIntegerValue(
      index));
  args.Append(object_args);

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(contents->profile(), events::kOnTabAttached, json_args);
}

void ExtensionBrowserEventRouter::TabDetachedAt(TabContentsWrapper* contents,
                                                int index) {
  if (!GetTabEntry(contents->tab_contents())) {
    // The tab was removed. Don't send detach event.
    return;
  }

  ListValue args;
  args.Append(Value::CreateIntegerValue(
      ExtensionTabUtil::GetTabId(contents->tab_contents())));

  DictionaryValue* object_args = new DictionaryValue();
  object_args->Set(tab_keys::kOldWindowIdKey, Value::CreateIntegerValue(
      ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
  object_args->Set(tab_keys::kOldPositionKey, Value::CreateIntegerValue(
      index));
  args.Append(object_args);

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(contents->profile(), events::kOnTabDetached, json_args);
}

void ExtensionBrowserEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
                                               TabContentsWrapper* contents,
                                               int index) {
  int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents());

  ListValue args;
  args.Append(Value::CreateIntegerValue(tab_id));

  DictionaryValue* object_args = new DictionaryValue();
  object_args->SetBoolean(tab_keys::kWindowClosing,
                          tab_strip_model->closing_all());
  args.Append(object_args);

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(contents->profile(), events::kOnTabRemoved, json_args);

  int removed_count = tab_entries_.erase(tab_id);
  DCHECK_GT(removed_count, 0);

  UnregisterForTabNotifications(contents->tab_contents());
}

void ExtensionBrowserEventRouter::TabSelectedAt(
    TabContentsWrapper* old_contents,
    TabContentsWrapper* new_contents,
    int index,
    bool user_gesture) {
  if (old_contents == new_contents)
    return;

  ListValue args;
  args.Append(Value::CreateIntegerValue(
      ExtensionTabUtil::GetTabId(new_contents->tab_contents())));

  DictionaryValue* object_args = new DictionaryValue();
  object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue(
      ExtensionTabUtil::GetWindowIdOfTab(new_contents->tab_contents())));
  args.Append(object_args);

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(new_contents->profile(), events::kOnTabSelectionChanged,
                json_args);
}

void ExtensionBrowserEventRouter::TabMoved(TabContentsWrapper* contents,
                                           int from_index,
                                           int to_index) {
  ListValue args;
  args.Append(Value::CreateIntegerValue(
      ExtensionTabUtil::GetTabId(contents->tab_contents())));

  DictionaryValue* object_args = new DictionaryValue();
  object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue(
      ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
  object_args->Set(tab_keys::kFromIndexKey, Value::CreateIntegerValue(
      from_index));
  object_args->Set(tab_keys::kToIndexKey, Value::CreateIntegerValue(
      to_index));
  args.Append(object_args);

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(contents->profile(), events::kOnTabMoved, json_args);
}

void ExtensionBrowserEventRouter::TabUpdated(TabContents* contents,
                                             bool did_navigate) {
  TabEntry* entry = GetTabEntry(contents);
  DictionaryValue* changed_properties = NULL;

  DCHECK(entry);

  if (did_navigate)
    changed_properties = entry->DidNavigate(contents);
  else
    changed_properties = entry->UpdateLoadState(contents);

  if (changed_properties)
    DispatchTabUpdatedEvent(contents, changed_properties);
}

void ExtensionBrowserEventRouter::DispatchTabUpdatedEvent(
    TabContents* contents, DictionaryValue* changed_properties) {
  DCHECK(changed_properties);
  DCHECK(contents);

  // The state of the tab (as seen from the extension point of view) has
  // changed.  Send a notification to the extension.
  ListValue args;

  // First arg: The id of the tab that changed.
  args.Append(Value::CreateIntegerValue(ExtensionTabUtil::GetTabId(contents)));

  // Second arg: An object containing the changes to the tab state.
  args.Append(changed_properties);

  // Third arg: An object containing the state of the tab.
  args.Append(ExtensionTabUtil::CreateTabValue(contents));

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEvent(contents->profile(), events::kOnTabUpdated, json_args);
}

ExtensionBrowserEventRouter::TabEntry* ExtensionBrowserEventRouter::GetTabEntry(
    const TabContents* contents) {
  int tab_id = ExtensionTabUtil::GetTabId(contents);
  std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id);
  if (tab_entries_.end() == i)
    return NULL;
  return &i->second;
}

void ExtensionBrowserEventRouter::Observe(NotificationType type,
                                          const NotificationSource& source,
                                          const NotificationDetails& details) {
  if (type == NotificationType::NAV_ENTRY_COMMITTED) {
    NavigationController* source_controller =
        Source<NavigationController>(source).ptr();
    TabUpdated(source_controller->tab_contents(), true);
  } else if (type == NotificationType::TAB_CONTENTS_DESTROYED) {
    // Tab was destroyed after being detached (without being re-attached).
    TabContents* contents = Source<TabContents>(source).ptr();
    registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
        Source<NavigationController>(&contents->controller()));
    registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
        Source<TabContents>(contents));
  } else if (type == NotificationType::BROWSER_WINDOW_READY) {
    const Browser* browser = Source<const Browser>(source).ptr();
    OnBrowserWindowReady(browser);
#if defined(OS_MACOSX)
  } else if (type == NotificationType::NO_KEY_WINDOW) {
    OnBrowserSetLastActive(NULL);
#endif
  } else {
    NOTREACHED();
  }
}

void ExtensionBrowserEventRouter::TabChangedAt(TabContentsWrapper* contents,
                                               int index,
                                               TabChangeType change_type) {
  TabUpdated(contents->tab_contents(), false);
}

void ExtensionBrowserEventRouter::TabReplacedAt(
    TabStripModel* tab_strip_model,
    TabContentsWrapper* old_contents,
    TabContentsWrapper* new_contents,
    int index) {
  TabClosingAt(tab_strip_model, old_contents, index);
  TabInsertedAt(new_contents, index, tab_strip_model->active_index() == index);
}

void ExtensionBrowserEventRouter::TabPinnedStateChanged(
    TabContentsWrapper* contents,
    int index) {
  TabStripModel* tab_strip = NULL;
  int tab_index;

  if (ExtensionTabUtil::GetTabStripModel(
        contents->tab_contents(), &tab_strip, &tab_index)) {
    DictionaryValue* changed_properties = new DictionaryValue();
    changed_properties->SetBoolean(tab_keys::kPinnedKey,
                                   tab_strip->IsTabPinned(tab_index));
    DispatchTabUpdatedEvent(contents->tab_contents(), changed_properties);
  }
}

void ExtensionBrowserEventRouter::TabStripEmpty() {}

void ExtensionBrowserEventRouter::DispatchOldPageActionEvent(
    Profile* profile,
    const std::string& extension_id,
    const std::string& page_action_id,
    int tab_id,
    const std::string& url,
    int button) {
  ListValue args;
  args.Append(Value::CreateStringValue(page_action_id));

  DictionaryValue* data = new DictionaryValue();
  data->Set(tab_keys::kTabIdKey, Value::CreateIntegerValue(tab_id));
  data->Set(tab_keys::kTabUrlKey, Value::CreateStringValue(url));
  data->Set(page_action_keys::kButtonKey, Value::CreateIntegerValue(button));
  args.Append(data);

  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);

  DispatchEventToExtension(profile, extension_id, "pageActions", json_args);
}

void ExtensionBrowserEventRouter::PageActionExecuted(
    Profile* profile,
    const std::string& extension_id,
    const std::string& page_action_id,
    int tab_id,
    const std::string& url,
    int button) {
  DispatchOldPageActionEvent(profile, extension_id, page_action_id, tab_id, url,
                             button);
  TabContentsWrapper* tab_contents = NULL;
  if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(),
                                    NULL, NULL, &tab_contents, NULL)) {
    return;
  }
  DispatchEventWithTab(profile, extension_id, "pageAction.onClicked",
                       tab_contents->tab_contents());
}

void ExtensionBrowserEventRouter::BrowserActionExecuted(
    Profile* profile, const std::string& extension_id, Browser* browser) {
  TabContentsWrapper* tab_contents = NULL;
  int tab_id = 0;
  if (!ExtensionTabUtil::GetDefaultTab(browser, &tab_contents, &tab_id))
    return;
  DispatchEventWithTab(profile, extension_id, "browserAction.onClicked",
                       tab_contents->tab_contents());
}