| // Copyright (c) 2006-2008 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/external_tab_container.h" |
| |
| #include "base/logging.h" |
| #include "base/win_util.h" |
| #include "chrome/browser/automation/automation_provider.h" |
| #include "chrome/browser/browser.h" |
| #include "chrome/browser/load_notification_details.h" |
| #include "chrome/browser/profile.h" |
| #include "chrome/browser/tab_contents/provisional_load_details.h" |
| #include "chrome/browser/tab_contents/tab_contents.h" |
| #include "chrome/browser/views/tab_contents_container_view.h" |
| #include "chrome/browser/tab_contents/web_contents.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/win_util.h" |
| // Included for SetRootViewForHWND. |
| #include "chrome/views/widget/widget_win.h" |
| #include "chrome/test/automation/automation_messages.h" |
| |
| static const wchar_t kWindowObjectKey[] = L"ChromeWindowObject"; |
| |
| // TODO(sanjeevr): The external_accel_table_ and external_accel_entry_count_ |
| // member variables are now obsolete and we don't use them. |
| // We need to remove them. |
| ExternalTabContainer::ExternalTabContainer( |
| AutomationProvider* automation) |
| : automation_(automation), |
| root_view_(this), |
| tab_contents_(NULL), |
| external_accel_table_(NULL), |
| external_accel_entry_count_(0), |
| tab_contents_container_(NULL), |
| ignore_next_load_notification_(false) { |
| } |
| |
| ExternalTabContainer::~ExternalTabContainer() { |
| Uninitialize(m_hWnd); |
| } |
| |
| bool ExternalTabContainer::Init(Profile* profile, HWND parent, |
| const gfx::Rect& dimensions, |
| unsigned int style) { |
| if (IsWindow()) { |
| NOTREACHED(); |
| return false; |
| } |
| |
| // First create the container window |
| if (!Create(NULL, dimensions.ToRECT())) { |
| NOTREACHED(); |
| return false; |
| } |
| |
| // We don't ever remove the prop because the lifetime of this object |
| // is the same as the lifetime of the window |
| SetProp(*this, kWindowObjectKey, this); |
| |
| views::SetRootViewForHWND(m_hWnd, &root_view_); |
| // CreateFocusManager will subclass this window and delete the FocusManager |
| // instance when this window goes away. |
| views::FocusManager* focus_manager = |
| views::FocusManager::CreateFocusManager(m_hWnd, GetRootView()); |
| |
| DCHECK(focus_manager); |
| focus_manager->AddKeystrokeListener(this); |
| |
| tab_contents_ = new WebContents(profile, NULL, MSG_ROUTING_NONE, NULL); |
| tab_contents_->SetupController(profile); |
| tab_contents_->set_delegate(this); |
| tab_contents_->render_view_host()->AllowExternalHostBindings(); |
| |
| // Create a TabContentsContainerView to handle focus cycling using Tab and |
| // Shift-Tab. |
| tab_contents_container_ = new TabContentsContainerView(); |
| root_view_.AddChildView(tab_contents_container_); |
| // Note that SetTabContents must be called after AddChildView is called |
| tab_contents_container_->SetTabContents(tab_contents_); |
| // Add a dummy view to catch when the user tabs out of the tab |
| // Create a dummy FocusTraversable object to represent the frame of the |
| // external host. This will allow Tab and Shift-Tab to cycle into the |
| // external frame. When the tab_contents_container_ loses focus, |
| // the focus will be moved to this class (See OnSetFocus in this file). |
| // An alternative to using views::View and catching when the focus manager |
| // shifts the focus to the dummy view could be to implement our own view |
| // and handle AboutToRequestFocusFromTabTraversal. |
| views::View* dummy = new views::View(); |
| dummy->SetFocusable(true); |
| DCHECK(dummy->IsFocusable()); |
| root_view_.AddChildView(dummy); |
| |
| NavigationController* controller = tab_contents_->controller(); |
| DCHECK(controller); |
| registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, |
| Source<NavigationController>(controller)); |
| registrar_.Add(this, NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR, |
| Source<NavigationController>(controller)); |
| registrar_.Add(this, NotificationType::LOAD_STOP, |
| Source<NavigationController>(controller)); |
| NotificationService::current()->Notify( |
| NotificationType::EXTERNAL_TAB_CREATED, |
| Source<NavigationController>(controller), |
| NotificationService::NoDetails()); |
| |
| // We need WS_POPUP to be on the window during initialization, but |
| // once initialized we apply the requested style which may or may not |
| // include the popup bit. |
| // Note that it's important to do this before we call SetParent since |
| // during the SetParent call we will otherwise get a WA_ACTIVATE call |
| // that causes us to steal the current focus. |
| ModifyStyle(WS_POPUP, style, 0); |
| |
| // Now apply the parenting and style |
| if (parent) |
| SetParent(parent); |
| |
| ::ShowWindow(tab_contents_->GetNativeView(), SW_SHOWNA); |
| return true; |
| } |
| |
| bool ExternalTabContainer::Uninitialize(HWND window) { |
| if (::IsWindow(window)) { |
| views::FocusManager* focus_manager = |
| views::FocusManager::GetFocusManager(window); |
| if (focus_manager) { |
| focus_manager->RemoveKeystrokeListener(this); |
| } |
| } |
| |
| root_view_.RemoveAllChildViews(true); |
| if (tab_contents_) { |
| NavigationController* controller = tab_contents_->controller(); |
| DCHECK(controller); |
| |
| NotificationService::current()->Notify( |
| NotificationType::EXTERNAL_TAB_CLOSED, |
| Source<NavigationController>(controller), |
| Details<ExternalTabContainer>(this)); |
| |
| tab_contents_->set_delegate(NULL); |
| tab_contents_->CloseContents(); |
| // WARNING: tab_contents_ has likely been deleted. |
| tab_contents_ = NULL; |
| } |
| |
| return true; |
| } |
| |
| void ExternalTabContainer::OnFinalMessage(HWND window) { |
| Uninitialize(window); |
| delete this; |
| } |
| |
| LRESULT ExternalTabContainer::OnSize(UINT, WPARAM, LPARAM, BOOL& handled) { |
| if (tab_contents_) { |
| RECT client_rect = {0}; |
| GetClientRect(&client_rect); |
| ::SetWindowPos(tab_contents_->GetNativeView(), NULL, client_rect.left, |
| client_rect.top, client_rect.right - client_rect.left, |
| client_rect.bottom - client_rect.top, SWP_NOZORDER); |
| } |
| return 0; |
| } |
| |
| LRESULT ExternalTabContainer::OnSetFocus(UINT msg, WPARAM wp, LPARAM lp, |
| BOOL& handled) { |
| if (automation_) { |
| views::FocusManager* focus_manager = |
| views::FocusManager::GetFocusManager(GetNativeView()); |
| DCHECK(focus_manager); |
| if (focus_manager) { |
| focus_manager->ClearFocus(); |
| automation_->Send(new AutomationMsg_TabbedOut(0, |
| win_util::IsShiftPressed())); |
| } |
| } |
| |
| return 0; |
| } |
| |
| void ExternalTabContainer::OpenURLFromTab(TabContents* source, |
| const GURL& url, |
| const GURL& referrer, |
| WindowOpenDisposition disposition, |
| PageTransition::Type transition) { |
| switch (disposition) { |
| case CURRENT_TAB: |
| case SINGLETON_TAB: |
| case NEW_FOREGROUND_TAB: |
| case NEW_BACKGROUND_TAB: |
| case NEW_WINDOW: |
| if (automation_) { |
| automation_->Send(new AutomationMsg_OpenURL(0, url, disposition)); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void ExternalTabContainer::NavigationStateChanged(const TabContents* source, |
| unsigned changed_flags) { |
| if (automation_) { |
| automation_->Send( |
| new AutomationMsg_NavigationStateChanged(0, changed_flags)); |
| } |
| } |
| |
| void ExternalTabContainer::AddNewContents(TabContents* source, |
| TabContents* new_contents, |
| WindowOpenDisposition disposition, |
| const gfx::Rect& initial_pos, |
| bool user_gesture) { |
| if (disposition == NEW_POPUP || disposition == NEW_WINDOW) { |
| Browser::BuildPopupWindowHelper(source, new_contents, initial_pos, |
| Browser::TYPE_POPUP, |
| tab_contents_->profile(), true); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void ExternalTabContainer::ActivateContents(TabContents* contents) { |
| } |
| |
| void ExternalTabContainer::LoadingStateChanged(TabContents* source) { |
| } |
| |
| void ExternalTabContainer::CloseContents(TabContents* source) { |
| } |
| |
| void ExternalTabContainer::MoveContents(TabContents* source, |
| const gfx::Rect& pos) { |
| } |
| |
| bool ExternalTabContainer::IsPopup(TabContents* source) { |
| return false; |
| } |
| |
| void ExternalTabContainer::URLStarredChanged(TabContents* source, |
| bool starred) { |
| } |
| |
| void ExternalTabContainer::UpdateTargetURL(TabContents* source, |
| const GURL& url) { |
| if (automation_) { |
| std::wstring url_string = CA2W(url.spec().c_str()); |
| automation_->Send( |
| new AutomationMsg_UpdateTargetUrl(0, url_string)); |
| } |
| } |
| |
| void ExternalTabContainer::ContentsZoomChange(bool zoom_in) { |
| } |
| |
| void ExternalTabContainer::ToolbarSizeChanged(TabContents* source, |
| bool finished) { |
| } |
| |
| void ExternalTabContainer::ForwardMessageToExternalHost( |
| const std::string& message, const std::string& origin, |
| const std::string& target) { |
| if(automation_) { |
| automation_->Send( |
| new AutomationMsg_ForwardMessageToExternalHost(0, message, origin, |
| target)); |
| } |
| } |
| |
| void ExternalTabContainer::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| if (!automation_) |
| return; |
| |
| static const int kHttpClientErrorStart = 400; |
| static const int kHttpServerErrorEnd = 510; |
| |
| switch (type.value) { |
| case NotificationType::LOAD_STOP: { |
| const LoadNotificationDetails* load = |
| Details<LoadNotificationDetails>(details).ptr(); |
| if (PageTransition::IsMainFrame(load->origin())) { |
| automation_->Send(new AutomationMsg_TabLoaded(0, load->url())); |
| } |
| break; |
| } |
| case NotificationType::NAV_ENTRY_COMMITTED: { |
| if (ignore_next_load_notification_) { |
| ignore_next_load_notification_ = false; |
| return; |
| } |
| |
| const NavigationController::LoadCommittedDetails* commit = |
| Details<NavigationController::LoadCommittedDetails>(details).ptr(); |
| |
| if (commit->http_status_code >= kHttpClientErrorStart && |
| commit->http_status_code <= kHttpServerErrorEnd) { |
| automation_->Send(new AutomationMsg_NavigationFailed( |
| 0, commit->http_status_code, commit->entry->url())); |
| |
| ignore_next_load_notification_ = true; |
| } else { |
| // When the previous entry index is invalid, it will be -1, which |
| // will still make the computation come out right (navigating to the |
| // 0th entry will be +1). |
| automation_->Send(new AutomationMsg_DidNavigate( |
| 0, commit->type, |
| commit->previous_entry_index - |
| tab_contents_->controller()->last_committed_entry_index(), |
| commit->entry->url())); |
| } |
| break; |
| } |
| case NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR: { |
| const ProvisionalLoadDetails* load_details = |
| Details<ProvisionalLoadDetails>(details).ptr(); |
| automation_->Send(new AutomationMsg_NavigationFailed( |
| 0, load_details->error_code(), load_details->url())); |
| |
| ignore_next_load_notification_ = true; |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void ExternalTabContainer::GetBounds(gfx::Rect* out, |
| bool including_frame) const { |
| CRect crect; |
| GetWindowRect(&crect); |
| *out = gfx::Rect(crect); |
| } |
| |
| void ExternalTabContainer::MoveToFront(bool should_activate) { |
| } |
| |
| gfx::NativeView ExternalTabContainer::GetNativeView() const { |
| return m_hWnd; |
| } |
| |
| void ExternalTabContainer::PaintNow(const gfx::Rect& update_rect) { |
| RECT native_update_rect = update_rect.ToRECT(); |
| RedrawWindow(&native_update_rect, |
| NULL, |
| RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_NOERASE); |
| } |
| |
| views::RootView* ExternalTabContainer::GetRootView() { |
| return const_cast<views::RootView*>(&root_view_); |
| } |
| |
| bool ExternalTabContainer::IsVisible() const { |
| return !!::IsWindowVisible(*this); |
| } |
| |
| bool ExternalTabContainer::IsActive() const { |
| return win_util::IsWindowActive(*this); |
| } |
| |
| bool ExternalTabContainer::ProcessKeyDown(HWND window, UINT message, |
| WPARAM wparam, LPARAM lparam) { |
| if (!automation_) { |
| return false; |
| } |
| if ((wparam == VK_TAB) && !win_util::IsCtrlPressed()) { |
| // Tabs are handled separately (except if this is Ctrl-Tab or |
| // Ctrl-Shift-Tab) |
| return false; |
| } |
| int flags = HIWORD(lparam); |
| if ((flags & KF_EXTENDED) || (flags & KF_ALTDOWN) || |
| (wparam >= VK_F1 && wparam <= VK_F24) || |
| win_util::IsShiftPressed() || win_util::IsCtrlPressed()) { |
| // If this is an extended key or if one or more of Alt, Shift and Control |
| // are pressed, this might be an accelerator that the external host wants |
| // to handle. If the host does not handle this accelerator, it will reflect |
| // the accelerator back to us via the ProcessUnhandledAccelerator method. |
| MSG msg = {0}; |
| msg.hwnd = window; |
| msg.message = message; |
| msg.wParam = wparam; |
| msg.lParam = lparam; |
| automation_->Send(new AutomationMsg_HandleAccelerator(0, msg)); |
| return true; |
| } |
| return false; |
| } |
| |
| void ExternalTabContainer::SetAccelerators(HACCEL accel_table, |
| int accel_table_entry_count) { |
| external_accel_table_ = accel_table; |
| external_accel_entry_count_ = accel_table_entry_count; |
| } |
| |
| void ExternalTabContainer::ProcessUnhandledAccelerator(const MSG& msg) { |
| // We just received an accelerator key that we had sent to external host |
| // back. Since the external host was not interested in handling this, we |
| // need to dispatch this message as if we had just peeked this out. (we |
| // also need to call TranslateMessage to generate a WM_CHAR if needed). |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| |
| void ExternalTabContainer::SetInitialFocus(bool reverse) { |
| DCHECK(tab_contents_); |
| if (tab_contents_) { |
| static_cast<TabContents*>(tab_contents_)->SetInitialFocus(reverse); |
| } |
| } |
| |
| // static |
| bool ExternalTabContainer::IsExternalTabContainer(HWND window) { |
| std::wstring class_name = win_util::GetClassName(window); |
| return _wcsicmp(class_name.c_str(), chrome::kExternalTabWindowClass) == 0; |
| } |
| |
| // static |
| ExternalTabContainer* ExternalTabContainer::GetContainerForTab( |
| HWND tab_window) { |
| HWND parent_window = ::GetParent(tab_window); |
| if (!::IsWindow(parent_window)) { |
| return NULL; |
| } |
| if (!IsExternalTabContainer(parent_window)) { |
| return NULL; |
| } |
| ExternalTabContainer* container = reinterpret_cast<ExternalTabContainer*>( |
| GetProp(parent_window, kWindowObjectKey)); |
| return container; |
| } |