utils.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const {parseDeclarations} = require("devtools/shared/css/parsing-utils");
  7. const promise = require("promise");
  8. const {getCSSLexer} = require("devtools/shared/css/lexer");
  9. const {KeyCodes} = require("devtools/client/shared/keycodes");
  10. const HTML_NS = "http://www.w3.org/1999/xhtml";
  11. /**
  12. * Create a child element with a set of attributes.
  13. *
  14. * @param {Element} parent
  15. * The parent node.
  16. * @param {string} tagName
  17. * The tag name.
  18. * @param {object} attributes
  19. * A set of attributes to set on the node.
  20. */
  21. function createChild(parent, tagName, attributes = {}) {
  22. let elt = parent.ownerDocument.createElementNS(HTML_NS, tagName);
  23. for (let attr in attributes) {
  24. if (attributes.hasOwnProperty(attr)) {
  25. if (attr === "textContent") {
  26. elt.textContent = attributes[attr];
  27. } else if (attr === "child") {
  28. elt.appendChild(attributes[attr]);
  29. } else {
  30. elt.setAttribute(attr, attributes[attr]);
  31. }
  32. }
  33. }
  34. parent.appendChild(elt);
  35. return elt;
  36. }
  37. exports.createChild = createChild;
  38. /**
  39. * Append a text node to an element.
  40. *
  41. * @param {Element} parent
  42. * The parent node.
  43. * @param {string} text
  44. * The text content for the text node.
  45. */
  46. function appendText(parent, text) {
  47. parent.appendChild(parent.ownerDocument.createTextNode(text));
  48. }
  49. exports.appendText = appendText;
  50. /**
  51. * Called when a character is typed in a value editor. This decides
  52. * whether to advance or not, first by checking to see if ";" was
  53. * typed, and then by lexing the input and seeing whether the ";"
  54. * would be a terminator at this point.
  55. *
  56. * @param {number} keyCode
  57. * Key code to be checked.
  58. * @param {string} aValue
  59. * Current text editor value.
  60. * @param {number} insertionPoint
  61. * The index of the insertion point.
  62. * @return {Boolean} True if the focus should advance; false if
  63. * the character should be inserted.
  64. */
  65. function advanceValidate(keyCode, value, insertionPoint) {
  66. // Only ";" has special handling here.
  67. if (keyCode !== KeyCodes.DOM_VK_SEMICOLON) {
  68. return false;
  69. }
  70. // Insert the character provisionally and see what happens. If we
  71. // end up with a ";" symbol token, then the semicolon terminates the
  72. // value. Otherwise it's been inserted in some spot where it has a
  73. // valid meaning, like a comment or string.
  74. value = value.slice(0, insertionPoint) + ";" + value.slice(insertionPoint);
  75. let lexer = getCSSLexer(value);
  76. while (true) {
  77. let token = lexer.nextToken();
  78. if (token.endOffset > insertionPoint) {
  79. if (token.tokenType === "symbol" && token.text === ";") {
  80. // The ";" is a terminator.
  81. return true;
  82. }
  83. // The ";" is not a terminator in this context.
  84. break;
  85. }
  86. }
  87. return false;
  88. }
  89. exports.advanceValidate = advanceValidate;
  90. /**
  91. * Create a throttling function wrapper to regulate its frequency.
  92. *
  93. * @param {Function} func
  94. * The function to throttle
  95. * @param {number} wait
  96. * The throttling period
  97. * @param {Object} scope
  98. * The scope to use for func
  99. * @return {Function} The throttled function
  100. */
  101. function throttle(func, wait, scope) {
  102. let timer = null;
  103. return function () {
  104. if (timer) {
  105. clearTimeout(timer);
  106. }
  107. let args = arguments;
  108. timer = setTimeout(function () {
  109. timer = null;
  110. func.apply(scope, args);
  111. }, wait);
  112. };
  113. }
  114. exports.throttle = throttle;
  115. /**
  116. * Event handler that causes a blur on the target if the input has
  117. * multiple CSS properties as the value.
  118. */
  119. function blurOnMultipleProperties(cssProperties) {
  120. return (e) => {
  121. setTimeout(() => {
  122. let props = parseDeclarations(cssProperties.isKnown, e.target.value);
  123. if (props.length > 1) {
  124. e.target.blur();
  125. }
  126. }, 0);
  127. };
  128. }
  129. exports.blurOnMultipleProperties = blurOnMultipleProperties;
  130. /**
  131. * Log the provided error to the console and return a rejected Promise for
  132. * this error.
  133. *
  134. * @param {Error} error
  135. * The error to log
  136. * @return {Promise} A rejected promise
  137. */
  138. function promiseWarn(error) {
  139. console.error(error);
  140. return promise.reject(error);
  141. }
  142. exports.promiseWarn = promiseWarn;