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