Home | History | Annotate | Download | only in login
      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.
      4 
      5 #include "chrome/browser/chromeos/login/webui_login_view.h"
      6 
      7 #include "ash/shell.h"
      8 #include "ash/system/tray/system_tray.h"
      9 #include "base/bind.h"
     10 #include "base/callback.h"
     11 #include "base/debug/trace_event.h"
     12 #include "base/i18n/rtl.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/values.h"
     15 #include "chrome/browser/chrome_notification_types.h"
     16 #include "chrome/browser/chromeos/accessibility/accessibility_util.h"
     17 #include "chrome/browser/chromeos/login/login_display_host_impl.h"
     18 #include "chrome/browser/chromeos/login/proxy_settings_dialog.h"
     19 #include "chrome/browser/chromeos/login/webui_login_display.h"
     20 #include "chrome/browser/chromeos/profiles/profile_helper.h"
     21 #include "chrome/browser/extensions/extension_web_contents_observer.h"
     22 #include "chrome/browser/media/media_stream_infobar_delegate.h"
     23 #include "chrome/browser/password_manager/password_manager.h"
     24 #include "chrome/browser/password_manager/password_manager_delegate_impl.h"
     25 #include "chrome/browser/renderer_preferences_util.h"
     26 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
     27 #include "chrome/common/render_messages.h"
     28 #include "chromeos/dbus/dbus_thread_manager.h"
     29 #include "chromeos/dbus/session_manager_client.h"
     30 #include "chromeos/network/network_state.h"
     31 #include "chromeos/network/network_state_handler.h"
     32 #include "components/web_modal/web_contents_modal_dialog_manager.h"
     33 #include "content/public/browser/notification_service.h"
     34 #include "content/public/browser/render_view_host.h"
     35 #include "content/public/browser/render_widget_host_view.h"
     36 #include "content/public/browser/web_contents.h"
     37 #include "content/public/browser/web_contents_view.h"
     38 #include "content/public/browser/web_ui.h"
     39 #include "ui/gfx/rect.h"
     40 #include "ui/gfx/size.h"
     41 #include "ui/views/controls/webview/webview.h"
     42 #include "ui/views/widget/widget.h"
     43 
     44 using content::NativeWebKeyboardEvent;
     45 using content::RenderViewHost;
     46 using content::WebContents;
     47 using web_modal::WebContentsModalDialogManager;
     48 
     49 namespace {
     50 
     51 // These strings must be kept in sync with handleAccelerator()
     52 // in display_manager.js.
     53 const char kAccelNameCancel[] = "cancel";
     54 const char kAccelNameEnrollment[] = "enrollment";
     55 const char kAccelNameKioskEnable[] = "kiosk_enable";
     56 const char kAccelNameVersion[] = "version";
     57 const char kAccelNameReset[] = "reset";
     58 const char kAccelFocusPrev[] = "focus_prev";
     59 const char kAccelFocusNext[] = "focus_next";
     60 const char kAccelNameDeviceRequisition[] = "device_requisition";
     61 const char kAccelNameDeviceRequisitionRemora[] = "device_requisition_remora";
     62 const char kAccelNameAppLaunchBailout[] = "app_launch_bailout";
     63 
     64 // A class to change arrow key traversal behavior when it's alive.
     65 class ScopedArrowKeyTraversal {
     66  public:
     67   explicit ScopedArrowKeyTraversal(bool new_arrow_key_tranversal_enabled)
     68       : previous_arrow_key_traversal_enabled_(
     69             views::FocusManager::arrow_key_traversal_enabled()) {
     70     views::FocusManager::set_arrow_key_traversal_enabled(
     71         new_arrow_key_tranversal_enabled);
     72   }
     73   ~ScopedArrowKeyTraversal() {
     74     views::FocusManager::set_arrow_key_traversal_enabled(
     75         previous_arrow_key_traversal_enabled_);
     76   }
     77 
     78  private:
     79   const bool previous_arrow_key_traversal_enabled_;
     80   DISALLOW_COPY_AND_ASSIGN(ScopedArrowKeyTraversal);
     81 };
     82 
     83 }  // namespace
     84 
     85 namespace chromeos {
     86 
     87 // static
     88 const char WebUILoginView::kViewClassName[] =
     89     "browser/chromeos/login/WebUILoginView";
     90 
     91 // WebUILoginView public: ------------------------------------------------------
     92 
     93 WebUILoginView::WebUILoginView()
     94     : webui_login_(NULL),
     95       is_hidden_(false),
     96       webui_visible_(false),
     97       should_emit_login_prompt_visible_(true),
     98       forward_keyboard_event_(true) {
     99   registrar_.Add(this,
    100                  chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
    101                  content::NotificationService::AllSources());
    102   registrar_.Add(this,
    103                  chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
    104                  content::NotificationService::AllSources());
    105 
    106   accel_map_[ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)] =
    107       kAccelNameCancel;
    108   accel_map_[ui::Accelerator(ui::VKEY_E,
    109                              ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
    110       kAccelNameEnrollment;
    111   accel_map_[ui::Accelerator(ui::VKEY_K,
    112                              ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
    113       kAccelNameKioskEnable;
    114   accel_map_[ui::Accelerator(ui::VKEY_V, ui::EF_ALT_DOWN)] =
    115       kAccelNameVersion;
    116   accel_map_[ui::Accelerator(ui::VKEY_R,
    117       ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)] =
    118       kAccelNameReset;
    119 
    120   accel_map_[ui::Accelerator(ui::VKEY_LEFT, ui::EF_NONE)] =
    121       kAccelFocusPrev;
    122   accel_map_[ui::Accelerator(ui::VKEY_RIGHT, ui::EF_NONE)] =
    123       kAccelFocusNext;
    124 
    125   // Use KEY_RELEASED because Gaia consumes KEY_PRESSED for up/down key.
    126   ui::Accelerator key_up(ui::VKEY_UP, ui::EF_NONE);
    127   key_up.set_type(ui::ET_KEY_RELEASED);
    128   ui::Accelerator key_down(ui::VKEY_DOWN, ui::EF_NONE);
    129   key_down.set_type(ui::ET_KEY_RELEASED);
    130   accel_map_[key_up] = kAccelFocusPrev;
    131   accel_map_[key_down] = kAccelFocusNext;
    132 
    133   accel_map_[ui::Accelerator(
    134       ui::VKEY_D, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)] =
    135       kAccelNameDeviceRequisition;
    136   accel_map_[
    137       ui::Accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
    138       kAccelNameDeviceRequisitionRemora;
    139 
    140   accel_map_[ui::Accelerator(ui::VKEY_S,
    141                              ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
    142       kAccelNameAppLaunchBailout;
    143 
    144   for (AccelMap::iterator i(accel_map_.begin()); i != accel_map_.end(); ++i)
    145     AddAccelerator(i->first);
    146 }
    147 
    148 WebUILoginView::~WebUILoginView() {
    149   FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver,
    150                     observer_list_,
    151                     OnHostDestroying());
    152 
    153   if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) {
    154     ash::Shell::GetInstance()->GetPrimarySystemTray()->
    155         SetNextFocusableView(NULL);
    156   }
    157 }
    158 
    159 void WebUILoginView::Init() {
    160   Profile* signin_profile = ProfileHelper::GetSigninProfile();
    161   auth_extension_.reset(new ScopedGaiaAuthExtension(signin_profile));
    162   webui_login_ = new views::WebView(signin_profile);
    163   webui_login_->set_allow_accelerators(true);
    164   AddChildView(webui_login_);
    165 
    166   WebContents* web_contents = webui_login_->GetWebContents();
    167 
    168   // Create the password manager that is needed for the proxy.
    169   PasswordManagerDelegateImpl::CreateForWebContents(web_contents);
    170   PasswordManager::CreateForWebContentsAndDelegate(
    171       web_contents, PasswordManagerDelegateImpl::FromWebContents(web_contents));
    172 
    173   // LoginHandlerViews uses a constrained window for the password manager view.
    174   WebContentsModalDialogManager::CreateForWebContents(web_contents);
    175   WebContentsModalDialogManager::FromWebContents(web_contents)->
    176       SetDelegate(this);
    177 
    178   web_contents->SetDelegate(this);
    179   extensions::ExtensionWebContentsObserver::CreateForWebContents(web_contents);
    180   WebContentsObserver::Observe(web_contents);
    181   renderer_preferences_util::UpdateFromSystemSettings(
    182       web_contents->GetMutableRendererPrefs(),
    183       signin_profile);
    184 }
    185 
    186 const char* WebUILoginView::GetClassName() const {
    187   return kViewClassName;
    188 }
    189 
    190 web_modal::WebContentsModalDialogHost*
    191     WebUILoginView::GetWebContentsModalDialogHost() {
    192   return this;
    193 }
    194 
    195 gfx::NativeView WebUILoginView::GetHostView() const {
    196   return GetWidget()->GetNativeView();
    197 }
    198 
    199 gfx::Point WebUILoginView::GetDialogPosition(const gfx::Size& size) {
    200   // Center the widget.
    201   gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size();
    202   return gfx::Point(widget_size.width() / 2 - size.width() / 2,
    203                     widget_size.height() / 2 - size.height() / 2);
    204 }
    205 
    206 gfx::Size WebUILoginView::GetMaximumDialogSize() {
    207   return GetWidget()->GetWindowBoundsInScreen().size();
    208 }
    209 
    210 void WebUILoginView::AddObserver(
    211     web_modal::ModalDialogHostObserver* observer) {
    212   if (observer && !observer_list_.HasObserver(observer))
    213     observer_list_.AddObserver(observer);
    214 }
    215 
    216 void WebUILoginView::RemoveObserver(
    217     web_modal::ModalDialogHostObserver* observer) {
    218   observer_list_.RemoveObserver(observer);
    219 }
    220 
    221 bool WebUILoginView::AcceleratorPressed(
    222     const ui::Accelerator& accelerator) {
    223   AccelMap::const_iterator entry = accel_map_.find(accelerator);
    224   if (entry == accel_map_.end())
    225     return false;
    226 
    227   if (!webui_login_)
    228     return true;
    229 
    230   content::WebUI* web_ui = GetWebUI();
    231   if (web_ui) {
    232     base::StringValue accel_name(entry->second);
    233     web_ui->CallJavascriptFunction("cr.ui.Oobe.handleAccelerator",
    234                                    accel_name);
    235   }
    236 
    237   return true;
    238 }
    239 
    240 gfx::NativeWindow WebUILoginView::GetNativeWindow() const {
    241   return GetWidget()->GetNativeWindow();
    242 }
    243 
    244 void WebUILoginView::LoadURL(const GURL & url) {
    245   webui_login_->LoadInitialURL(url);
    246   webui_login_->RequestFocus();
    247 
    248   // TODO(nkostylev): Use WebContentsObserver::RenderViewCreated to track
    249   // when RenderView is created.
    250   // Use a background with transparency to trigger transparency in Webkit.
    251   SkBitmap background;
    252   background.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
    253   background.allocPixels();
    254   background.eraseARGB(0x00, 0x00, 0x00, 0x00);
    255   content::RenderViewHost* host = GetWebContents()->GetRenderViewHost();
    256   host->GetView()->SetBackground(background);
    257 }
    258 
    259 content::WebUI* WebUILoginView::GetWebUI() {
    260   return webui_login_->web_contents()->GetWebUI();
    261 }
    262 
    263 content::WebContents* WebUILoginView::GetWebContents() {
    264   return webui_login_->web_contents();
    265 }
    266 
    267 void WebUILoginView::OpenProxySettings() {
    268   const NetworkState* network =
    269       NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
    270   if (!network) {
    271     LOG(ERROR) << "No default network found!";
    272     return;
    273   }
    274   ProxySettingsDialog* dialog =
    275       new ProxySettingsDialog(*network, NULL, GetNativeWindow());
    276   dialog->Show();
    277 }
    278 
    279 void WebUILoginView::OnPostponedShow() {
    280   set_is_hidden(false);
    281   OnLoginPromptVisible();
    282 }
    283 
    284 void WebUILoginView::SetStatusAreaVisible(bool visible) {
    285   if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) {
    286     ash::SystemTray* tray = ash::Shell::GetInstance()->GetPrimarySystemTray();
    287     if (visible) {
    288       // Tray may have been initialized being hidden.
    289       tray->SetVisible(visible);
    290       tray->GetWidget()->Show();
    291     } else {
    292       tray->GetWidget()->Hide();
    293     }
    294   }
    295 }
    296 
    297 void WebUILoginView::SetUIEnabled(bool enabled) {
    298   forward_keyboard_event_ = enabled;
    299   ash::Shell::GetInstance()->GetPrimarySystemTray()->SetEnabled(enabled);
    300 }
    301 
    302 // WebUILoginView protected: ---------------------------------------------------
    303 
    304 void WebUILoginView::Layout() {
    305   DCHECK(webui_login_);
    306   webui_login_->SetBoundsRect(bounds());
    307 
    308   FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver,
    309                     observer_list_,
    310                     OnPositionRequiresUpdate());
    311 }
    312 
    313 void WebUILoginView::OnLocaleChanged() {
    314 }
    315 
    316 void WebUILoginView::ChildPreferredSizeChanged(View* child) {
    317   Layout();
    318   SchedulePaint();
    319 }
    320 
    321 void WebUILoginView::AboutToRequestFocusFromTabTraversal(bool reverse) {
    322   // Return the focus to the web contents.
    323   webui_login_->web_contents()->FocusThroughTabTraversal(reverse);
    324   GetWidget()->Activate();
    325   webui_login_->web_contents()->GetView()->Focus();
    326 }
    327 
    328 void WebUILoginView::Observe(int type,
    329                              const content::NotificationSource& source,
    330                              const content::NotificationDetails& details) {
    331   switch (type) {
    332     case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE:
    333     case chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN: {
    334       OnLoginPromptVisible();
    335       registrar_.RemoveAll();
    336       break;
    337     }
    338     default:
    339       NOTREACHED() << "Unexpected notification " << type;
    340   }
    341 }
    342 
    343 // WebUILoginView private: -----------------------------------------------------
    344 
    345 bool WebUILoginView::HandleContextMenu(
    346     const content::ContextMenuParams& params) {
    347   // Do not show the context menu.
    348 #ifndef NDEBUG
    349   return false;
    350 #else
    351   return true;
    352 #endif
    353 }
    354 
    355 void WebUILoginView::HandleKeyboardEvent(content::WebContents* source,
    356                                          const NativeWebKeyboardEvent& event) {
    357   if (forward_keyboard_event_) {
    358     // Disable arrow key traversal because arrow keys are handled via
    359     // accelerator when this view has focus.
    360     ScopedArrowKeyTraversal arrow_key_traversal(false);
    361 
    362     unhandled_keyboard_event_handler_.HandleKeyboardEvent(event,
    363                                                           GetFocusManager());
    364   }
    365 
    366   // Make sure error bubble is cleared on keyboard event. This is needed
    367   // when the focus is inside an iframe. Only clear on KeyDown to prevent hiding
    368   // an immediate authentication error (See crbug.com/103643).
    369   if (event.type == blink::WebInputEvent::KeyDown) {
    370     content::WebUI* web_ui = GetWebUI();
    371     if (web_ui)
    372       web_ui->CallJavascriptFunction("cr.ui.Oobe.clearErrors");
    373   }
    374 }
    375 
    376 bool WebUILoginView::IsPopupOrPanel(const WebContents* source) const {
    377   return true;
    378 }
    379 
    380 bool WebUILoginView::TakeFocus(content::WebContents* source, bool reverse) {
    381   // In case of blocked UI (ex.: sign in is in progress)
    382   // we should not process focus change events.
    383   if (!forward_keyboard_event_)
    384     return false;
    385 
    386   ash::SystemTray* tray = ash::Shell::GetInstance()->GetPrimarySystemTray();
    387   if (tray && tray->GetWidget()->IsVisible()) {
    388     tray->SetNextFocusableView(this);
    389     ash::Shell::GetInstance()->RotateFocus(reverse ? ash::Shell::BACKWARD :
    390                                                      ash::Shell::FORWARD);
    391   }
    392 
    393   return true;
    394 }
    395 
    396 void WebUILoginView::RequestMediaAccessPermission(
    397     WebContents* web_contents,
    398     const content::MediaStreamRequest& request,
    399     const content::MediaResponseCallback& callback) {
    400   if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback))
    401     NOTREACHED() << "Media stream not allowed for WebUI";
    402 }
    403 
    404 void WebUILoginView::DidFailProvisionalLoad(
    405     int64 frame_id,
    406     const base::string16& frame_unique_name,
    407     bool is_main_frame,
    408     const GURL& validated_url,
    409     int error_code,
    410     const base::string16& error_description,
    411     content::RenderViewHost* render_view_host) {
    412   if (frame_unique_name != UTF8ToUTF16("gaia-frame"))
    413     return;
    414 
    415   base::FundamentalValue error_value(-error_code);
    416   GetWebUI()->CallJavascriptFunction("login.GaiaSigninScreen.onFrameError",
    417                                      error_value);
    418 }
    419 
    420 void WebUILoginView::OnLoginPromptVisible() {
    421   // If we're hidden than will generate this signal once we're shown.
    422   if (is_hidden_ || webui_visible_) {
    423     LOG(WARNING) << "Login WebUI >> not emitting signal, hidden: "
    424                  << is_hidden_;
    425     return;
    426   }
    427   TRACE_EVENT0("chromeos", "WebUILoginView::OnLoginPromoptVisible");
    428   if (should_emit_login_prompt_visible_) {
    429     LOG(WARNING) << "Login WebUI >> login-prompt-visible";
    430     chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->
    431         EmitLoginPromptVisible();
    432   }
    433 
    434   webui_visible_ = true;
    435 }
    436 
    437 void WebUILoginView::ReturnFocus(bool reverse) {
    438   // Return the focus to the web contents.
    439   webui_login_->web_contents()->FocusThroughTabTraversal(reverse);
    440   GetWidget()->Activate();
    441 }
    442 
    443 }  // namespace chromeos
    444