[Extensions] Make extension message bubble factory platform-abstract
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / browser_tab_strip_controller.cc
blob61797eb3ad736ae353cc6dfb9c8fa8a8854ae10e
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
7 #include "base/auto_reset.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/task_runner_util.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
12 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/extensions/tab_helper.h"
16 #include "chrome/browser/favicon/favicon_helper.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/search/search.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_tabstrip.h"
21 #include "chrome/browser/ui/tabs/tab_menu_model.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
24 #include "chrome/browser/ui/tabs/tab_utils.h"
25 #include "chrome/browser/ui/views/frame/browser_view.h"
26 #include "chrome/browser/ui/views/tabs/tab.h"
27 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
28 #include "chrome/browser/ui/views/tabs/tab_strip.h"
29 #include "chrome/common/pref_names.h"
30 #include "chrome/common/url_constants.h"
31 #include "components/favicon/content/content_favicon_driver.h"
32 #include "components/metrics/proto/omnibox_event.pb.h"
33 #include "components/omnibox/autocomplete_match.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/notification_service.h"
36 #include "content/public/browser/plugin_service.h"
37 #include "content/public/browser/user_metrics.h"
38 #include "content/public/browser/web_contents.h"
39 #include "content/public/common/webplugininfo.h"
40 #include "ipc/ipc_message.h"
41 #include "net/base/filename_util.h"
42 #include "ui/base/models/list_selection_model.h"
43 #include "ui/gfx/image/image.h"
44 #include "ui/views/controls/menu/menu_runner.h"
45 #include "ui/views/widget/widget.h"
47 using base::UserMetricsAction;
48 using content::WebContents;
50 namespace {
52 TabRendererData::NetworkState TabContentsNetworkState(
53 WebContents* contents) {
54 if (!contents || !contents->IsLoadingToDifferentDocument())
55 return TabRendererData::NETWORK_STATE_NONE;
56 if (contents->IsWaitingForResponse())
57 return TabRendererData::NETWORK_STATE_WAITING;
58 return TabRendererData::NETWORK_STATE_LOADING;
61 bool DetermineTabStripLayoutStacked(
62 PrefService* prefs,
63 chrome::HostDesktopType host_desktop_type,
64 bool* adjust_layout) {
65 *adjust_layout = false;
66 // For ash, always allow entering stacked mode.
67 if (host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH)
68 return false;
69 *adjust_layout = true;
70 return prefs->GetBoolean(prefs::kTabStripStackedLayout);
73 // Get the MIME type of the file pointed to by the url, based on the file's
74 // extension. Must be called on a thread that allows IO.
75 std::string FindURLMimeType(const GURL& url) {
76 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
77 base::FilePath full_path;
78 net::FileURLToFilePath(url, &full_path);
80 // Get the MIME type based on the filename.
81 std::string mime_type;
82 net::GetMimeTypeFromFile(full_path, &mime_type);
84 return mime_type;
87 } // namespace
89 class BrowserTabStripController::TabContextMenuContents
90 : public ui::SimpleMenuModel::Delegate {
91 public:
92 TabContextMenuContents(Tab* tab,
93 BrowserTabStripController* controller)
94 : tab_(tab),
95 controller_(controller),
96 last_command_(TabStripModel::CommandFirst) {
97 model_.reset(new TabMenuModel(
98 this, controller->model_,
99 controller->tabstrip_->GetModelIndexOfTab(tab)));
100 menu_runner_.reset(new views::MenuRunner(
101 model_.get(),
102 views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
105 ~TabContextMenuContents() override {
106 if (controller_)
107 controller_->tabstrip_->StopAllHighlighting();
110 void Cancel() {
111 controller_ = NULL;
114 void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
115 if (menu_runner_->RunMenuAt(tab_->GetWidget(),
116 NULL,
117 gfx::Rect(point, gfx::Size()),
118 views::MENU_ANCHOR_TOPLEFT,
119 source_type) ==
120 views::MenuRunner::MENU_DELETED) {
121 return;
125 // Overridden from ui::SimpleMenuModel::Delegate:
126 bool IsCommandIdChecked(int command_id) const override { return false; }
127 bool IsCommandIdEnabled(int command_id) const override {
128 return controller_->IsCommandEnabledForTab(
129 static_cast<TabStripModel::ContextMenuCommand>(command_id),
130 tab_);
132 bool GetAcceleratorForCommandId(int command_id,
133 ui::Accelerator* accelerator) override {
134 int browser_cmd;
135 return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
136 &browser_cmd) ?
137 controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
138 accelerator) :
139 false;
141 void CommandIdHighlighted(int command_id) override {
142 controller_->StopHighlightTabsForCommand(last_command_, tab_);
143 last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
144 controller_->StartHighlightTabsForCommand(last_command_, tab_);
146 void ExecuteCommand(int command_id, int event_flags) override {
147 // Executing the command destroys |this|, and can also end up destroying
148 // |controller_|. So stop the highlights before executing the command.
149 controller_->tabstrip_->StopAllHighlighting();
150 controller_->ExecuteCommandForTab(
151 static_cast<TabStripModel::ContextMenuCommand>(command_id),
152 tab_);
155 void MenuClosed(ui::SimpleMenuModel* /*source*/) override {
156 if (controller_)
157 controller_->tabstrip_->StopAllHighlighting();
160 private:
161 scoped_ptr<TabMenuModel> model_;
162 scoped_ptr<views::MenuRunner> menu_runner_;
164 // The tab we're showing a menu for.
165 Tab* tab_;
167 // A pointer back to our hosting controller, for command state information.
168 BrowserTabStripController* controller_;
170 // The last command that was selected, so that we can start/stop highlighting
171 // appropriately as the user moves through the menu.
172 TabStripModel::ContextMenuCommand last_command_;
174 DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
177 ////////////////////////////////////////////////////////////////////////////////
178 // BrowserTabStripController, public:
180 BrowserTabStripController::BrowserTabStripController(Browser* browser,
181 TabStripModel* model)
182 : model_(model),
183 tabstrip_(NULL),
184 browser_(browser),
185 hover_tab_selector_(model),
186 weak_ptr_factory_(this) {
187 model_->AddObserver(this);
189 local_pref_registrar_.Init(g_browser_process->local_state());
190 local_pref_registrar_.Add(
191 prefs::kTabStripStackedLayout,
192 base::Bind(&BrowserTabStripController::UpdateStackedLayout,
193 base::Unretained(this)));
196 BrowserTabStripController::~BrowserTabStripController() {
197 // When we get here the TabStrip is being deleted. We need to explicitly
198 // cancel the menu, otherwise it may try to invoke something on the tabstrip
199 // from its destructor.
200 if (context_menu_contents_.get())
201 context_menu_contents_->Cancel();
203 model_->RemoveObserver(this);
206 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
207 tabstrip_ = tabstrip;
209 UpdateStackedLayout();
211 // Walk the model, calling our insertion observer method for each item within
212 // it.
213 for (int i = 0; i < model_->count(); ++i)
214 AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
217 bool BrowserTabStripController::IsCommandEnabledForTab(
218 TabStripModel::ContextMenuCommand command_id,
219 Tab* tab) const {
220 int model_index = tabstrip_->GetModelIndexOfTab(tab);
221 return model_->ContainsIndex(model_index) ?
222 model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
225 void BrowserTabStripController::ExecuteCommandForTab(
226 TabStripModel::ContextMenuCommand command_id,
227 Tab* tab) {
228 int model_index = tabstrip_->GetModelIndexOfTab(tab);
229 if (model_->ContainsIndex(model_index))
230 model_->ExecuteContextMenuCommand(model_index, command_id);
233 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
234 return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
237 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
238 return model_->selection_model();
241 int BrowserTabStripController::GetCount() const {
242 return model_->count();
245 bool BrowserTabStripController::IsValidIndex(int index) const {
246 return model_->ContainsIndex(index);
249 bool BrowserTabStripController::IsActiveTab(int model_index) const {
250 return model_->active_index() == model_index;
253 int BrowserTabStripController::GetActiveIndex() const {
254 return model_->active_index();
257 bool BrowserTabStripController::IsTabSelected(int model_index) const {
258 return model_->IsTabSelected(model_index);
261 bool BrowserTabStripController::IsTabPinned(int model_index) const {
262 return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
265 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
266 if (!model_->ContainsIndex(model_index))
267 return false;
269 const WebContents* contents = model_->GetWebContentsAt(model_index);
270 return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
271 chrome::IsInstantNTP(contents));
274 void BrowserTabStripController::SelectTab(int model_index) {
275 model_->ActivateTabAt(model_index, true);
278 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
279 model_->ExtendSelectionTo(model_index);
282 void BrowserTabStripController::ToggleSelected(int model_index) {
283 model_->ToggleSelectionAt(model_index);
286 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
287 model_->AddSelectionFromAnchorTo(model_index);
290 void BrowserTabStripController::CloseTab(int model_index,
291 CloseTabSource source) {
292 // Cancel any pending tab transition.
293 hover_tab_selector_.CancelTabTransition();
295 tabstrip_->PrepareForCloseAt(model_index, source);
296 model_->CloseWebContentsAt(model_index,
297 TabStripModel::CLOSE_USER_GESTURE |
298 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
301 void BrowserTabStripController::ToggleTabAudioMute(int model_index) {
302 content::WebContents* const contents = model_->GetWebContentsAt(model_index);
303 chrome::SetTabAudioMuted(contents, !chrome::IsTabAudioMuted(contents),
304 chrome::kMutedToggleCauseUser);
307 void BrowserTabStripController::ShowContextMenuForTab(
308 Tab* tab,
309 const gfx::Point& p,
310 ui::MenuSourceType source_type) {
311 context_menu_contents_.reset(new TabContextMenuContents(tab, this));
312 context_menu_contents_->RunMenuAt(p, source_type);
315 void BrowserTabStripController::UpdateLoadingAnimations() {
316 // Don't use the model count here as it's possible for this to be invoked
317 // before we've applied an update from the model (Browser::TabInsertedAt may
318 // be processed before us and invokes this).
319 for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i) {
320 if (model_->ContainsIndex(i)) {
321 Tab* tab = tabstrip_->tab_at(i);
322 WebContents* contents = model_->GetWebContentsAt(i);
323 tab->UpdateLoadingAnimation(TabContentsNetworkState(contents));
328 int BrowserTabStripController::HasAvailableDragActions() const {
329 return model_->delegate()->GetDragActions();
332 void BrowserTabStripController::OnDropIndexUpdate(int index,
333 bool drop_before) {
334 // Perform a delayed tab transition if hovering directly over a tab.
335 // Otherwise, cancel the pending one.
336 if (index != -1 && !drop_before) {
337 hover_tab_selector_.StartTabTransition(index);
338 } else {
339 hover_tab_selector_.CancelTabTransition();
343 void BrowserTabStripController::PerformDrop(bool drop_before,
344 int index,
345 const GURL& url) {
346 chrome::NavigateParams params(browser_, url, ui::PAGE_TRANSITION_LINK);
347 params.tabstrip_index = index;
349 if (drop_before) {
350 content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
351 params.disposition = NEW_FOREGROUND_TAB;
352 } else {
353 content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
354 params.disposition = CURRENT_TAB;
355 params.source_contents = model_->GetWebContentsAt(index);
357 params.window_action = chrome::NavigateParams::SHOW_WINDOW;
358 chrome::Navigate(&params);
361 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
362 Profile* other_profile =
363 static_cast<BrowserTabStripController*>(other->controller())->profile();
364 return other_profile == profile();
367 void BrowserTabStripController::CreateNewTab() {
368 model_->delegate()->AddTabAt(GURL(), -1, true);
371 void BrowserTabStripController::CreateNewTabWithLocation(
372 const base::string16& location) {
373 // Use autocomplete to clean up the text, going so far as to turn it into
374 // a search query if necessary.
375 AutocompleteMatch match;
376 AutocompleteClassifierFactory::GetForProfile(profile())->Classify(
377 location, false, false, metrics::OmniboxEventProto::BLANK, &match, NULL);
378 if (match.destination_url.is_valid())
379 model_->delegate()->AddTabAt(match.destination_url, -1, true);
382 bool BrowserTabStripController::IsIncognito() {
383 return browser_->profile()->IsOffTheRecord();
386 void BrowserTabStripController::StackedLayoutMaybeChanged() {
387 bool adjust_layout = false;
388 bool stacked_layout =
389 DetermineTabStripLayoutStacked(g_browser_process->local_state(),
390 browser_->host_desktop_type(),
391 &adjust_layout);
392 if (!adjust_layout || stacked_layout == tabstrip_->stacked_layout())
393 return;
395 g_browser_process->local_state()->SetBoolean(prefs::kTabStripStackedLayout,
396 tabstrip_->stacked_layout());
399 void BrowserTabStripController::OnStartedDraggingTabs() {
400 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
401 if (browser_view && !immersive_reveal_lock_.get()) {
402 // The top-of-window views should be revealed while the user is dragging
403 // tabs in immersive fullscreen. The top-of-window views may not be already
404 // revealed if the user is attempting to attach a tab to a tabstrip
405 // belonging to an immersive fullscreen window.
406 immersive_reveal_lock_.reset(
407 browser_view->immersive_mode_controller()->GetRevealedLock(
408 ImmersiveModeController::ANIMATE_REVEAL_NO));
412 void BrowserTabStripController::OnStoppedDraggingTabs() {
413 immersive_reveal_lock_.reset();
416 void BrowserTabStripController::CheckFileSupported(const GURL& url) {
417 base::PostTaskAndReplyWithResult(
418 content::BrowserThread::GetBlockingPool(),
419 FROM_HERE,
420 base::Bind(&FindURLMimeType, url),
421 base::Bind(&BrowserTabStripController::OnFindURLMimeTypeCompleted,
422 weak_ptr_factory_.GetWeakPtr(),
423 url));
426 ////////////////////////////////////////////////////////////////////////////////
427 // BrowserTabStripController, TabStripModelObserver implementation:
429 void BrowserTabStripController::TabInsertedAt(WebContents* contents,
430 int model_index,
431 bool is_active) {
432 DCHECK(contents);
433 DCHECK(model_->ContainsIndex(model_index));
434 AddTab(contents, model_index, is_active);
437 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
438 int model_index) {
439 // Cancel any pending tab transition.
440 hover_tab_selector_.CancelTabTransition();
442 tabstrip_->RemoveTabAt(model_index);
445 void BrowserTabStripController::TabSelectionChanged(
446 TabStripModel* tab_strip_model,
447 const ui::ListSelectionModel& old_model) {
448 tabstrip_->SetSelection(old_model, model_->selection_model());
451 void BrowserTabStripController::TabMoved(WebContents* contents,
452 int from_model_index,
453 int to_model_index) {
454 // Cancel any pending tab transition.
455 hover_tab_selector_.CancelTabTransition();
457 // Pass in the TabRendererData as the pinned state may have changed.
458 TabRendererData data;
459 SetTabRendererDataFromModel(contents, to_model_index, &data, EXISTING_TAB);
460 tabstrip_->MoveTab(from_model_index, to_model_index, data);
463 void BrowserTabStripController::TabChangedAt(WebContents* contents,
464 int model_index,
465 TabChangeType change_type) {
466 if (change_type == TITLE_NOT_LOADING) {
467 tabstrip_->TabTitleChangedNotLoading(model_index);
468 // We'll receive another notification of the change asynchronously.
469 return;
472 SetTabDataAt(contents, model_index);
475 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
476 WebContents* old_contents,
477 WebContents* new_contents,
478 int model_index) {
479 SetTabDataAt(new_contents, model_index);
482 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
483 int model_index) {
484 // Currently none of the renderers render pinned state differently.
487 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
488 int model_index) {
489 SetTabDataAt(contents, model_index);
492 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
493 int model_index) {
494 SetTabDataAt(contents, model_index);
497 void BrowserTabStripController::SetTabRendererDataFromModel(
498 WebContents* contents,
499 int model_index,
500 TabRendererData* data,
501 TabStatus tab_status) {
502 favicon::FaviconDriver* favicon_driver =
503 favicon::ContentFaviconDriver::FromWebContents(contents);
505 data->favicon = favicon_driver->GetFavicon().AsImageSkia();
506 data->network_state = TabContentsNetworkState(contents);
507 data->title = contents->GetTitle();
508 data->url = contents->GetURL();
509 data->loading = contents->IsLoading();
510 data->crashed_status = contents->GetCrashedStatus();
511 data->incognito = contents->GetBrowserContext()->IsOffTheRecord();
512 data->mini = model_->IsMiniTab(model_index);
513 data->show_icon = data->mini || favicon::ShouldDisplayFavicon(contents);
514 data->blocked = model_->IsTabBlocked(model_index);
515 data->app = extensions::TabHelper::FromWebContents(contents)->is_app();
516 data->media_state = chrome::GetTabMediaStateForContents(contents);
519 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
520 int model_index) {
521 TabRendererData data;
522 SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
523 tabstrip_->SetTabData(model_index, data);
526 void BrowserTabStripController::StartHighlightTabsForCommand(
527 TabStripModel::ContextMenuCommand command_id,
528 Tab* tab) {
529 if (command_id == TabStripModel::CommandCloseOtherTabs ||
530 command_id == TabStripModel::CommandCloseTabsToRight) {
531 int model_index = tabstrip_->GetModelIndexOfTab(tab);
532 if (IsValidIndex(model_index)) {
533 std::vector<int> indices =
534 model_->GetIndicesClosedByCommand(model_index, command_id);
535 for (std::vector<int>::const_iterator i(indices.begin());
536 i != indices.end(); ++i) {
537 tabstrip_->StartHighlight(*i);
543 void BrowserTabStripController::StopHighlightTabsForCommand(
544 TabStripModel::ContextMenuCommand command_id,
545 Tab* tab) {
546 if (command_id == TabStripModel::CommandCloseTabsToRight ||
547 command_id == TabStripModel::CommandCloseOtherTabs) {
548 // Just tell all Tabs to stop pulsing - it's safe.
549 tabstrip_->StopAllHighlighting();
553 void BrowserTabStripController::AddTab(WebContents* contents,
554 int index,
555 bool is_active) {
556 // Cancel any pending tab transition.
557 hover_tab_selector_.CancelTabTransition();
559 TabRendererData data;
560 SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
561 tabstrip_->AddTabAt(index, data, is_active);
564 void BrowserTabStripController::UpdateStackedLayout() {
565 bool adjust_layout = false;
566 bool stacked_layout =
567 DetermineTabStripLayoutStacked(g_browser_process->local_state(),
568 browser_->host_desktop_type(),
569 &adjust_layout);
570 tabstrip_->set_adjust_layout(adjust_layout);
571 tabstrip_->SetStackedLayout(stacked_layout);
574 void BrowserTabStripController::OnFindURLMimeTypeCompleted(
575 const GURL& url,
576 const std::string& mime_type) {
577 // Check whether the mime type, if given, is known to be supported or whether
578 // there is a plugin that supports the mime type (e.g. PDF).
579 // TODO(bauerb): This possibly uses stale information, but it's guaranteed not
580 // to do disk access.
581 content::WebPluginInfo plugin;
582 tabstrip_->FileSupported(
583 url,
584 mime_type.empty() ||
585 net::IsSupportedMimeType(mime_type) ||
586 content::PluginService::GetInstance()->GetPluginInfo(
587 -1, // process ID
588 MSG_ROUTING_NONE, // routing ID
589 model_->profile()->GetResourceContext(),
590 url, GURL(), mime_type, false,
591 NULL, &plugin, NULL));