Toggle Passwords

Places an eye-icon at the right side of every password field. Clicking this icon toggles the display of all passwords between •••• and text.(submitting a form will allways revert the fields to type=password, to make sure no auto-completion information is stored for these fields by your browser.)

  1. //
  2. // ==UserScript==
  3. // @name Toggle Passwords
  4. // @namespace http://jaron.nl/
  5. // @description Places an eye-icon at the right side of every password field. Clicking this icon toggles the display of all passwords between •••• and text.(submitting a form will allways revert the fields to type=password, to make sure no auto-completion information is stored for these fields by your browser.)
  6. // @version 2.0.6
  7. // @include *
  8. // @exclude
  9. // ==/UserScript==
  10. (() => {
  11. const hideIconUrl = 'data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 60"><path d="M45 15c-2.8 0-5.3.8-7.6 2.1 1.3 1.1 2.1 2.7 2.1 4.5 0 3.2-2.6 5.8-5.8 5.8-1.2 0-2.4-.4-3.3-1-.2 1.1-.4 2.3-.4 3.6 0 8.3 6.7 15 15 15s15-6.7 15-15-6.7-15-15-15z"/><g stroke="black" stroke-width="7" stroke-miterlimit="10"><path fill="none" stroke-linecap="round" d="M5 55L85 5"/><path d="M85 30S67.1 55 45 55 5 30 5 30 22.9 5 45 5s40 25 40 25z" fill="none"/></g></svg>';
  12. const showIconUrl = 'data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 60"><style></style><g id="Layer_4"><path d="M85 30S67.1 55 45 55 5 30 5 30 22.9 5 45 5s40 25 40 25z" fill="none" stroke="black" stroke-width="7" stroke-miterlimit="10"/><path d="M45 15c-2.8 0-5.3.8-7.6 2.1 1.3 1.1 2.1 2.7 2.1 4.5 0 3.2-2.6 5.8-5.8 5.8-1.2 0-2.4-.4-3.3-1-.2 1.1-.4 2.3-.4 3.6 0 8.3 6.7 15 15 15s15-6.7 15-15-6.7-15-15-15z"/></g></svg>'
  13. const css = `
  14. .tggl-pw {
  15. --icon-width: 18px;
  16. --icon-height: calc(2 * var(--icon-width) / 3);
  17. --pd-v: 10px;
  18. --pd-h: 15px;
  19.  
  20. position: relative;
  21. display: inline-block;
  22. border-radius: 5px;
  23. background: white;
  24. vertical-align: middle;
  25. }
  26.  
  27. .tggl-pw__btn {
  28. --hide-icon: url('${hideIconUrl}');
  29.  
  30. --show-icon: url('${showIconUrl}');
  31.  
  32. position: absolute;
  33. z-index: 1;
  34. top: 50%;
  35. right: 5px;
  36. width: 100%;
  37. height: 100%;
  38. border: none;
  39. border-radius: 3px;
  40. padding: var(--pd-v) var(--pd-h);
  41. width: 0;
  42. height: 0;
  43. transform: translateY(-50%);
  44. cursor: pointer;
  45. background: var(--show-icon) center center no-repeat;
  46. background-size: var(--icon-width) var(--icon-height);
  47. background-color: white;
  48. opacity: 0.5;
  49. transition: opacity 0.3s ease-in-out;
  50. }
  51.  
  52. .tggl-pw__btn:hover,
  53. .tggl-pw__btn:focus {
  54. opacity: 0.8;
  55. }
  56.  
  57. .tggl-pw__btn:hover {
  58. outline: none;
  59. }
  60.  
  61. .tggl-pw__btn--hide {
  62. background-image: var(--hide-icon);
  63. }
  64. `;
  65.  
  66. let pwFields = [];
  67. let allToggleBtns = [];
  68. let stylesAdded = false;
  69. const attributes = {
  70. id: 'data-toggl-pw-id',
  71. form: 'data-toggl-pw-form'
  72. };
  73.  
  74. /**
  75. * add a toggle to a password field
  76. * @returns {undefined}
  77. */
  78. const addToggle = function(pwField) {
  79. const span = document.createElement('span');
  80. const btn = document.createElement('button');
  81. const id = Math.floor(1000000 * Math.random());
  82. pwField.setAttribute(attributes.id, id);
  83. span.classList.add('tggl-pw');
  84. btn.classList.add('tggl-pw__btn');
  85. btn.type = 'button';
  86. btn.title = 'toggle all password fields';
  87. btn.setAttribute(attributes.id, id);
  88. btn.tabIndex = -1;
  89. span.appendChild(btn);
  90. btn.addEventListener('click', toggleAllFields);
  91. pwField.after(span);
  92. allToggleBtns.push(btn);
  93. };
  94.  
  95. /**
  96. * toggle all password fields
  97. * @returns {undefined}
  98. */
  99. const toggleAllFields = function(e) {
  100. let newType;
  101. const tgtId = e.target.getAttribute(attributes.id);
  102. pwFields.forEach((pwField) => {
  103. if (!newType) {
  104. // when we get to first field; this will determine all field's new type
  105. newType = (pwField.type === 'password') ? 'text' : 'password';
  106. }
  107. pwField.type = newType;
  108.  
  109. allToggleBtns.forEach((btn) => {
  110. if (pwField.type === 'password') {
  111. btn.classList.remove('tggl-pw__btn--hide');
  112. } else {
  113. btn.classList.add('tggl-pw__btn--hide');
  114. }
  115. });
  116.  
  117. });
  118. if (newType === 'text') {
  119. document.querySelector(`[data-toggl-pw-id="${tgtId}"]`).select();
  120. }
  121. };
  122.  
  123. /**
  124. * reset all password fields to type password
  125. * @returns {undefined}
  126. */
  127. const resetAllPwFields = function(e) {
  128. pwFields.forEach(pwField => { pwField.type = 'password'; })
  129. };
  130.  
  131. /**
  132. * initialize
  133. * @returns {undefined}
  134. */
  135. const init = function(isFirstRun = true) {
  136. pwFields = document.querySelectorAll(`input[type='password']`) ;
  137. if (pwFields.length > 0) {
  138. pwFields.forEach((pwField) => {
  139. if (pwField.getAttribute(attributes.id) === null) {
  140. // on second run, only add new fields
  141. addToggle(pwField);
  142. const currForm = pwField.closest('form');
  143. if (currForm && currForm.getAttribute(attributes.form) !== 'true') {
  144. currForm.addEventListener('submit', resetAllPwFields);
  145. currForm.setAttribute(attributes.form, true);
  146. }
  147. }
  148. });
  149.  
  150. if (!stylesAdded) {
  151. // don't add styles again on second run if they had already been added on first run
  152. const styles = document.createElement('style');
  153. styles.innerHTML = css;
  154. document.querySelector('head').appendChild(styles);
  155. stylesAdded = true;
  156. }
  157. }
  158.  
  159. if (isFirstRun) {
  160. // sometimes password field get inserted by script, so they may not yet be present yet. re-run init again in 1000ms to catch those cases too
  161. setTimeout(() => { init(false); }, 1000);
  162. }
  163. };
  164.  
  165. init();
  166. })();