web-dev-qa-db-ja.com

HTTPSページを離れたり戻ったりするときに変更されたフォームコンテンツを保持するにはどうすればよいですか? (HTTPで動作)

  1. テキストエリアに何かを入力/変更する
  2. フォームを送信する前に、ページを離れます(例:ブラウザの[戻る]ボタンをクリックして)
  3. 編集ページに戻ります(たとえば、[進む]ボタンをクリックします)

期待される結果:テキストエリアに入力されたコンテンツはまだそこにあるはずです

実結果:

  • [〜#〜] https [〜#〜]の場合:すべての変更が失われました(bad!)
  • with [〜#〜] http [〜#〜]:変更はまだあります(good!)

HTTPSを使用するとなぜこれが起こるのですか?どうすればこれを防ぐことができますか?ブラウザまたはウェブサイトは責任がありますか?

18
unor

次のソリューションを検討できます。

autocomplete属性(HTML5)

autocompleteは、フォームで「送信」された値以前のユーザー入力に基づくを使用してフィールドに入力するようブラウザに指示するため、これは無関係であるようです。しかし、私のテストではそれを見ました。送信せずにフォームに記入した後;進む(履歴)ボタンを押してからもう一度押すと、 autocomplete="on"に設定するとフォームフィールドが自動入力され、"off"に設定するとすべてがクリアされました。

そう; (HTML5ユーザーを対象とする場合)この属性を使用して、フォームデータを「キャッシュ」できます。 (Operaを除くすべての主要ブラウザで動作します)。

<form action="/update" method="post" autocomplete="on">
    Email:    <input type="text" id="email" /><br />
    Username: <input type="text" id="uname" /><br />
    Password: <input type="password" id="pwd" autocomplete="off"/><br />
    <input type="submit" />
</form> 

残りのフォームコントロールがオンの場合、特定のフィールド(この場合はパスワード)のオートコンプリート機能をオフに設定できることに注意してください。

MSDN備考:

  • autocomplete属性がない場合、要素に親フォームがない場合、またはフォームでオートコンプリートが 'on'に設定されている場合、フィールドはデフォルトで 'on'状態になります。
  • オートコンプリート機能によって提供される情報はオブジェクトモデルに公開されず、ユーザーがテキストフィールドの値として提案の1つを選択するまでWebページに表示されません。

未送信のフォームデータをローカルに保存します。

入力データは、ページリダイレクトの直前、またはすべてのフォームコントロールのfocus-outイベントにローカルに保存できます。

クッキー

この場合、古き良きCookieは便利ですが、欠点を考慮する必要があります。

  1. プログラムで値を暗号化できますが、クライアント側で作業するため、Cookieはこれに対して本当に安全ではありません。 Http-OnlyおよびSecureでマークされたCookieはここでは役に立ちません。これらのオプションは、Cookieが「送信」(安全)され、JavaScript(httpのみ)からアクセスできない場合にSSLを強制するために使用されるためです。
  2. ブラウザにはCookieサイズの制限があります。 From [〜#〜] msdn [〜#〜] "ほとんどのブラウザは最大4096バイトのCookieをサポートしています。この小さな制限のため、Cookieはしたがって、このサイズを監視しない場合(Cookieを書き込むとき、および/またはmaxlength属性を介してコントロールの値を制限することによって);それが問題になる可能性があります。 (この場合、値のトリミングは最悪です)。
  3. ブラウザには、ドメインごとに設定できるCookieの数にも制限があります。そう;フォームデータをクッキーに保存するとき;各フォームフィールド値にCookieを設定する代わりに、それらを1つまたはいくつかのCookieにマージする必要があります。サイトがこの制限を超えないようにします。

それでも、明るい面は、すべてのブラウザーでサポートされていることであり、Cookieを介して機密性が高く長すぎるデータを「キャッシュ」する予定がない場合は、次のソリューションを使用できます。そうでない場合;次の提案、localStorageを使用することをお勧めします。

// Below is just a demonstration and is not tested thoroughly for 
// production-ready web applications by any means.  
// But it should give you an idea.

/** 
 * Caches the user-input data from the targeted form, stores it in the cookies 
 * and fetches back to the form when requested or needed. 
 */
var formCache = (function () {
    var _form = null, 
        _formData = [],
        _strFormElements = "input[type='text'],"
                + "input[type='checkbox']," 
                + "input[type='radio']," 
                // + "input[type='password'],"  // leave password field out 
                + "input[type='hidden'],"
                // + "input[type='image'],"
                + "input[type='file'],"
                // more input types...
                + "input[type='email'],"
                + "input[type='tel'],"
                + "input[type='url'],"
                + "select,"
                + "textarea";

    function _warn() {
        console.log('formCache is not initialized.');
    }

    return {

        /**
         * Initializes the formCache with a target form (id). 
         * You can pass any container id for the formId parameter, formCache will 
         * still look for form elements inside the given container. If no form id 
         * is passed, it will target the first <form> element in the DOM. 
         */
        init: function (formId) {
            var f = (typeof formId === 'undefined' || formId === null || $.trim(formId) === '') 
                    ? $('form').first() 
                    : $('#' + formId);
            _form = f.length > 0 ? f : null;
            console.log(_form);
            return formCache; // make it chainable
        },

        /** 
         * Stores the form data in the cookies.
         */
        save: function () {
            if (_form === null) return _warn();

            _form
                .find(_strFormElements)
                .each(function() {
                    var f = $(this).attr('id') + ':' + formCache.getFieldValue($(this));
                    _formData.Push(f);
                });
            docCookies.setItem('formData', _formData.join(), 31536e3); // 1 year expiration (persistent)
            console.log('Cached form data:', _formData);
            return formCache;
        },

        /** 
         * Fills out the form elements from the data previously stored in the cookies.
         */
        fetch: function () {
            if (_form === null) return _warn();

            if (!docCookies.hasItem('formData')) return;
            var fd = _formData.length < 1 ? docCookies.getItem('formData').split(',') : _formData;
            $.each(fd, function (i, item) {
                var s = item.split(':');
                var elem = $('#' + s[0]);
                formCache.setFieldValue(elem, s[1]);
            });
            return formCache;
        },

        /** 
         * Sets the value of the specified form field from previously stored data.
         */
        setFieldValue: function (elem, value) {
            if (_form === null) return _warn();

            if (elem.is('input:text') || elem.is('input:hidden') || elem.is('input:image') ||
                    elem.is('input:file') || elem.is('textarea')) {
                elem.val(value);
            } else if (elem.is('input:checkbox') || elem.is('input:radio')) {
                elem.prop('checked', value);
            } else if (elem.is('select')) {
                elem.prop('selectedIndex', value);
            }
            return formCache;
        },

        /**
         * Gets the previously stored value of the specified form field.
         */
        getFieldValue: function (elem) {
            if (_form === null) return _warn();

            if (elem.is('input:text') || elem.is('input:hidden') || elem.is('input:image') ||
                elem.is('input:file') || elem.is('textarea')) {
                    return elem.val();
                } else if (elem.is('input:checkbox') || elem.is('input:radio')) {
                    return elem.prop('checked');
                } else if (elem.is('select')) {
                    return elem.prop('selectedIndex');
                }
            else return null;
        },

        /**
         * Clears the cache and removes the previously stored form data from cookies.
         */
        clear: function () {
            _formData = [];
            docCookies.removeItem('formData');
            return formCache;
        },

        /**
         * Clears all the form fields. 
         * This is different from form.reset() which only re-sets the fields 
         * to their initial values.
         */
        clearForm: function () {
            _form
                .find(_strFormElements)
                .each(function() {
                    var elem = $(this);
                    if (elem.is('input:text') || elem.is('input:password') || elem.is('input:hidden') || 
                        elem.is('input:image') || elem.is('input:file') || elem.is('textarea')) {
                        elem.val('');
                    } else if (elem.is('input:checkbox') || elem.is('input:radio')) {
                        elem.prop('checked', false);
                    } else if (elem.is('select')) {
                        elem.prop('selectedIndex', -1);
                    }
                });
            return formCache;
        }
    };
})();

// Save form data right before we unload the form-page
$(window).on('beforeunload', function (event) {
    formCache.save();
    return false;
});

// Initialize and fetch form data (if exists) when we load the form-page back
$(document).on('ready', function (event) {
    formCache.init().fetch();
});

これがjsFiddleのデモです。

注:developer.mozilla.org の「cookies reader/writer」スクリプトは、上記のコード。 Yahooの YUI 2:Cookieユーティリティ を使用することもできます。これは、前述のブラウザ制限のために、単一のCookie内にサブCookieを設定するための便利な setSub() メソッドを備えています。

localStorage

また、localStorage(HTML5)のようなより近代的な手法を使用することもできます。より安全で高速です。 IE 8+を含むすべての主要なブラウザがこの機能をサポートします。(さらに、iOSおよびAndroidサポート!)

if (typeof Storage !== 'undefined') { // We have local storage support
    localStorage.username = 'Onur'; // to save to local storage
    document.getElementById('uname').value = localStorage.username; // to fetch from local storage
}

したがって、Cookieの例のように、

$(window).on('beforeunload', function (event) {
    saveFormToLocalStorage();
    return false;
});

$(document).on('ready', function (event) {
    fillFormFromLocalStorage()
});

SessionStorage

これはほとんど同じように機能します。 W3Cから:sessionStorageオブジェクトは、1つのセッションのデータのみを格納することを除いて、localStorageオブジェクトと同じです。

サイレント経由でフォームデータをサーバー/ DBに保存AJAX Post(s):

あまり効率的な方法ではありませんが、他の方法が実行できない場合に使用することをお勧めします。 beforeunloadイベントで投稿を作成し、ユーザーにメッセージを表示できます。

$(window).on('beforeunload', function (event) {
    //check if at least one field is filled out.
    //make the AJAX post if filled out.
    return "You are leaving the page without submitting the form...";
});

ページの読み込み時に以前に保存されたデータをサーバーから取得:

君に言っておく;たとえば、ユーザーが「更新」フォームに入力している場合。以前に保存したデータをサーバーからいつでもフェッチして、フォームに入力することができます(重要でないフィールド)。

結論

あなたが本当にこれを必要とし、トラブルに値する場合;フォールバックメカニズムを実装するクロスブラウザソリューションを検討する必要があります。といった:

  • HTML5機能をサポートしている場合。 HTML5 autocomplete attributeを使用します。 (事前にHTMLに属性を埋め込むか、ブラウザーのサポートをテストするときにJavaScript/jQueryを介して設定できます。)
  • ELSE-Storageオブジェクトをサポートしている場合。 localStorageを使用します。
  • ELSE IF [現在のセッションが保存するCookie] + [フォームデータに必要なCookieサイズ] <4096バイト。次にcookiesを使用します。
  • ELSEサーバー側のWebアプリでサイレントにする場合AJAXへのリクエストサーバーにデータを保存]。
  • ELSEはそれをしません。

注:HTML5機能の検出については、 このページ または このページ をご覧ください。または、 Modernizr を使用できます。

HTTPSの問題

[〜#〜] https [〜#〜]を使用すると、すべてのフォームの変更が失われるのはそのためです。 セキュアプロトコルです。フォームは主にユーザー入力に使用され、(おそらく)機密データを含めることができます。したがって、この動作は自然で予期されているようです。上記で提供するソリューションは、HTTPで行うのと同じように機能します。したがって、これですべての懸念がカバーされます。

参考文献:

63