Получение Chrome для запроса пароля при использовании AJAX для входа в систему

ПРИМЕЧАНИЕ. Этот вопрос был сильно изменен из его первоначальной версии. Этот вопрос был значительно упрощен.

Подобные вопросы задавались несколько раз раньше, в разных формах - например,

Как заставить браузер запрашивать пароль?

Как браузер знает, когда запрашивать пользователя для сохранения пароля?

Однако этот вопрос касается очень специфического аспекта функциональности Chrome, поэтому в этом отношении он совсем другой.

Судя по прошлым ответам, кажется, что лучший подход к тому, чтобы заставить Chrome запрашивать сохранение пароля, заключается в том, чтобы отправить форму в фиктивный iframe, а фактически войти в систему через AJAX: пример. Это имеет смысл для меня, и поэтому я несколько раз искал несколько примеров кода. Однако поведение Chrome в этом отношении НЕ имеет смысла для меня. Вообще. Отсюда вопрос.

По какой-то причине Chrome не предложит вам сохранить ваш пароль, если форма, которая отправляется в фиктивный iframe, присутствует во время/справа после onDomReady.

В jsfiddle можно найти здесь, но это мало используется, потому что вы должны локально создать dummy.html, чтобы увидеть описанное поведение. Поэтому лучший способ увидеть это - скопировать полный html в свой собственный index.html, а затем создать файл dummy.html.

Вот полный код для index.html. Три подхода выделяются как (1), (2), (3). Только (2) гарантирует, что пользователю будет предложено сохранить свой пароль, а тот факт, что (3) не работает, вызывает у меня особое недоумение.

<html>
<head>
<title>Chrome: Remember Password</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>

<script type="text/javascript">  

    $(function(){

        function create_login_form()
        {
            $('#login_form').html(
            "<input type='text' name='email'>" +
            "<input type='password' name='password'>" +
            "<input id='login-button' type='submit' value='Login'/>");
        }

        // (1) this does not work. chrome seems to require time after "onDomReady" to process
        // the forms that are present on the page. if the form fields exist while that processing
        // is going on, they will not generate the "Save Password?" prompt.
        //create_login_form();

        // (2) This works. chrome has obviously finished its "work" on the form fields by now so
        // we can add our content to the form and, upon submit, it will prompt "Save Password?"

        setTimeout(create_login_form,500);

    });

</script>
</head>
<body>

<!-- Our dummy iframe where the form submits to -->
<iframe src="dummy.html" name="dummy" style="display: none"></iframe>

<form action="" method="post" target="dummy" id="login_form">

    <!-- (3) fails. form content cannot be present on pageload -->
    <!--
    <input type='text' name='email'>
    <input type='password' name='password'>
    <input id='login-button' type='submit' value='Login'/>      
    -->

</form>

</body> 
</html>

Если бы кто-нибудь мог объяснить, что здесь происходит, я был бы очень благодарен.

РЕДАКТИРОВАТЬ: Обратите внимание, что эта "проблема с сохранением пароля" с Chrome распространяется на людей, работающих с AngularJS, также разработанных Google: Github

Ответ 1

После тщательного изучения этой проблемы, вот мой окончательный отчет:

Во-первых, проблема, отмеченная в вопросе, была на самом деле красной селедкой. Я неправильно отправил форму в iframe (который arty выделен, но я не подключил точки). Мой подход к этой проблеме был основан на в этом примере, о котором также упоминаются некоторые другие связанные ответы. Правильный подход можно увидеть здесь. В принципе, action для form должен быть установлен в src для iframe (именно это предложил @arty).

Когда вы это делаете, конкретная проблема, выделенная в вопросе, исчезает, потому что страница вообще не перезагружается (что имеет смысл - почему страница должна перезагружаться при отправке в iframe?)., который должен был опрокинуть меня). Во всяком случае, поскольку страница не перезагружается, Chrome никогда не просит вас сохранить ваш пароль, независимо от того, как долго вы будете ждать, чтобы отобразить форму после onDomReady.

Соответственно, отправка в iframe (правильно) НИКОГДА не приведет к тому, что Chrome попросит вас сохранить ваш пароль. Chrome будет делать это только в том случае, если страница, содержащая форму перезагрузки (обратите внимание: в отличие от старых сообщений здесь Chrome просит вас сохранить пароль, если форма была динамически создана).

Итак, единственное решение - принудительно перезагрузить страницу при отправке формы. Но как мы это сделаем и сохраним нашу структуру AJAX/SPA? Вот мое решение:

(1) Разделите SPA на две части: (1) Для не зарегистрированных пользователей, (2) Для зарегистрированных пользователей. Это может оказаться невыполнимым для тех, у кого больше сайтов. И я не считаю это постоянным решением - пожалуйста, Chrome, пожалуйста... исправьте эту ошибку.

(2) Я фиксирую два события в своих формах: onClickSaveButton и onFormSubmit. Для формы входа, в частности, я беру данные пользователя на onClickSaveButton и делаю вызов AJAX для проверки их информации. Если эта информация проходит, я вручную вызываю formName.submit() В onFormSubmit, я гарантирую, что форма не отправлена ​​до вызова onClickSaveButton.

(3) Форма отправляется в файл PHP, который просто перенаправляет файл index.php

Есть два преимущества для этого подхода:

(1) Он работает в Chrome.

(2) В Firefox пользователю теперь предлагается только сохранить свой пароль, если они успешно вошли в систему (лично мне всегда было досадно, что меня попросили сохранить мой pwd, когда это было неправильно).

Вот соответствующий код (упрощенный):

index.php

<html>
<head>
<title>Chrome: Remember Password</title>

<!-- dependencies -->
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore.js"></script>
<script type="text/javascript" src="http://backbonejs.org/backbone.js"></script>

<!-- app code -->
<script type="text/javascript" src="mycode.js"></script>
<script>

    $(function(){

        createForm();

    });

</script>

</head>
<body>

    <div id='header'>
        <h1>Welcome to my page!</h1>
    </div>

    <div id='content'>
        <div id='form'>
        </div>
    </div>

</body> 
</html>

MYCODE.JS

function createForm() {

    VLoginForm = Backbone.View.extend({

        allowDefaultSubmit : true,

        // UI events from the HTML created by this view
        events : {
            "click button[name=login]" : "onClickLogin",
            "submit form" : "onFormSubmit"
        },

        initialize : function() {
            this.verified = false;
            // this would be in a template
            this.html = "<form method='post' action='dummy.php'><p><label for='email'>Email</label><input type='text' name='email'></p><p><label for='password'>Password<input type='password' name='password'></p><button name='login'>Login</button></form>";        
        },
        render : function() {
            this.$el.html(this.html);
            return this;
        },
        onClickLogin : function(event) {

            // verify the data with an AJAX call (not included)

            if ( verifiedWithAJAX ) {
                this.verified = true;
                this.$("form").submit();
            }           
            else {
                // not verified, output a message
            }

            // we may have been called manually, so double check
            // that we have an event to stop.
            if ( event ) {
                event.preventDefault();
                event.stopPropagation();
            }

        },
        onFormSubmit : function(event) {
            if ( !this.verified ) {             
                event.preventDefault();
                event.stopPropagation();
                this.onClickLogin();
            }
            else if ( this.allowDefaultSubmit ) {
                // submits the form as per default
            }
            else {
                // do your own thing...
                event.preventDefault();
                event.stopPropagation();
            }
        }
    });

    var form = new VLoginForm();
    $("#form").html(form.render().$el);

}

DUMMY.PHP

<?php
    header("Location: index.php");
?>

EDIT: Ответ mkurz выглядит многообещающим. Возможно, ошибки исправлены.

Ответ 2

Начиная с Chrome 46 вам больше не нужны iframe хаки!

Исправлены все соответствующие проблемы Chrome: 1 2 3

Просто убедитесь, что исходная форма входа не существует уже после состояния push или запроса ajax путем удаления (или скрытия) формы или изменения URL-адреса действия (не тестировалось, но должно работать тоже). Также убедитесь, что все другие формы на одной странице указывают на другой URL-адрес действия, иначе они также будут считаться формой входа.

Посмотрите этот пример:

<!doctype html>
<title>dynamic</title>
<button onclick="addGeneratedForms()">addGeneratedForms</button>
<script>
function addGeneratedForms(){
  var div = document.createElement('div');
  div.innerHTML = '<form class="login" method="post" action="login">\
    <input type="text" name="username">\
    <input type="password" name="password">\
    <button type="submit">login</button> stay on this page but update the url with pushState\
  </form>';
  document.body.appendChild(div);
}
document.body.addEventListener('submit', function ajax(e){
  e.preventDefault();
  setTimeout(function(){
      e.target.parentNode.removeChild(e.target); // Or alternatively just hide the form: e.target.style.display = 'none';

      history.replaceState({success:true}, 'title', "/success.html");

      // This is working too!!! (uncomment the history.xxxState(..) line above) (it works when the http response is a redirect or a 200 status)
      //var request = new XMLHttpRequest();
      //request.open('POST', '/success.html', true); // use a real url you have instead of '/success.html'
      //request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
      //request.send();
  }, 1);
}, false);
</script>

Если вам интересно, есть дополнительные примеры в этом репо. Вы можете запустить его с помощью node: node server.js. Возможно, также см. Комментарии от этой фиксации: https://github.com/mkurz/ajax-login/commit/c0d9503c1d2a6a3a052f337b8cad2259033b1a58

Если вам нужна помощь, дайте мне знать.

Ответ 3

Вы можете заставить Chrome (v39) показать приглашение сохранить пароль без перезагрузки. Просто отправьте форму в iframe, чья src является пустой страницей, в которой не имеет < заголовок Content-Type: text/html (исключая заголовок или используя text/plain, оба, похоже, работают).

Не знаю, является ли это ошибкой или предполагаемым поведением. Если бывший, пожалуйста, держите его в покое: -)

Ответ 4

Я использую Chrome 32.0. Оба метода отлично работают здесь, не могут понять, почему в вашем конце все по-другому...

Тем не менее, мне часто приходилось использовать setTimeout() в течение нескольких сотен миллисекунд, чтобы сделать, например, focus() работает должным образом. Вероятно, это тоже поможет вам. В вашем случае:

setTimeout(display_login_form(),500)