Add support of setPasswordEchoEnabled and setPasswordEchoDuration for password echo feature
https://bugs.webkit.org/show_bug.cgi?id=66052

Patch by Chang Shu <cshu@webkit.org> on 2011-08-18
Reviewed by Alexey Proskuryakov.

Source/WebCore:

Added runtime settings in WebCore.
Added support in window.internals for testing.

Tests: editing/input/password-echo-passnode.html
       editing/input/password-echo-passnode2.html
       editing/input/password-echo-passnode3.html
       editing/input/password-echo-textnode.html

* page/Settings.cpp:
(WebCore::Settings::Settings):
* page/Settings.h:
(WebCore::Settings::setPasswordEchoEnabled):
(WebCore::Settings::passwordEchoEnabled):
(WebCore::Settings::setPasswordEchoDurationInSeconds):
(WebCore::Settings::passwordEchoDurationInSeconds):
* testing/Internals.cpp:
(WebCore::Internals::Internals):
(WebCore::Internals::setPasswordEchoEnabled):
(WebCore::Internals::setPasswordEchoDurationInSeconds):
(WebCore::Internals::reset):
* testing/Internals.h:
* testing/Internals.idl:

Source/WebKit/qt:

Enable password echo under the build flag.

* Api/qwebsettings.cpp:
(QWebSettingsPrivate::apply):

LayoutTests:

Added tests.

* editing/input/password-echo-passnode-expected.txt: Added.
* editing/input/password-echo-passnode.html: Added.
* editing/input/password-echo-passnode2-expected.txt: Added.
* editing/input/password-echo-passnode2.html: Added.
* editing/input/password-echo-passnode3-expected.txt: Added.
* editing/input/password-echo-passnode3.html: Added.
* editing/input/password-echo-textnode-expected.txt: Added.
* editing/input/password-echo-textnode.html: Added.
* editing/input/resources: Added.
* editing/input/resources/password-echo.js: Added.
(secureChar):
(secureText):
(log):
(assert):
(run.else):
(run):
(init):
* platform/wk2/Skipped: No support yet.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@93291 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 302e54b..30dd7c3 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,31 @@
+2011-08-18  Chang Shu  <cshu@webkit.org>
+
+        Add support of setPasswordEchoEnabled and setPasswordEchoDuration for password echo feature
+        https://bugs.webkit.org/show_bug.cgi?id=66052
+
+        Reviewed by Alexey Proskuryakov.
+
+        Added tests.
+
+        * editing/input/password-echo-passnode-expected.txt: Added.
+        * editing/input/password-echo-passnode.html: Added.
+        * editing/input/password-echo-passnode2-expected.txt: Added.
+        * editing/input/password-echo-passnode2.html: Added.
+        * editing/input/password-echo-passnode3-expected.txt: Added.
+        * editing/input/password-echo-passnode3.html: Added.
+        * editing/input/password-echo-textnode-expected.txt: Added.
+        * editing/input/password-echo-textnode.html: Added.
+        * editing/input/resources: Added.
+        * editing/input/resources/password-echo.js: Added.
+        (secureChar):
+        (secureText):
+        (log):
+        (assert):
+        (run.else):
+        (run):
+        (init):
+        * platform/wk2/Skipped: No support yet.
+
 2011-08-18  Wyatt Carss  <wcarss@chromium.org>
 
         Selecting all and inserting text into a page with a frameset leads to a NULL ptr
diff --git a/LayoutTests/editing/input/password-echo-passnode-expected.txt b/LayoutTests/editing/input/password-echo-passnode-expected.txt
new file mode 100644
index 0000000..1b199f2
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-passnode-expected.txt
@@ -0,0 +1,6 @@
+Tests if input chars are secured correctly 
+
+Error: secured right after. expected=false, actual=true
+Success: secured after delay. expected=true, actual=true
+Error: secured right after. expected=false, actual=true
+Success: secured after delay. expected=true, actual=true
diff --git a/LayoutTests/editing/input/password-echo-passnode.html b/LayoutTests/editing/input/password-echo-passnode.html
new file mode 100644
index 0000000..c9141f4
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-passnode.html
@@ -0,0 +1,16 @@
+<html>
+<script src="resources/password-echo.js" language="JavaScript" type="text/JavaScript" ></script>
+<script language="javascript" type="text/javascript">
+var mytests = [
+        //format: [preedit1, preedit2,...,commit_text], secured_right_after?, secured_after_delay? check?
+        [['a'], false, true, true],         // test password (when only 1 char) is only secured after a delay(regular).
+        [['2','2','b'], false, true, true], // test password (when only 1 char) is only secured after a delay(ime).
+    ];
+</script>
+<body onload=init(mytests)>
+
+<p>Tests if input chars are secured correctly
+<input type='password' id='testnode' />
+<ul id="console"></ul>
+</body>
+</html>
diff --git a/LayoutTests/editing/input/password-echo-passnode2-expected.txt b/LayoutTests/editing/input/password-echo-passnode2-expected.txt
new file mode 100644
index 0000000..1b199f2
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-passnode2-expected.txt
@@ -0,0 +1,6 @@
+Tests if input chars are secured correctly 
+
+Error: secured right after. expected=false, actual=true
+Success: secured after delay. expected=true, actual=true
+Error: secured right after. expected=false, actual=true
+Success: secured after delay. expected=true, actual=true
diff --git a/LayoutTests/editing/input/password-echo-passnode2.html b/LayoutTests/editing/input/password-echo-passnode2.html
new file mode 100644
index 0000000..fd1ebf8
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-passnode2.html
@@ -0,0 +1,18 @@
+<html>
+<script src="resources/password-echo.js" language="JavaScript" type="text/JavaScript" ></script>
+<script language="javascript" type="text/javascript">
+var mytests = [
+        //format: [preedit1, preedit2,...,commit_text], secured_right_after?, secured_after_delay? check?
+        [['a'], false, true, false],
+        [['f'], false, true, true],             // test password (when more than 1 char) is only secured after a delay(regular).
+        [['3','3','3','f'], false, true, true],  // test password (when more than 1 char) is only secured after a delay(ime).
+    ];
+</script>
+<body onload=init(mytests)>
+
+<p>Tests if input chars are secured correctly
+<input type='password' id='testnode' />
+<ul id="console"></ul>
+</body>
+</html>
+
diff --git a/LayoutTests/editing/input/password-echo-passnode3-expected.txt b/LayoutTests/editing/input/password-echo-passnode3-expected.txt
new file mode 100644
index 0000000..65b5e0b
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-passnode3-expected.txt
@@ -0,0 +1,4 @@
+Tests if input chars are secured correctly 
+
+Success: secured right after. expected=true, actual=true
+Success: secured after delay. expected=true, actual=true
diff --git a/LayoutTests/editing/input/password-echo-passnode3.html b/LayoutTests/editing/input/password-echo-passnode3.html
new file mode 100644
index 0000000..16cae81
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-passnode3.html
@@ -0,0 +1,17 @@
+<html>
+<script src="resources/password-echo.js" language="JavaScript" type="text/JavaScript" ></script>
+<script language="javascript" type="text/javascript">
+var mytests = [
+        //format: [preedit1, preedit2,...,commit_text], secured_right_after?, secured_after_delay? check?
+        [['a'], false, true, false],
+        [['3','3','3','f'], false, true, false],
+        [['backspace'], true, true, true],       // test password is secured all the time when deleting.
+    ];
+</script>
+<body onload=init(mytests)>
+
+<p>Tests if input chars are secured correctly
+<input type='password' id='testnode' />
+<ul id="console"></ul>
+</body>
+</html>
diff --git a/LayoutTests/editing/input/password-echo-textnode-expected.txt b/LayoutTests/editing/input/password-echo-textnode-expected.txt
new file mode 100644
index 0000000..5eb64f4
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-textnode-expected.txt
@@ -0,0 +1,6 @@
+Test text input is never secured. 
+
+Success: secured right after. expected=false, actual=false
+Success: secured after delay. expected=false, actual=false
+Success: secured right after. expected=false, actual=false
+Success: secured after delay. expected=false, actual=false
diff --git a/LayoutTests/editing/input/password-echo-textnode.html b/LayoutTests/editing/input/password-echo-textnode.html
new file mode 100644
index 0000000..647a8ad
--- /dev/null
+++ b/LayoutTests/editing/input/password-echo-textnode.html
@@ -0,0 +1,17 @@
+<html>
+<script src="resources/password-echo.js" language="JavaScript" type="text/JavaScript" ></script>
+<script language="javascript" type="text/javascript">
+var mytests = [
+        //format: [preedit1, preedit2,...,commit_text], secured_right_after?, secured_after_delay? check?
+        [['a'], false, false, true],            // test text input is never secured, (regular input).
+        [['2','2','b'], false, false, true],    // test text input is never secured (ime input).
+    ];
+</script>
+<body onload=init(mytests)>
+
+<p>Test text input is never secured.
+<input type='text' id='testnode' />
+<ul id="console"></ul>
+</body>
+</html>
+
diff --git a/LayoutTests/editing/input/resources/password-echo.js b/LayoutTests/editing/input/resources/password-echo.js
new file mode 100644
index 0000000..afaf872
--- /dev/null
+++ b/LayoutTests/editing/input/resources/password-echo.js
@@ -0,0 +1,89 @@
+var testnode;
+
+function secureChar()
+{
+    var element = testnode;
+    var securechar = document.defaultView.getComputedStyle(element, "").getPropertyValue("-webkit-text-security");
+    switch(securechar) {
+    case "square":
+        return String.fromCharCode(0x25A0);
+    case "disc":
+        return String.fromCharCode(0x2022);
+    case "circle":
+        return String.fromCharCode(0x25E6);
+    }
+}
+
+function secureText(textLength)
+{
+    var text = "";
+    for (var counter = 0; counter < textLength; counter++)
+        text += secureChar();
+    return text;
+}
+
+function log(msg)
+{
+    var console = document.getElementById("console");
+    var li = document.createElement("li");
+    li.appendChild(document.createTextNode(msg));
+    console.appendChild(li);
+}
+
+function assert(expected, actual, msg)
+{
+    if (expected != actual)
+        log("Error: " + msg + " expected=" + expected + ", actual=" + actual);
+    else
+        log("Success: " + msg + " expected=" + expected + ", actual=" + actual);
+}
+
+function run(tests, testIdx)
+{
+    var expectedSecureTextLen;
+    if (testIdx >= 0) {
+        textInputController.doCommand("moveForward:");
+        if(tests[testIdx][3])
+            assert(tests[testIdx][2], window.find(secureText(testnode.value.length), false, true), "secured after delay.");
+    }
+    testIdx++;
+    if (testIdx >= tests.length) {
+        layoutTestController.notifyDone();
+        return;
+    }
+
+    testnode.focus();
+    textInputController.doCommand("moveForward:");
+
+    var charSequence = tests[testIdx][0];
+    for (var i = 0; i < charSequence.length - 1; i++) {
+        textInputController.setMarkedText(charSequence[i], testnode.value.length, testnode.value.length);
+    }
+    if (charSequence[charSequence.length - 1] == "backspace")
+        textInputController.doCommand("deleteBackward:");
+    else
+        textInputController.insertText(charSequence[charSequence.length - 1]);
+
+    if(tests[testIdx][3])
+        assert(tests[testIdx][1], window.find(secureText(testnode.value.length), false, true), "secured right after.");
+
+    if(tests[testIdx][3])
+        window.setTimeout(function(){ run(tests, testIdx); }, 600);
+    else
+        window.setTimeout(function(){ run(tests, testIdx); }, 0);
+}
+
+function init(tests)
+{
+    if (window.layoutTestController && window.textInputController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+        if (window.internals) {
+            window.internals.setPasswordEchoEnabled(document, true);
+            window.internals.setPasswordEchoDurationInSeconds(document, 0.1);
+            testnode = document.getElementById('testnode');
+            run(tests, -1);
+        }
+    }
+}
+
diff --git a/LayoutTests/platform/wk2/Skipped b/LayoutTests/platform/wk2/Skipped
index c9d51ea..2d49a52 100644
--- a/LayoutTests/platform/wk2/Skipped
+++ b/LayoutTests/platform/wk2/Skipped
@@ -1850,6 +1850,13 @@
 # missing window.internals.createShadowContentElement
 fast/dom/shadow/create-content-element.html
 
+# WebKitTestRunner needs layoutTestController.setPasswordEchoEnabled
+# WebKitTestRunner needs layoutTestController.setPasswordEchoDuration
+editing/input/password-echo-passnode.html
+editing/input/password-echo-passnode2.html
+editing/input/password-echo-passnode3.html
+editing/input/password-echo-textnode.html
+
 ### END OF (2) Classified failures without bug reports (yet)
 ########################################
 
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index c172c32..83f641b 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,33 @@
+2011-08-18  Chang Shu  <cshu@webkit.org>
+
+        Add support of setPasswordEchoEnabled and setPasswordEchoDuration for password echo feature
+        https://bugs.webkit.org/show_bug.cgi?id=66052
+
+        Reviewed by Alexey Proskuryakov.
+
+        Added runtime settings in WebCore.
+        Added support in window.internals for testing.
+
+        Tests: editing/input/password-echo-passnode.html
+               editing/input/password-echo-passnode2.html
+               editing/input/password-echo-passnode3.html
+               editing/input/password-echo-textnode.html
+
+        * page/Settings.cpp:
+        (WebCore::Settings::Settings):
+        * page/Settings.h:
+        (WebCore::Settings::setPasswordEchoEnabled):
+        (WebCore::Settings::passwordEchoEnabled):
+        (WebCore::Settings::setPasswordEchoDurationInSeconds):
+        (WebCore::Settings::passwordEchoDurationInSeconds):
+        * testing/Internals.cpp:
+        (WebCore::Internals::Internals):
+        (WebCore::Internals::setPasswordEchoEnabled):
+        (WebCore::Internals::setPasswordEchoDurationInSeconds):
+        (WebCore::Internals::reset):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2011-08-18  Wyatt Carss  <wcarss@chromium.org>
 
         Selecting all and inserting text into a page with a frameset leads to a NULL ptr
diff --git a/Source/WebCore/page/Settings.cpp b/Source/WebCore/page/Settings.cpp
index e3e02a4..45007c3 100644
--- a/Source/WebCore/page/Settings.cpp
+++ b/Source/WebCore/page/Settings.cpp
@@ -108,6 +108,7 @@
     : m_page(page)
     , m_editableLinkBehavior(EditableLinkDefaultBehavior)
     , m_textDirectionSubmenuInclusionBehavior(TextDirectionSubmenuAutomaticallyIncluded)
+    , m_passwordEchoDurationInSeconds(1)
     , m_minimumFontSize(0)
     , m_minimumLogicalFontSize(0)
     , m_defaultFontSize(0)
@@ -213,6 +214,7 @@
 #endif
     , m_mediaPlaybackRequiresUserGesture(false)
     , m_mediaPlaybackAllowsInline(true)
+    , m_passwordEchoEnabled(false)
     , m_loadsImagesAutomaticallyTimer(this, &Settings::loadsImagesAutomaticallyTimerFired)
 {
     // A Frame may not have been created yet, so we initialize the AtomicString 
diff --git a/Source/WebCore/page/Settings.h b/Source/WebCore/page/Settings.h
index 5ce64f0..51519ad 100644
--- a/Source/WebCore/page/Settings.h
+++ b/Source/WebCore/page/Settings.h
@@ -461,6 +461,12 @@
         void setMediaPlaybackAllowsInline(bool flag) { m_mediaPlaybackAllowsInline = flag; };
         bool mediaPlaybackAllowsInline() const { return m_mediaPlaybackAllowsInline; }
 
+        void setPasswordEchoEnabled(bool flag) { m_passwordEchoEnabled = flag; }
+        bool passwordEchoEnabled() const { return m_passwordEchoEnabled; }
+
+        void setPasswordEchoDurationInSeconds(double durationInSeconds) { m_passwordEchoDurationInSeconds = durationInSeconds; }
+        double passwordEchoDurationInSeconds() const { return m_passwordEchoDurationInSeconds; }
+
     private:
         Page* m_page;
 
@@ -477,6 +483,7 @@
         ScriptFontFamilyMap m_pictographFontFamilyMap;
         EditableLinkBehavior m_editableLinkBehavior;
         TextDirectionSubmenuInclusionBehavior m_textDirectionSubmenuInclusionBehavior;
+        double m_passwordEchoDurationInSeconds;
         int m_minimumFontSize;
         int m_minimumLogicalFontSize;
         int m_defaultFontSize;
@@ -580,6 +587,7 @@
 #endif
         bool m_mediaPlaybackRequiresUserGesture : 1;
         bool m_mediaPlaybackAllowsInline : 1;
+        bool m_passwordEchoEnabled : 1;
 
         Timer<Settings> m_loadsImagesAutomaticallyTimer;
         void loadsImagesAutomaticallyTimerFired(Timer<Settings>*);
diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp
index 261fefa..2695dac 100644
--- a/Source/WebCore/testing/Internals.cpp
+++ b/Source/WebCore/testing/Internals.cpp
@@ -55,6 +55,8 @@
 }
 
 Internals::Internals()
+    : passwordEchoDurationInSecondsBackedUp(false)
+    , passwordEchoEnabledBackedUp(false)
 {
 }
 
@@ -191,9 +193,44 @@
     document->settings()->setForceCompositingMode(enabled);
 }
 
-void Internals::reset(Document*)
+void Internals::setPasswordEchoEnabled(Document* document, bool enabled, ExceptionCode& ec)
 {
-// FIXME: Implement
+    if (!document || !document->settings()) {
+        ec = INVALID_ACCESS_ERR;
+        return;
+    }
+
+    if (!passwordEchoEnabledBackedUp) {
+        passwordEchoEnabledBackup = enabled;
+        passwordEchoEnabledBackedUp = true;
+    }
+    document->settings()->setPasswordEchoEnabled(enabled);
+}
+
+void Internals::setPasswordEchoDurationInSeconds(Document* document, double durationInSeconds, ExceptionCode& ec)
+{
+    if (!document || !document->settings()) {
+        ec = INVALID_ACCESS_ERR;
+        return;
+    }
+
+    if (!passwordEchoDurationInSecondsBackedUp) {
+        passwordEchoDurationInSecondsBackup = durationInSeconds;
+        passwordEchoDurationInSecondsBackedUp = true;
+    }
+    document->settings()->setPasswordEchoDurationInSeconds(durationInSeconds);
+}
+
+void Internals::reset(Document* document)
+{
+    if (!document || !document->settings())
+        return;
+
+    if (passwordEchoDurationInSecondsBackedUp)
+        document->settings()->setPasswordEchoDurationInSeconds(passwordEchoDurationInSecondsBackup);
+
+    if (passwordEchoEnabledBackedUp)
+        document->settings()->setPasswordEchoDurationInSeconds(passwordEchoEnabledBackup);
 }
 
 }
diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h
index 6afe9f5..6892554 100644
--- a/Source/WebCore/testing/Internals.h
+++ b/Source/WebCore/testing/Internals.h
@@ -69,9 +69,18 @@
 
     void setForceCompositingMode(Document*, bool enabled, ExceptionCode&);
 
+    void setPasswordEchoEnabled(Document*, bool enabled, ExceptionCode&);
+    void setPasswordEchoDurationInSeconds(Document*, double durationInSeconds, ExceptionCode&);
+
     static const char* internalsId;
+
 private:
     Internals();
+
+    double passwordEchoDurationInSecondsBackup;
+    bool passwordEchoEnabledBackup : 1;
+    bool passwordEchoDurationInSecondsBackedUp : 1;
+    bool passwordEchoEnabledBackedUp : 1;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl
index 8da0b99..22023f6 100644
--- a/Source/WebCore/testing/Internals.idl
+++ b/Source/WebCore/testing/Internals.idl
@@ -44,6 +44,9 @@
         ClientRect boundingBox(in Element element) raises(DOMException);
 
         void setForceCompositingMode(in Document document, in boolean enabled) raises(DOMException);
+
+        void setPasswordEchoEnabled(in Document document, in boolean enabled) raises(DOMException);
+        void setPasswordEchoDurationInSeconds(in Document document, in double durationInSeconds) raises(DOMException);
     };
 }
 
diff --git a/Source/WebKit/qt/Api/qwebsettings.cpp b/Source/WebKit/qt/Api/qwebsettings.cpp
index 18d3d97..fa162c1 100644
--- a/Source/WebKit/qt/Api/qwebsettings.cpp
+++ b/Source/WebKit/qt/Api/qwebsettings.cpp
@@ -284,6 +284,11 @@
         settings->setNeedsSiteSpecificQuirks(value);
 
         settings->setUsesPageCache(WebCore::pageCache()->capacity());
+
+#if ENABLE(PASSWORD_ECHO)
+        settings->setPasswordEchoEnabled(true);
+        settings->setPasswordEchoDurationInSeconds(1);
+#endif
     } else {
         QList<QWebSettingsPrivate*> settings = *::allSettings();
         for (int i = 0; i < settings.count(); ++i)
diff --git a/Source/WebKit/qt/ChangeLog b/Source/WebKit/qt/ChangeLog
index 2c0643e..c120883 100644
--- a/Source/WebKit/qt/ChangeLog
+++ b/Source/WebKit/qt/ChangeLog
@@ -1,3 +1,15 @@
+2011-08-18  Chang Shu  <cshu@webkit.org>
+
+        Add support of setPasswordEchoEnabled and setPasswordEchoDuration for password echo feature
+        https://bugs.webkit.org/show_bug.cgi?id=66052
+
+        Reviewed by Alexey Proskuryakov.
+
+        Enable password echo under the build flag.
+
+        * Api/qwebsettings.cpp:
+        (QWebSettingsPrivate::apply):
+
 2011-08-16  Chang Shu  <cshu@webkit.org>
 
         Support reset in WebCore::Internals