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