console.log(`%c
╔════╗─────────────╔╗───────────────╔╗──────╔════╗╔═══╗╔═══╗╔═╗─╔╗╔═══╗──╔═══╗╔═══╗╔═══╗
║╔╗╔╗║─────────────║║───────────────║║──────║╔╗╔╗║║╔═╗║║╔═╗║║║╚╗║║║╔═╗║──║╔═╗║║╔═╗║║╔═╗║
╚╝║║╚╝╔╗─╔╗╔══╗╔══╗║║───╔══╗╔══╗╔══╗║╚═╦╗─╔╗╚╝║║╚╝║╚═╝║║║─║║║╔╗╚╝║║║─╚╝──║╚═╝║║╚═╝║║║─║║
──║║──║║─║║║╔╗║║║═╣║║─╔╗║║═╣║══╣║══╣║╔╗║║─║║──║║──║╔╗╔╝║║─║║║║╚╗║║║║╔═╗──║╔══╝║╔╗╔╝║║─║║
──║║──║╚═╝║║╚╝║║║═╣║╚═╝║║║═╣╠══║╠══║║╚╝║╚═╝║──║║──║║║╚╗║╚═╝║║║─║║║║╚╩═║╔╗║║───║║║╚╗║╚═╝║
──╚╝──╚═╗╔╝║╔═╝╚══╝╚═══╝╚══╝╚══╝╚══╝╚══╩═╗╔╝──╚╝──╚╝╚═╝╚═══╝╚╝─╚═╝╚═══╝╚╝╚╝───╚╝╚═╝╚═══╝
──────╔═╝║─║║──────────────────────────╔═╝║
──────╚══╝─╚╝──────────────────────────╚══╝
TypeLess - Auto Form Filler
v1.0.5 by TRONG.PRO
`, 'color: #667eea; font-weight: bold;');
// Enhanced form filling utilities
const EnhancedFormUtils = {
// Set value using native setter (React/Vue compatible)
setNativeValue(element, value) {
try {
// Normalize date format for HTML5 date inputs
if (element instanceof HTMLInputElement && element.type === 'date' && typeof value === 'string') {
value = value.replace(/\//g, '-');
}
// valueSetter fallback logic
let valueSetter = null;
if (element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement ||
element instanceof HTMLSelectElement) {
const proto = Object.getPrototypeOf(element);
valueSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
if (!valueSetter && element instanceof HTMLInputElement) {
valueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
}
}
if (valueSetter) {
valueSetter.call(element, value);
} else {
element.value = value;
}
// Dispatch events
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
// Try InputEvent
try {
const inputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
data: String(value)
});
element.dispatchEvent(inputEvent);
} catch (e) {
// Ignore InputEvent errors
}
return true;
} catch (err) {
// Silently fall back to standard assignment if advanced method fails
// This avoids "DOMException" noise in console for some edge cases
try {
element.value = value;
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
} catch (e) {
// If even simple assignment fails, it's likely a read-only or invalid element
// console.warn('[Enhanced] Final fallback failed:', e);
}
return false;
}
},
// Apply value with enhanced event dispatching
applyValue(element, value) {
// ── ComboBox check (FineUI / combo-wrap) — must come before standard text path ──
// For DDLs WITH a $Value hidden: applyComboBoxValue sets hidden (code) + display.
// For DDLs WITHOUT a $Value hidden (e.g. ddlzmb, ddlwh): applyComboBoxValue falls
// through to the text-only path — same net effect as setNativeValue.
//
// NOTE: SmartFill does not supply a displayText here. applyComboBoxValue will use
// the code value as both hidden and display (functional, slightly ugly display).
if (element.tagName === 'INPUT' && element.type !== 'hidden' &&
element.type !== 'checkbox' && element.type !== 'radio' &&
this.isWebFormsComboBox(element)) {
this.applyComboBoxValue(element, value /*, displayText = undefined */);
return;
}
if (element.tagName === 'SELECT') {
// For select elements
const option = Array.from(element.options).find(opt =>
opt.value === value || opt.text === value
);
if (option) {
element.value = option.value;
element.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
}
} else if (element.type === 'checkbox') {
// For checkboxes
const shouldCheck = value === 'on' || value === 'true' || value === true || value === '1' || value === 'checked';
const shouldUncheck = value === 'off' || value === 'false' || value === false || value === '0' || value === 'unchecked';
if (shouldCheck) {
element.checked = true;
} else if (shouldUncheck) {
element.checked = false;
}
element.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
} else if (element.type === 'radio') {
// For radio: check if this radio's value matches the saved value
// SmartFill returns the value of the radio it wants to check
if (value && element.value === value) {
element.checked = true;
element.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
}
} else {
// For text inputs and textareas - use enhanced setter
this.setNativeValue(element, value);
}
},
// ═══════════════════════════════════════════════════════════════════════════
// JS ComboBox Support — FineUI / ExtJS / custom combo-wrap patterns
//
// Learned from real DOM (repair_input_vie_sap.aspx + _report_.html):
//
// ── FineUI DDL (ASP.NET WebForms) ──────────────────────────────────────────
// Wrapper:
// Text input:
// Hidden input: ← server reads THIS
// List:
//
// CRITICAL: some DDLs have NO $Value hidden (ddlzmb, ddlwh, ddlyjgg, ddldgwh).
// For those, text IS the submitted value → treat as normal text input.
//
// ── _report_.html combo-wrap ────────────────────────────────────────────────
// Wrapper:
// Text input: ← NO name attribute!
// Hidden input:
// → Must detect via container, not via text-input name lookup
//
// ── Page's hidden-value interceptor ────────────────────────────────────────
// The page overrides hidden.value via Object.defineProperty (instance property).
// To trigger the page's setter (which fires change/input events + badge update),
// we MUST use assignment hidden.value = x — NOT the prototype native setter.
// Using the prototype setter bypasses the instance override and breaks the page.
//
// ── No F() / Ext global API on this page ───────────────────────────────────
// buildDDL() is a local function; the fallback DOM approach is the real path.
// ═══════════════════════════════════════════════════════════════════════════
/**
* Detect if a text input element is paired with a hidden "$Value" input,
* indicating it is a JS ComboBox whose real submit value lives in the hidden input.
*
* Detection order (most-specific first):
* 1. FineUI id suffix — id ends with "-inputEl" and a sibling "-hiddenEl" exists
* 2. Container-based — parent has class f-ddl-wrap or combo-wrap with a hidden $Value child
* 3. Name-based — a document-level hidden input exists with name = textName + "$Value"
*/
isWebFormsComboBox(element) {
if (!element || element.tagName !== 'INPUT') return false;
if (element.type === 'hidden' || element.type === 'checkbox' || element.type === 'radio') return false;
const doc = element.ownerDocument || document;
const id = element.id || '';
// 1. FineUI pattern: id = "ContentPanel2_ddlblyy-inputEl"
// → sibling hidden id = "ContentPanel2_ddlblyy-hiddenEl"
if (id.endsWith('-inputEl')) {
const hiddenId = id.replace(/-inputEl$/, '-hiddenEl');
if (doc.getElementById(hiddenId)) return true;
}
// 2. Container-based: parent .f-ddl-wrap or .combo-wrap has a hidden $Value child
const container = element.closest('.f-ddl-wrap, .combo-wrap, .f-combo-wrap');
if (container) {
const hidden = this._findHiddenValueInContainer(container);
if (hidden) return true;
}
// 3. Name-based fallback: look for a document-wide hidden with name = name + "$Value"
const name = element.name || '';
if (name && this._findComboBoxHiddenByName(name, doc)) return true;
return false;
},
/**
* Find the hidden "$Value" input inside a wrapper container (.f-ddl-wrap / .combo-wrap).
* The hidden input's name must end with "$Value".
*/
_findHiddenValueInContainer(container) {
return Array.from(container.querySelectorAll('input[type="hidden"]'))
.find(h => h.name && h.name.endsWith('$Value')) || null;
},
/**
* Find the hidden "$Value" input by constructing the expected name from the text input name.
* Used as a last-resort document-wide search.
*/
_findComboBoxHiddenByName(textInputName, doc) {
const hiddenName = textInputName + '$Value';
try {
return doc.querySelector(
`input[type="hidden"][name="${hiddenName.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"]`
) || null;
} catch (e) {
return Array.from(doc.querySelectorAll('input[type="hidden"]'))
.find(h => h.name === hiddenName) || null;
}
},
/**
* Return the hidden "$Value" input paired with this ComboBox text input.
*
* Lookup order mirrors isWebFormsComboBox detection:
* 1. FineUI "-hiddenEl" id suffix
* 2. Container-based ($Value hidden child inside f-ddl-wrap / combo-wrap)
* 3. Document-wide name + "$Value" search
*/
getComboBoxHiddenInput(element) {
const doc = element.ownerDocument || document;
const id = element.id || '';
// 1. FineUI: ContentPanel2_ddlblyy-inputEl → ContentPanel2_ddlblyy-hiddenEl
if (id.endsWith('-inputEl')) {
const hiddenId = id.replace(/-inputEl$/, '-hiddenEl');
const el = doc.getElementById(hiddenId);
if (el) return el;
}
// 2. Container-based
const container = element.closest('.f-ddl-wrap, .combo-wrap, .f-combo-wrap');
if (container) {
const hidden = this._findHiddenValueInContainer(container);
if (hidden) return hidden;
}
// 3. Name-based
const name = element.name || '';
if (name) return this._findComboBoxHiddenByName(name, doc);
return null;
},
/**
* Apply a value to a JS ComboBox control.
*
* The function understands three tiers of framework integration:
*
* Tier 1 — Component API (real FineUI / ExtJS with a running framework):
* F("id").setValue(value) or Ext.getCmp("id").setValue(value)
* These update internal component state + hidden + display all at once.
*
* Tier 2 — DOM fallback (this page's buildDDL / _report_ buildCombo):
* Set hidden.value = codeValue ← use assignment (NOT native setter) so the
* page's Object.defineProperty interceptor fires (badge + events).
* Set textInput.value = displayText
* Dispatch input + change + blur events on the text input.
*
* @param {HTMLInputElement} element Visible text input of the combo.
* @param {string} value Code value to submit, e.g. "BJ".
* @param {string} [displayText] Display label, e.g. "补件 Bù kiện".
* Falls back to value if not supplied.
* @returns {boolean} true if value was successfully applied.
*/
applyComboBoxValue(element, value, displayText) {
if (value === null || value === undefined) return false;
const id = element.id || '';
const display = (displayText !== undefined && displayText !== null && displayText !== '')
? displayText : value;
const win = (element.ownerDocument || document).defaultView || window;
// ── Tier 0: FineUI popup
click ────────────────────────────────────
// Most reliable path: find the dropdown popup
// and click the matching
. This fires FineUI's own
// selection handler — properly updating internal state, hidden $Value,
// and display text all at once.
//
// Works for both code/label DDLs (e.g. hidden="BJ", display="补件 Bù kiện")
// and DDLs where value === display (e.g. "无 không có").
{
const compId = id.endsWith('-inputEl') ? id.replace(/-inputEl$/, '') : id;
if (compId) {
const doc = element.ownerDocument || document;
// Popup
has data-targetid = FineUI component id.
// Try full compId and short name after last underscore.
const candidateIds = [compId];
const lastUs = compId.lastIndexOf('_');
if (lastUs >= 0) candidateIds.push(compId.slice(lastUs + 1));
for (const tid of candidateIds) {
const popup = doc.querySelector(`ul[data-targetid="${CSS.escape(tid)}"]`);
if (!popup) continue;
const li = Array.from(popup.querySelectorAll('li[data-value]'))
.find(el =>
el.dataset.value === value ||
el.dataset.value === display ||
el.textContent.trim() === value ||
el.textContent.trim() === display
);
if (li) {
try {
li.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
li.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
li.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
console.log(`[TypeLess] ComboBox Tier0
.click: "${li.dataset.value}" → [data-targetid="${tid}"]`);
return true;
} catch (_) { /* fall through to Tier 1 */ }
}
}
}
}
// ── Tier 1a: Real FineUI — F("ContentPanel2_ddlblyy-inputEl").setValue() ──
// In a real FineUI deployment, F() accepts the component wrapper id
// (the -inputEl id stripped), not the raw -inputEl id.
if (id && typeof win.F === 'function') {
// Try both the raw id and the wrapper id (without -inputEl suffix)
const ids = [id];
if (id.endsWith('-inputEl')) ids.push(id.replace(/-inputEl$/, ''));
for (const tryId of ids) {
try {
const comp = win.F(tryId);
if (comp && typeof comp.setValue === 'function') {
comp.setValue(value);
console.log(`[TypeLess] ComboBox FineUI F("${tryId}").setValue("${value}")`);
return true;
}
} catch (e) { /* component not found — try next */ }
}
}
// ── Tier 1b: ExtJS — Ext.getCmp().setValue() ──────────────────────────
if (id && win.Ext && typeof win.Ext.getCmp === 'function') {
const ids = [id];
if (id.endsWith('-inputEl')) ids.push(id.replace(/-inputEl$/, ''));
for (const tryId of ids) {
try {
const comp = win.Ext.getCmp(tryId);
if (comp && typeof comp.setValue === 'function') {
comp.setValue(value);
console.log(`[TypeLess] ComboBox ExtJS getCmp("${tryId}").setValue("${value}")`);
return true;
}
} catch (e) { /* not found */ }
}
}
// ── Tier 2: DOM fallback ────────────────────────────────────────────────
// This is the real path for this page's custom buildDDL / buildCombo widgets.
//
// The page intercepts hidden.value via Object.defineProperty (instance property):
// set(v) { origValDesc.set.call(this, v); dispatchEvent('change'); badge.update(); }
//
// We MUST use hidden.value = x (JS property assignment) — NOT the prototype setter
// — so the page's instance-level interceptor fires and syncs the component state.
const hidden = this.getComboBoxHiddenInput(element);
if (hidden) {
// Assignment triggers page's custom setter (interceptor) if defined.
hidden.value = value;
// Also dispatch standard events in case page doesn't intercept.
try { hidden.dispatchEvent(new Event('input', { bubbles: true })); } catch (_) {}
try { hidden.dispatchEvent(new Event('change', { bubbles: true })); } catch (_) {}
// Update display text on the text input.
// Use the native prototype setter here so React/Vue watchers (if any) also fire.
try {
const setter = Object.getOwnPropertyDescriptor(
win.HTMLInputElement.prototype, 'value'
)?.set;
if (setter) setter.call(element, display);
else element.value = display;
} catch (_) {
element.value = display;
}
// Dispatch events that FineUI/custom DDL listeners respond to.
try { element.dispatchEvent(new Event('input', { bubbles: true })); } catch (_) {}
try { element.dispatchEvent(new Event('change', { bubbles: true })); } catch (_) {}
try { element.dispatchEvent(new Event('blur', { bubbles: true })); } catch (_) {}
console.log(
`[TypeLess] ComboBox DOM: hidden="${value}", display="${display}"` +
(element.name ? ` → [name="${element.name}"]` : ` → #${id}`)
);
return true;
}
// No hidden found — this DDL has no $Value (e.g. ddlwh, ddlzmb on this page).
// The text input IS the submitted value; set it normally via setNativeValue.
console.log(`[TypeLess] ComboBox no-hidden: setting text="${display}" → #${id}`);
this.setNativeValue(element, display);
try { element.dispatchEvent(new Event('input', { bubbles: true })); } catch (_) {}
try { element.dispatchEvent(new Event('change', { bubbles: true })); } catch (_) {}
// Return true so the caller knows value was applied (even via text-only path).
return true;
},
// Track last used profile for keyboard shortcuts
saveLastProfile(profile) {
try {
chrome.storage.local.set({ lastUsedProfile: profile });
} catch (error) {
console.error('[Enhanced] Failed to save last profile:', error);
}
},
// Get last used profile
async getLastProfile() {
try {
const result = await chrome.storage.local.get(['lastUsedProfile']);
return result.lastUsedProfile || null;
} catch (error) {
console.error('[Enhanced] Failed to get last profile:', error);
return null;
}
}
};
// Make available globally
if (typeof window !== 'undefined') {
window.EnhancedFormUtils = EnhancedFormUtils;
}
/**
* End of enhanced-utils.js
* ╔══╗─────────╔╗────╔═╗╔═╗╔╗───╔══╗╔═╗╔═╗╔═╦╗╔══╗──╔═╗╔═╗╔═╗
* ╚╗╔╝╔╦╗╔═╗╔═╗║║─╔═╗║═╣║═╣║╚╦╦╗╚╗╔╝║╬║║║║║║║║║╔═╣──║╬║║╬║║║║
* ─║║─║║║║╬║║╩╣║╚╗║╩╣╠═║╠═║║╬║║║─║║─║╗╣║║║║║║║║╚╗║╔╗║╔╝║╗╣║║║
* ─╚╝─╠╗║║╔╝╚═╝╚═╝╚═╝╚═╝╚═╝╚═╬╗║─╚╝─╚╩╝╚═╝╚╩═╝╚══╝╚╝╚╝─╚╩╝╚═╝
* ────╚═╝╚╝──────────────────╚═╝
*/