Skip to content

Commit f2d051a

Browse files
Bug Fixes, Hardening Solution
Bug Fixes, Hardening Solution
1 parent 4ddd06a commit f2d051a

1 file changed

Lines changed: 70 additions & 24 deletions

File tree

‎MerlinAU.asp‎

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ let isFormSubmitting = false;
4141
let FW_NewUpdateVersAvailable = '';
4242
var fwTimeInvalidFromConfig = false;
4343
var fwTimeInvalidMsg = '';
44+
var fwUpdateEstimatedRunDate = 'TBD';
4445
4546
// Order of NVRAM keys to search for 'Model ID' and 'Product ID' //
4647
const modelKeys = ["nvram_odmpid", "nvram_wps_modelnum", "nvram_model", "nvram_build_name"];
@@ -343,8 +344,16 @@ function MerlinAU_TimeSelectFallbackAttach(id) {
343344
var input = document.getElementById(id);
344345
if (!input) return;
345346
347+
// guard: don’t double-attach
348+
if (input.dataset.mauTimeApplied === '1') return;
349+
350+
// Native is fine almost everywhere except Firefox.
346351
var isFirefox = /firefox/i.test(navigator.userAgent);
347-
if (!isFirefox && typeof input.showPicker === 'function') return;
352+
var probe = document.createElement('input');
353+
probe.type = 'time';
354+
var hasNative = (probe.type === 'time'); // real time input, not text
355+
356+
if (hasNative && !isFirefox) return; // keep native on Chrome/Edge/Safari/iOS/Android
348357
349358
function pad(n){ return (n<10?'0':'')+n; }
350359
function parseHHMM(v){
@@ -355,19 +364,29 @@ function MerlinAU_TimeSelectFallbackAttach(id) {
355364
}
356365
357366
var wrap = document.createElement('span');
367+
wrap.className = 'mau-time-fallback';
358368
var selH = document.createElement('select');
359369
var selM = document.createElement('select');
360370
361371
for (var h=0; h<24; h++) selH.add(new Option(pad(h), h));
362-
for (var m=0; m<60; m++) selM.add(new Option(pad(m), m));
372+
var stepSec = parseInt(input.getAttribute('step') || '60', 10);
373+
if (isNaN(stepSec) || stepSec < 60) stepSec = 60;
374+
var minInc = Math.max(1, Math.floor(stepSec/60));
375+
for (var m=0; m<60; m+=minInc) selM.add(new Option(pad(m), m));
363376
364377
input.readOnly = true;
378+
input.dataset.mauTimeApplied = '1';
379+
380+
function emit(el, type){
381+
el.dispatchEvent(new Event(type, { bubbles:true }));
382+
}
365383
366384
function apply(){
367385
var hh = pad(+selH.value), mm = pad(+selM.value);
368386
input.value = hh + ':' + mm;
369387
if (window.ClearTimePickerInvalid) window.ClearTimePickerInvalid(input);
370388
if (window.ValidateTimePicker) window.ValidateTimePicker(input);
389+
emit(input,'input'); emit(input,'change');
371390
}
372391
373392
if (input.nextSibling) input.parentNode.insertBefore(wrap, input.nextSibling);
@@ -376,10 +395,40 @@ function MerlinAU_TimeSelectFallbackAttach(id) {
376395
wrap.appendChild(document.createTextNode(' : '));
377396
wrap.appendChild(selM);
378397
398+
input.setAttribute('aria-hidden','true');
399+
input.tabIndex = -1;
400+
selH.setAttribute('aria-label','Hour');
401+
selM.setAttribute('aria-label','Minute');
402+
379403
var start = parseHHMM(input.value);
380404
selH.value = start.h; selM.value = start.m;
381405
selH.onchange = apply; selM.onchange = apply;
382-
apply(); // normalize displayed HH:MM
406+
var flaggedInvalid = (input.getAttribute('aria-invalid') === 'true') ||
407+
(typeof fwTimeInvalidFromConfig !== 'undefined' && fwTimeInvalidFromConfig) ||
408+
(input.classList && input.classList.contains('Invalid'));
409+
410+
if (!flaggedInvalid) {
411+
apply(); // normalize displayed HH:MM
412+
}
413+
414+
function syncDisabled(){
415+
var dis = !!input.disabled;
416+
selH.disabled = dis; selM.disabled = dis;
417+
wrap.style.opacity = dis ? '0.5' : '1';
418+
}
419+
syncDisabled();
420+
421+
var mo = new MutationObserver(syncDisabled);
422+
mo.observe(input, { attributes:true, attributeFilter:['disabled'] });
423+
424+
function syncFromInput(){
425+
var v = (input.value || '').trim();
426+
if (!v) return; // keep current selects if input was cleared for invalid state
427+
var t = parseHHMM(v);
428+
selH.value = t.h; selM.value = t.m;
429+
}
430+
input.addEventListener('input', syncFromInput);
431+
input.addEventListener('change', syncFromInput);
383432
}
384433
385434
/**---------------------------------------**/
@@ -415,20 +464,21 @@ function MarkTimePickerInvalid(T, msg){
415464
fwTimeInvalidMsg = msg;
416465
417466
var $t = $(T);
418-
$t.addClass('Invalid');
419-
// Remove ONLY namespaced handlers, then rebind
420-
$t.off('.fwtime');
421-
// Show tooltip ONLY on mouseover (no focus)
422-
$t.on('mouseover.fwtime', function(){ return overlib(msg,0,0); });
423-
// Hide tooltip as soon as user stops hovering or edits/leaves the field
424-
$t.on('mouseleave.fwtime input.fwtime keydown.fwtime keyup.fwtime blur.fwtime', function(){
425-
try { nd(); } catch(e){}
426-
});
427-
// if something already opened a tooltip, close it now
467+
$t.addClass('Invalid').off('.fwtime')
468+
.on('mouseover.fwtime', function(){ return overlib(msg,0,0); })
469+
.on('mouseleave.fwtime input.fwtime keydown.fwtime keyup.fwtime blur.fwtime', function(){ try { nd(); } catch(e){} });
470+
428471
try { nd(); } catch(e){}
429472
T.setAttribute('aria-invalid','true');
430473
T.value = '';
431-
setTimeout(function(){ T.focus(); }, 0); // focus no longer triggers tooltip
474+
475+
// If our fallback is attached, focus the hour select instead of the hidden input
476+
if (T.dataset && T.dataset.mauTimeApplied === '1') {
477+
var wrap = T.nextElementSibling; // our <span> wrapper
478+
var sel = wrap && wrap.querySelector('select');
479+
if (sel) { sel.focus(); return; }
480+
}
481+
setTimeout(function(){ T.focus(); }, 0);
432482
}
433483
434484
function ClearTimePickerInvalid(T){
@@ -607,25 +657,22 @@ function SetScheduleDAYofWEEK (cronDAYofWEEK)
607657
/**------------------------------------------**/
608658
// FW_New_Update_Cron_Job_Schedule //
609659
function FWConvertCronScheduleToWebUISettings(rawCronSchedule){
610-
let fwRaw = rawCronSchedule.split(' ');
660+
let s = (rawCronSchedule || '').replace(/\|/g,' ').trim();
661+
let fwRaw = s.split(/\s+/);
611662
let T = document.getElementById('fwScheduleTIME');
612-
let fwSchedD1, fwSchedX, fwXD;
613-
614663
if (!T) return;
615664
616-
// default UI state
617665
ClearTimePickerInvalid(T);
618666
619-
if (rawCronSchedule === 'TBD' || fwRaw.length < 5){
667+
if (fwRaw.length < 5) {
620668
ToggleDaysOfWeek(true, '1');
621-
fwSchedD1 = document.getElementById('fwSchedBoxDAYS1');
622-
if (fwSchedD1){ fwSchedD1.checked = true; fwSchedD1.disabled = false; }
669+
document.getElementById('fwSchedBoxDAYS1')?.setAttribute('checked','');
623670
T.value = '00:00';
624671
T.disabled = false;
625672
return;
626673
}
627674
628-
let rawM = fwRaw[0], rawH = fwRaw[1], rawDM = fwRaw[2], rawMN = fwRaw[3], rawDW = fwRaw[4];
675+
let [rawM, rawH, rawDM, rawMN, rawDW] = fwRaw;
629676
630677
// ---- Time handling: use fwScheduleTime messages only ----
631678
let timeCheck = ValidateHHMMUsingFwScheduleTime(String(rawH) + ':' + String(rawM));
@@ -1679,7 +1726,7 @@ function InitializeFields()
16791726
16801727
let fwUpdateRawCronSchedule = custom_settings.FW_New_Update_Cron_Job_Schedule || 'TBD';
16811728
FWConvertCronScheduleToWebUISettings (fwUpdateRawCronSchedule);
1682-
1729+
MerlinAU_TimeSelectFallbackAttach('fwScheduleTIME');
16831730
SetUpEmailNotificationFields();
16841731
16851732
if (rogFWBuildType)
@@ -2150,7 +2197,6 @@ function initial()
21502197
UpdateScriptVersion();
21512198
showhide('Script_AutoUpdate_SchedText',true);
21522199
showhide('FW_AutoUpdate_CheckSchedText',true);
2153-
MerlinAU_TimeSelectFallbackAttach('fwScheduleTIME');
21542200
21552201
// Debugging iframe behavior //
21562202
var hiddenFrame = document.getElementById('hidden_frame');

0 commit comments

Comments
 (0)