blob: fed086a56166158e508efca53adcbc8c946b2b17 [file] [log] [blame]
// 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/chromeos/wm_overview_controller.h"
#include <algorithm>
#include <vector>
#include "base/memory/linked_ptr.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/wm_ipc.h"
#include "chrome/browser/chromeos/wm_overview_favicon.h"
#include "chrome/browser/chromeos/wm_overview_snapshot.h"
#include "chrome/browser/chromeos/wm_overview_title.h"
#include "chrome/browser/tab_contents/thumbnail_generator.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/browser/ui/views/frame/browser_view.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/renderer_host/render_widget_host.h"
#include "content/browser/renderer_host/render_widget_host_view.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_view.h"
#include "content/common/notification_service.h"
#include "views/widget/root_view.h"
#include "views/widget/widget_gtk.h"
#include "views/window/window.h"
using std::vector;
#if !defined(OS_CHROMEOS)
#error This file is only meant to be compiled for ChromeOS
#endif
namespace chromeos {
// Use this timer to delay consecutive snapshots during the updating process.
// We will start the timer upon successfully retrieving a new snapshot, or if
// for some reason the current snapshot is still pending (while Chrome is
// still loading the page.)
static const int kDelayTimeMs = 10;
// This is the size of the web page when we lay it out for a snapshot.
static const int kSnapshotWebPageWidth = 1024;
static const int kSnapshotWebPageHeight = 1280;
static const double kSnapshotWebPageRatio =
static_cast<double>(kSnapshotWebPageWidth) / kSnapshotWebPageHeight;
// This is the maximum percentage of the original browser client area
// that a snapshot can take up.
static const double kSnapshotMaxSizeRatio = 0.77;
// This is the height of the title in pixels.
static const int kTitleHeight = 32;
// The number of additional padding pixels to remove from the title width.
static const int kFaviconPadding = 5;
class BrowserListener : public TabStripModelObserver {
public:
BrowserListener(Browser* browser, WmOverviewController* parent);
~BrowserListener();
// Begin TabStripModelObserver methods
virtual void TabInsertedAt(TabContentsWrapper* contents,
int index,
bool foreground);
virtual void TabClosingAt(TabStripModel* tab_strip_model,
TabContentsWrapper* contents,
int index) {}
virtual void TabDetachedAt(TabContentsWrapper* contents, int index);
virtual void TabMoved(TabContentsWrapper* contents,
int from_index,
int to_index);
virtual void TabChangedAt(TabContentsWrapper* contents, int index,
TabStripModelObserver::TabChangeType change_type);
virtual void TabStripEmpty();
virtual void TabDeselected(TabContentsWrapper* contents) {}
virtual void TabSelectedAt(TabContentsWrapper* old_contents,
TabContentsWrapper* new_contents,
int index,
bool user_gesture);
// End TabStripModelObserver methods
// Returns the number of tabs in this child.
int count() const { return browser_->tabstrip_model()->count(); }
// Returns the browser that this child gets its data from.
Browser* browser() const { return browser_; }
// Removes all the snapshots and re-populates them from the browser.
void RecreateSnapshots();
// Mark the given snapshot as dirty, and start the delay timer.
void MarkSnapshotAsDirty(int index);
// Updates the selected index and tab count on the toplevel window.
void UpdateSelectedIndex(int index);
// Update the first "dirty" snapshot, which is ordered after (excluding)
// the snapshot whose index is given by |start_from|; When |start_from| is
// -1, search start at the begining of the list.
// Return the index of the snapshot which is actually updated; -1 if there
// are no more tab contents (after |start_from|) to configure on this
// listener.
int ConfigureNextUnconfiguredSnapshot(int start_from);
// Saves the currently selected tab.
void SaveCurrentTab() { original_selected_tab_ = browser_->active_index(); }
// Reverts the selected browser tab to the tab that was selected
// when This BrowserListener was created, or the last time
// SaveCurrentTab was called.
void RestoreOriginalSelectedTab();
// Selects the tab at the given index.
void SelectTab(int index, uint32 timestamp);
// Shows any snapshots that are not visible.
void ShowSnapshots();
// Callback for |AskForSnapshot|, start delay timer for next round.
void OnSnapshotReady(const SkBitmap& sk_bitmap);
// Returns the tab contents from the tab model for this child at index.
TabContents* GetTabContentsAt(int index) const {
return browser_->tabstrip_model()->GetTabContentsAt(index)->tab_contents();
}
private:
// Calculate the size of a cell based on the browser window's size.
gfx::Size CalculateCellSize();
// Configures a cell from the tab contents.
void ConfigureCell(WmOverviewSnapshot* cell, TabContents* contents);
// Configures a cell from the model.
void ConfigureCell(WmOverviewSnapshot* cell, int index) {
ConfigureCell(cell, GetTabContentsAt(index));
}
// Inserts a new snapshot, initialized from the model, at the given
// index, and renumbers any following snapshots.
void InsertSnapshot(int index);
// Removes the snapshot at index.
void ClearSnapshot(int index);
// Renumbers the index atom in the snapshots starting at the given
// index.
void RenumberSnapshots(int start_index);
Browser* browser_; // Not owned
WmOverviewController* controller_; // Not owned
// Which renderer host we are working on.
RenderWidgetHost* current_renderer_host_; // Not owned
// Widgets containing snapshot images for this browser. Note that
// these are all subclasses of WidgetGtk, and they are all added to
// parents, so they will be deleted by the parents when they are
// closed.
struct SnapshotNode {
WmOverviewSnapshot* snapshot; // Not owned
WmOverviewTitle* title; // Not owned
WmOverviewFavicon* favicon; // Not owned
};
typedef std::vector<SnapshotNode> SnapshotVector;
SnapshotVector snapshots_;
// Non-zero if we are currently setting the tab from within SelectTab.
// This is used to make sure we use the right timestamp when sending
// property changes that originated from the window manager.
uint32 select_tab_timestamp_;
// The tab selected the last time SaveCurrentTab is called.
int original_selected_tab_;
DISALLOW_COPY_AND_ASSIGN(BrowserListener);
};
BrowserListener::BrowserListener(Browser* browser,
WmOverviewController* controller)
: browser_(browser),
controller_(controller),
current_renderer_host_(NULL),
select_tab_timestamp_(0),
original_selected_tab_(-1) {
CHECK(browser_);
CHECK(controller_);
browser_->tabstrip_model()->AddObserver(this);
// This browser didn't already exist, and so we haven't been
// watching it for tab insertions, so we need to create the
// snapshots associated with it.
RecreateSnapshots();
}
BrowserListener::~BrowserListener() {
browser_->tabstrip_model()->RemoveObserver(this);
}
void BrowserListener::TabInsertedAt(TabContentsWrapper* contents,
int index,
bool foreground) {
InsertSnapshot(index);
RenumberSnapshots(index);
UpdateSelectedIndex(browser_->active_index());
}
void BrowserListener::TabDetachedAt(TabContentsWrapper* contents, int index) {
ClearSnapshot(index);
UpdateSelectedIndex(browser_->active_index());
RenumberSnapshots(index);
}
void BrowserListener::TabMoved(TabContentsWrapper* contents,
int from_index,
int to_index) {
// Need to reorder tab in the snapshots list, and reset the window
// type atom on the affected snapshots (the one moved, and all the
// ones after it), so that their indices are correct.
SnapshotNode node = snapshots_[from_index];
snapshots_.erase(snapshots_.begin() + from_index);
snapshots_.insert(snapshots_.begin() + to_index, node);
RenumberSnapshots(std::min(to_index, from_index));
UpdateSelectedIndex(browser_->active_index());
}
void BrowserListener::TabChangedAt(
TabContentsWrapper* contents,
int index,
TabStripModelObserver::TabChangeType change_type) {
if (change_type != TabStripModelObserver::LOADING_ONLY) {
snapshots_[index].title->SetTitle(contents->tab_contents()->GetTitle());
snapshots_[index].title->SetUrl(contents->tab_contents()->GetURL());
snapshots_[index].favicon->SetFavicon(
contents->tab_contents()->GetFavicon());
if (change_type != TabStripModelObserver::TITLE_NOT_LOADING)
MarkSnapshotAsDirty(index);
}
}
void BrowserListener::TabStripEmpty() {
snapshots_.clear();
}
void BrowserListener::TabSelectedAt(TabContentsWrapper* old_contents,
TabContentsWrapper* new_contents,
int index,
bool user_gesture) {
if (old_contents == new_contents)
return;
UpdateSelectedIndex(index);
}
void BrowserListener::MarkSnapshotAsDirty(int index) {
snapshots_[index].snapshot->reload_snapshot();
controller_->UpdateSnapshots();
}
void BrowserListener::RecreateSnapshots() {
snapshots_.clear();
for (int i = 0; i < count(); ++i)
InsertSnapshot(i);
RenumberSnapshots(0);
}
void BrowserListener::UpdateSelectedIndex(int index) {
WmIpcWindowType type = WmIpc::instance()->GetWindowType(
GTK_WIDGET(browser_->window()->GetNativeHandle()), NULL);
// Make sure we only operate on toplevel windows.
if (type == WM_IPC_WINDOW_CHROME_TOPLEVEL) {
std::vector<int> params;
params.push_back(browser_->tab_count());
params.push_back(index);
params.push_back(select_tab_timestamp_ ? select_tab_timestamp_ :
gtk_get_current_event_time());
WmIpc::instance()->SetWindowType(
GTK_WIDGET(browser_->window()->GetNativeHandle()),
WM_IPC_WINDOW_CHROME_TOPLEVEL,
&params);
}
}
int BrowserListener::ConfigureNextUnconfiguredSnapshot(int start_from) {
for (SnapshotVector::size_type i = start_from + 1;
i < snapshots_.size(); ++i) {
WmOverviewSnapshot* cell = snapshots_[i].snapshot;
if (!cell->configured_snapshot()) {
ConfigureCell(cell, i);
return i;
}
}
return -1;
}
void BrowserListener::RestoreOriginalSelectedTab() {
if (original_selected_tab_ >= 0) {
browser_->ActivateTabAt(original_selected_tab_, false);
UpdateSelectedIndex(browser_->active_index());
}
}
void BrowserListener::ShowSnapshots() {
for (SnapshotVector::size_type i = 0; i < snapshots_.size(); ++i) {
const SnapshotNode& node = snapshots_[i];
if (!node.snapshot->IsVisible())
node.snapshot->Show();
if (!snapshots_[i].title->IsVisible())
node.title->Show();
if (!snapshots_[i].favicon->IsVisible())
node.favicon->Show();
}
}
void BrowserListener::SelectTab(int index, uint32 timestamp) {
// Ignore requests to switch to non-existent tabs (the window manager gets
// notified asynchronously about the number of tabs in each window, so there's
// no guarantee that the messages that it sends us will make sense).
if (index < 0 || index >= browser_->tab_count())
return;
uint32 old_value = select_tab_timestamp_;
select_tab_timestamp_ = timestamp;
browser_->ActivateTabAt(index, true);
select_tab_timestamp_ = old_value;
}
gfx::Size BrowserListener::CalculateCellSize() {
// Make the page size and the cell size a fixed size for overview
// mode. The cell size is calculated based on the desired maximum
// size on the screen, so it's related to the width and height of
// the browser client area. In this way, when this snapshot gets
// to the window manager, it will already have the correct size,
// and will be scaled by 1.0, meaning that it won't be resampled
// and will not be blurry.
gfx::Rect bounds = static_cast<BrowserView*>(browser_->window())->
GetClientAreaBounds();
const gfx::Size max_size = gfx::Size(
bounds.width() * kSnapshotMaxSizeRatio,
bounds.height() * kSnapshotMaxSizeRatio);
const double max_size_ratio = static_cast<double>(max_size.width()) /
max_size.height();
gfx::Size cell_size;
if (kSnapshotWebPageRatio > max_size_ratio) {
const double scale_factor =
static_cast<double>(max_size.width())/ kSnapshotWebPageWidth;
cell_size = gfx::Size(max_size.width(),
kSnapshotWebPageHeight * scale_factor + 0.5);
} else {
const double scale_factor =
static_cast<double>(max_size.height())/ kSnapshotWebPageHeight;
cell_size = gfx::Size(kSnapshotWebPageWidth * scale_factor + 0.5,
max_size.height());
}
return cell_size;
}
void BrowserListener::OnSnapshotReady(const SkBitmap& sk_bitmap) {
for (int i = 0; i < count(); i++) {
RenderWidgetHostView* view =
GetTabContentsAt(i)->GetRenderWidgetHostView();
if (view && view->GetRenderWidgetHost() == current_renderer_host_) {
snapshots_[i].snapshot->SetImage(sk_bitmap);
current_renderer_host_ = NULL;
// Start timer for next round of snapshot updating.
controller_->StartDelayTimer();
return;
}
}
DCHECK(current_renderer_host_ == NULL);
}
void BrowserListener::ConfigureCell(WmOverviewSnapshot* cell,
TabContents* contents) {
if (contents) {
ThumbnailGenerator* generator =
g_browser_process->GetThumbnailGenerator();
// TODO: Make sure that if the cell gets deleted before the
// callback is called that it sticks around until it gets
// called. (some kind of "in flight" list that uses linked_ptr
// to make sure they don't actually get deleted?) Also, make
// sure that any request for a thumbnail eventually returns
// (even if it has bogus data), so we don't leak orphaned cells,
// which could happen if a tab is closed while it is being
// rendered.
ThumbnailGenerator::ThumbnailReadyCallback* callback =
NewCallback(this, &BrowserListener::OnSnapshotReady);
current_renderer_host_ = contents->render_view_host();
generator->AskForSnapshot(contents->render_view_host(),
false,
callback,
gfx::Size(kSnapshotWebPageWidth,
kSnapshotWebPageHeight),
CalculateCellSize());
} else {
// This happens because the contents haven't been loaded yet.
// Make sure we set the snapshot image to something, otherwise
// configured_snapshot remains false and
// ConfigureNextUnconfiguredSnapshot would get stuck.
current_renderer_host_ = NULL;
cell->SetImage(SkBitmap());
cell->reload_snapshot();
controller_->StartDelayTimer();
}
}
void BrowserListener::InsertSnapshot(int index) {
SnapshotNode node;
node.snapshot = new WmOverviewSnapshot;
gfx::Size cell_size = CalculateCellSize();
node.snapshot->Init(cell_size, browser_, index);
node.favicon = new WmOverviewFavicon;
node.favicon->Init(node.snapshot);
node.favicon->SetFavicon(browser_->GetTabContentsAt(index)->GetFavicon());
node.title = new WmOverviewTitle;
node.title->Init(gfx::Size(std::max(0, cell_size.width() -
WmOverviewFavicon::kIconSize -
kFaviconPadding),
kTitleHeight), node.snapshot);
node.title->SetTitle(browser_->GetTabContentsAt(index)->GetTitle());
snapshots_.insert(snapshots_.begin() + index, node);
node.snapshot->reload_snapshot();
controller_->UpdateSnapshots();
}
// Removes the snapshot at index.
void BrowserListener::ClearSnapshot(int index) {
snapshots_[index].snapshot->CloseNow();
snapshots_[index].title->CloseNow();
snapshots_[index].favicon->CloseNow();
snapshots_.erase(snapshots_.begin() + index);
}
void BrowserListener::RenumberSnapshots(int start_index) {
for (SnapshotVector::size_type i = start_index; i < snapshots_.size(); ++i) {
if (snapshots_[i].snapshot->index() != static_cast<int>(i))
snapshots_[i].snapshot->UpdateIndex(browser_, i);
}
}
///////////////////////////////////
// WmOverviewController methods
// static
WmOverviewController* WmOverviewController::GetInstance() {
static WmOverviewController* instance = NULL;
if (!instance) {
instance = Singleton<WmOverviewController>::get();
}
return instance;
}
WmOverviewController::WmOverviewController()
: layout_mode_(ACTIVE_MODE),
updating_snapshots_(false),
browser_listener_index_(0),
tab_contents_index_(-1) {
AddAllBrowsers();
if (registrar_.IsEmpty()) {
// Make sure we get notifications for when the tab contents are
// connected, so we know when a new browser has been created.
registrar_.Add(this,
NotificationType::TAB_CONTENTS_CONNECTED,
NotificationService::AllSources());
// Ask for notification when the snapshot source image has changed
// and needs to be refreshed.
registrar_.Add(this,
NotificationType::THUMBNAIL_GENERATOR_SNAPSHOT_CHANGED,
NotificationService::AllSources());
}
BrowserList::AddObserver(this);
WmMessageListener::GetInstance()->AddObserver(this);
}
WmOverviewController::~WmOverviewController() {
WmMessageListener::GetInstance()->RemoveObserver(this);
BrowserList::RemoveObserver(this);
listeners_.clear();
}
void WmOverviewController::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
// Now that the tab contents are ready, we create the listeners
// and snapshots for any new browsers out there. This actually
// results in us traversing the list of browsers more often than
// necessary (whenever a tab is connected, as opposed to only when
// a new browser is created), but other notifications aren't
// sufficient to know when the first tab of a new browser has its
// dimensions set. The implementation of AddAllBrowsers avoids
// doing anything to already-existing browsers, so it's not a huge
// problem, but still, it would be nice if there were a more
// appropriate (browser-level) notification.
case NotificationType::TAB_CONTENTS_CONNECTED:
AddAllBrowsers();
break;
case NotificationType::THUMBNAIL_GENERATOR_SNAPSHOT_CHANGED: {
// It's OK to do this in active mode too -- nothing will happen
// except invalidation of the snapshot, because the delay timer
// won't start until we're in overview mode.
RenderWidgetHost* renderer = Details<RenderViewHost>(details).ptr();
SnapshotImageChanged(renderer);
break;
}
default:
// Do nothing.
break;
}
}
void WmOverviewController::SnapshotImageChanged(RenderWidgetHost* renderer) {
// Find out which TabContents this renderer is attached to, and then
// invalidate the associated snapshot so it'll update.
BrowserListenerVector::iterator iter = listeners_.begin();
while (iter != listeners_.end()) {
for (int i = 0; i < (*iter)->count(); i++) {
RenderWidgetHostView* view =
(*iter)->GetTabContentsAt(i)->GetRenderWidgetHostView();
if (view && view->GetRenderWidgetHost() == renderer) {
(*iter)->MarkSnapshotAsDirty(i);
return;
}
}
++iter;
}
DLOG(ERROR) << "SnapshotImageChanged, but we do not know which it is?";
}
void WmOverviewController::OnBrowserRemoved(const Browser* browser) {
for (BrowserListenerVector::iterator i = listeners_.begin();
i != listeners_.end(); ++i) {
if ((*i)->browser() == browser) {
listeners_.erase(i);
return;
}
}
}
BrowserView* GetBrowserViewForGdkWindow(GdkWindow* gdk_window) {
gpointer data = NULL;
gdk_window_get_user_data(gdk_window, &data);
GtkWidget* widget = reinterpret_cast<GtkWidget*>(data);
if (widget) {
GtkWindow* gtk_window = GTK_WINDOW(widget);
return BrowserView::GetBrowserViewForNativeWindow(gtk_window);
} else {
return NULL;
}
}
void WmOverviewController::ProcessWmMessage(const WmIpc::Message& message,
GdkWindow* window) {
switch (message.type()) {
case WM_IPC_MESSAGE_CHROME_NOTIFY_LAYOUT_MODE: {
layout_mode_ = message.param(0) == 0 ? ACTIVE_MODE : OVERVIEW_MODE;
if (layout_mode_ == ACTIVE_MODE || BrowserList::size() == 0) {
Hide(message.param(1) != 0);
} else {
Show();
}
break;
}
case WM_IPC_MESSAGE_CHROME_NOTIFY_TAB_SELECT: {
BrowserView* browser_window = GetBrowserViewForGdkWindow(window);
// Find out which listener this goes to, and send it there.
for (BrowserListenerVector::iterator i = listeners_.begin();
i != listeners_.end(); ++i) {
if ((*i)->browser()->window() == browser_window) {
// param(0): index of the tab to select.
// param(1): timestamp of the event.
(*i)->SelectTab(message.param(0), message.param(1));
break;
}
}
break;
}
default:
break;
}
}
void WmOverviewController::StartDelayTimer() {
// We're rate limiting the number of times we can reconfigure the
// snapshots. If we were to restart the delay timer, it could
// result in a very long delay until they get configured if tabs
// keep changing.
updating_snapshots_ = false;
if (layout_mode_ == OVERVIEW_MODE) {
delay_timer_.Start(
base::TimeDelta::FromMilliseconds(kDelayTimeMs), this,
&WmOverviewController::UpdateSnapshots);
}
}
void WmOverviewController::RestoreTabSelections() {
for (BrowserListenerVector::iterator i = listeners_.begin();
i != listeners_.end(); ++i) {
(*i)->RestoreOriginalSelectedTab();
}
}
void WmOverviewController::SaveTabSelections() {
for (BrowserListenerVector::iterator i = listeners_.begin();
i != listeners_.end(); ++i) {
(*i)->SaveCurrentTab();
}
}
void WmOverviewController::Show() {
SaveTabSelections();
for (BrowserListenerVector::iterator i = listeners_.begin();
i != listeners_.end(); ++i) {
(*i)->ShowSnapshots();
}
// TODO(jiesun): Make the focused tab as the start point.
browser_listener_index_ = 0;
tab_contents_index_ = -1;
UpdateSnapshots();
}
void WmOverviewController::Hide(bool cancelled) {
delay_timer_.Stop();
updating_snapshots_ = false;
if (cancelled) {
RestoreTabSelections();
}
}
void WmOverviewController::UpdateSnapshots() {
// Only updating snapshots during overview mode.
if (layout_mode_ != OVERVIEW_MODE)
return;
// Only reloading snapshots when not already started.
if (updating_snapshots_ || delay_timer_.IsRunning())
return;
// Only update one snapshot in round-robin mode.
// Start delay timer after each update unless all snapshots had been updated.
if (!listeners_.size())
return;
if (int(listeners_.size()) <= browser_listener_index_) {
DLOG(INFO) << "Browser listener(s) have disappeared since last update";
browser_listener_index_ = 0;
tab_contents_index_ = -1;
} else {
BrowserListener* listener = listeners_[browser_listener_index_].get();
if (listener->count() <= tab_contents_index_) {
DLOG(INFO) << "Tab content(s) have disappeared since last update";
tab_contents_index_ = -1;
}
}
int browser_listener_index = browser_listener_index_;
int tab_contents_index = tab_contents_index_;
bool loop_back = false;
while (1) {
BrowserListener* listener = listeners_[browser_listener_index].get();
tab_contents_index =
listener->ConfigureNextUnconfiguredSnapshot(tab_contents_index);
if (tab_contents_index >= 0) {
updating_snapshots_ = true; // Prevent future parallel updates.
browser_listener_index_ = browser_listener_index;
tab_contents_index_ = tab_contents_index;
return;
}
// Found next one;
browser_listener_index++;
browser_listener_index = browser_listener_index % listeners_.size();
tab_contents_index = -1;
if (loop_back)
break;
loop_back = browser_listener_index == browser_listener_index_;
}
// All snapshots have been fully updated.
updating_snapshots_ = false;
}
void WmOverviewController::AddAllBrowsers() {
// Make a copy so the old ones aren't deleted yet.
BrowserListenerVector old_listeners;
listeners_.swap(old_listeners);
// Iterator through the browser list, adding all the browsers in the
// new order. If they were in the old list of listeners, just copy
// that linked pointer, instead of making a new listener, so that we
// can avoid lots of spurious destroy/create messages.
BrowserList::const_iterator iterator = BrowserList::begin();
while (iterator != BrowserList::end()) {
// Don't add a browser to the list if that type of browser doesn't
// have snapshots in overview mode.
if ((*iterator)->type() != Browser::TYPE_NORMAL &&
(*iterator)->type() != Browser::TYPE_APP) {
++iterator;
continue;
}
BrowserListenerVector::value_type item(
BrowserListenerVector::value_type(NULL));
for (BrowserListenerVector::iterator old_iter = old_listeners.begin();
old_iter != old_listeners.end(); ++old_iter) {
if ((*old_iter)->browser() == *iterator) {
item = *old_iter;
break;
}
}
// This browser isn't tracked by any listener, so create it.
if (item.get() == NULL) {
item = BrowserListenerVector::value_type(
new BrowserListener(*iterator, this));
}
listeners_.push_back(item);
++iterator;
}
}
} // namespace chromeos