performance.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /**
  2. * @fileoverview Performance utilities for debouncing, throttling, and optimization
  3. * @author GrabZilla Development Team
  4. * @version 2.1.0
  5. * @since 2024-01-01
  6. */
  7. // UI_CONFIG constants
  8. const UI_CONFIG = {
  9. DEBOUNCE_DELAY: 300,
  10. THROTTLE_DELAY: 300
  11. };
  12. /**
  13. * Performance Utilities
  14. *
  15. * Collection of utilities for optimizing application performance
  16. * including debouncing, throttling, and memory management
  17. */
  18. /**
  19. * Debounce function calls to prevent excessive execution
  20. * @param {Function} func - Function to debounce
  21. * @param {number} wait - Wait time in milliseconds
  22. * @param {Object} options - Debounce options
  23. * @param {boolean} options.leading - Execute on leading edge
  24. * @param {boolean} options.trailing - Execute on trailing edge
  25. * @returns {Function} Debounced function
  26. */
  27. function debounce(func, wait = 300, options = {}) {
  28. let timeout;
  29. let lastArgs;
  30. let lastThis;
  31. let result;
  32. const { leading = false, trailing = true } = options;
  33. function invokeFunc() {
  34. result = func.apply(lastThis, lastArgs);
  35. timeout = lastThis = lastArgs = null;
  36. return result;
  37. }
  38. function leadingEdge() {
  39. timeout = setTimeout(timerExpired, wait);
  40. return leading ? invokeFunc() : result;
  41. }
  42. function timerExpired() {
  43. const timeSinceLastCall = Date.now() - lastCallTime;
  44. if (timeSinceLastCall < wait && timeSinceLastCall >= 0) {
  45. timeout = setTimeout(timerExpired, wait - timeSinceLastCall);
  46. } else {
  47. timeout = null;
  48. if (trailing && lastArgs) {
  49. return invokeFunc();
  50. }
  51. }
  52. }
  53. let lastCallTime = 0;
  54. function debounced(...args) {
  55. lastArgs = args;
  56. lastThis = this;
  57. lastCallTime = Date.now();
  58. const isInvoking = !timeout;
  59. if (isInvoking) {
  60. return leadingEdge();
  61. }
  62. if (!timeout) {
  63. timeout = setTimeout(timerExpired, wait);
  64. }
  65. return result;
  66. }
  67. debounced.cancel = function() {
  68. if (timeout) {
  69. clearTimeout(timeout);
  70. timeout = lastThis = lastArgs = null;
  71. }
  72. };
  73. debounced.flush = function() {
  74. return timeout ? invokeFunc() : result;
  75. };
  76. return debounced;
  77. }
  78. /**
  79. * Throttle function calls to limit execution frequency
  80. * @param {Function} func - Function to throttle
  81. * @param {number} wait - Wait time in milliseconds
  82. * @param {Object} options - Throttle options
  83. * @param {boolean} options.leading - Execute on leading edge
  84. * @param {boolean} options.trailing - Execute on trailing edge
  85. * @returns {Function} Throttled function
  86. */
  87. function throttle(func, wait = 300, options = {}) {
  88. let timeout;
  89. let previous = 0;
  90. let result;
  91. const { leading = true, trailing = true } = options;
  92. function later() {
  93. previous = leading === false ? 0 : Date.now();
  94. timeout = null;
  95. result = func.apply(this, arguments);
  96. }
  97. function throttled(...args) {
  98. const now = Date.now();
  99. if (!previous && leading === false) {
  100. previous = now;
  101. }
  102. const remaining = wait - (now - previous);
  103. if (remaining <= 0 || remaining > wait) {
  104. if (timeout) {
  105. clearTimeout(timeout);
  106. timeout = null;
  107. }
  108. previous = now;
  109. result = func.apply(this, args);
  110. } else if (!timeout && trailing !== false) {
  111. timeout = setTimeout(() => later.apply(this, args), remaining);
  112. }
  113. return result;
  114. }
  115. throttled.cancel = function() {
  116. if (timeout) {
  117. clearTimeout(timeout);
  118. timeout = null;
  119. }
  120. previous = 0;
  121. };
  122. return throttled;
  123. }
  124. /**
  125. * Memoize function results for performance optimization
  126. * @param {Function} func - Function to memoize
  127. * @param {Function} resolver - Custom key resolver function
  128. * @returns {Function} Memoized function
  129. */
  130. function memoize(func, resolver) {
  131. const cache = new Map();
  132. function memoized(...args) {
  133. const key = resolver ? resolver(...args) : JSON.stringify(args);
  134. if (cache.has(key)) {
  135. return cache.get(key);
  136. }
  137. const result = func.apply(this, args);
  138. cache.set(key, result);
  139. return result;
  140. }
  141. memoized.cache = cache;
  142. memoized.clear = () => cache.clear();
  143. return memoized;
  144. }
  145. /**
  146. * Create a function that only executes once
  147. * @param {Function} func - Function to execute once
  148. * @returns {Function} Function that executes only once
  149. */
  150. function once(func) {
  151. let called = false;
  152. let result;
  153. return function(...args) {
  154. if (!called) {
  155. called = true;
  156. result = func.apply(this, args);
  157. }
  158. return result;
  159. };
  160. }
  161. /**
  162. * Batch DOM updates for better performance
  163. * @param {Function} callback - Function containing DOM updates
  164. * @returns {Promise} Promise that resolves after updates
  165. */
  166. function batchDOMUpdates(callback) {
  167. return new Promise(resolve => {
  168. requestAnimationFrame(() => {
  169. callback();
  170. resolve();
  171. });
  172. });
  173. }
  174. /**
  175. * Lazy load function execution until needed
  176. * @param {Function} factory - Function that creates the actual function
  177. * @returns {Function} Lazy-loaded function
  178. */
  179. function lazy(factory) {
  180. let func;
  181. let initialized = false;
  182. return function(...args) {
  183. if (!initialized) {
  184. func = factory();
  185. initialized = true;
  186. }
  187. return func.apply(this, args);
  188. };
  189. }
  190. /**
  191. * Create a timeout-based cache for expensive operations
  192. * @param {number} ttl - Time to live in milliseconds
  193. * @returns {Object} Cache object with get/set/clear methods
  194. */
  195. function createTimeoutCache(ttl = 300000) { // 5 minutes default
  196. const cache = new Map();
  197. const timers = new Map();
  198. return {
  199. get(key) {
  200. return cache.get(key);
  201. },
  202. set(key, value) {
  203. // Clear existing timer
  204. if (timers.has(key)) {
  205. clearTimeout(timers.get(key));
  206. }
  207. // Set value and timer
  208. cache.set(key, value);
  209. const timer = setTimeout(() => {
  210. cache.delete(key);
  211. timers.delete(key);
  212. }, ttl);
  213. timers.set(key, timer);
  214. },
  215. has(key) {
  216. return cache.has(key);
  217. },
  218. delete(key) {
  219. if (timers.has(key)) {
  220. clearTimeout(timers.get(key));
  221. timers.delete(key);
  222. }
  223. return cache.delete(key);
  224. },
  225. clear() {
  226. timers.forEach(timer => clearTimeout(timer));
  227. cache.clear();
  228. timers.clear();
  229. },
  230. size() {
  231. return cache.size;
  232. }
  233. };
  234. }
  235. /**
  236. * Sanitize user input to prevent XSS and injection attacks
  237. * @param {string} input - User input to sanitize
  238. * @param {Object} options - Sanitization options
  239. * @param {boolean} options.allowHTML - Allow safe HTML tags
  240. * @returns {string} Sanitized input
  241. */
  242. function sanitizeInput(input, options = {}) {
  243. if (typeof input !== 'string') {
  244. return '';
  245. }
  246. let sanitized = input.trim();
  247. if (!options.allowHTML) {
  248. // Remove all HTML tags and dangerous characters
  249. sanitized = sanitized
  250. .replace(/[<>]/g, '')
  251. .replace(/javascript:/gi, '')
  252. .replace(/on\w+=/gi, '');
  253. }
  254. return sanitized;
  255. }
  256. /**
  257. * Validate filename patterns for yt-dlp compatibility
  258. * @param {string} pattern - Filename pattern to validate
  259. * @returns {boolean} True if pattern is valid
  260. */
  261. function validateFilenamePattern(pattern) {
  262. if (typeof pattern !== 'string') {
  263. return false;
  264. }
  265. // Check for dangerous characters
  266. const dangerousChars = /[<>:"|?*]/;
  267. if (dangerousChars.test(pattern)) {
  268. return false;
  269. }
  270. // Check for valid yt-dlp placeholders
  271. const validPlaceholders = /%(title|uploader|duration|ext|id|upload_date)s/g;
  272. const placeholders = pattern.match(validPlaceholders);
  273. // Must contain at least title and ext
  274. return placeholders &&
  275. placeholders.includes('%(title)s') &&
  276. placeholders.includes('%(ext)s');
  277. }