mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-14 03:30:17 +01:00
583 lines
18 KiB
JavaScript
583 lines
18 KiB
JavaScript
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code, released
|
|
* March 31, 1998.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998-1999
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Chip Clark <chipc@netscape.com>
|
|
* Seth Spitzer <sspitzer@netscape.com>
|
|
* Neil Rashbrook <neil@parkwaycc.co.uk>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
|
|
const nsISupportsString = Components.interfaces.nsISupportsString;
|
|
const nsIPromptService = Components.interfaces.nsIPromptService;
|
|
const nsIPrefService = Components.interfaces.nsIPrefService;
|
|
const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
|
|
const nsIClipboardHelper = Components.interfaces.nsIClipboardHelper;
|
|
const nsIAtomService = Components.interfaces.nsIAtomService;
|
|
|
|
const nsSupportsString_CONTRACTID = "@mozilla.org/supports-string;1";
|
|
const nsPrompt_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
|
|
const nsPrefService_CONTRACTID = "@mozilla.org/preferences-service;1";
|
|
const nsClipboardHelper_CONTRACTID = "@mozilla.org/widget/clipboardhelper;1";
|
|
const nsAtomService_CONTRACTID = "@mozilla.org/atom-service;1";
|
|
|
|
const gPromptService = Components.classes[nsPrompt_CONTRACTID].getService(nsIPromptService);
|
|
const gPrefService = Components.classes[nsPrefService_CONTRACTID].getService(nsIPrefService);
|
|
const gPrefBranch = gPrefService.getBranch(null).QueryInterface(Components.interfaces.nsIPrefBranch2);
|
|
const gClipboardHelper = Components.classes[nsClipboardHelper_CONTRACTID].getService(nsIClipboardHelper);
|
|
const gAtomService = Components.classes[nsAtomService_CONTRACTID].getService(nsIAtomService);
|
|
|
|
var gLockAtoms = [gAtomService.getAtom("default"), gAtomService.getAtom("user"), gAtomService.getAtom("locked")];
|
|
// we get these from a string bundle
|
|
var gLockStrs = [];
|
|
var gTypeStrs = [];
|
|
|
|
const PREF_IS_DEFAULT_VALUE = 0;
|
|
const PREF_IS_USER_SET = 1;
|
|
const PREF_IS_LOCKED = 2;
|
|
|
|
var gPrefHash = {};
|
|
var gPrefArray = [];
|
|
var gPrefView = gPrefArray; // share the JS array
|
|
var gFastIndex = 0;
|
|
var gSortedColumn = "prefCol";
|
|
var gSortFunction = null;
|
|
var gSortDirection = 1; // 1 is ascending; -1 is descending
|
|
var gConfigBundle = null;
|
|
|
|
var view = {
|
|
get rowCount() { return gPrefView.length; },
|
|
getCellText : function(index, col) {
|
|
if (!(index in gPrefView))
|
|
return "";
|
|
|
|
var value = gPrefView[index][col.id];
|
|
|
|
switch (col.id) {
|
|
case "lockCol":
|
|
return gLockStrs[value];
|
|
case "typeCol":
|
|
return gTypeStrs[value];
|
|
default:
|
|
return value;
|
|
}
|
|
},
|
|
getRowProperties : function(index, prop) {},
|
|
getCellProperties : function(index, col, prop) {
|
|
if (index in gPrefView)
|
|
prop.AppendElement(gLockAtoms[gPrefView[index].lockCol]);
|
|
},
|
|
getColumnProperties : function(col, prop) {},
|
|
treebox : null,
|
|
selection : null,
|
|
isContainer : function(index) { return false; },
|
|
isContainerOpen : function(index) { return false; },
|
|
isContainerEmpty : function(index) { return false; },
|
|
isSorted : function() { return true; },
|
|
canDrop : function(index, orientation) { return false; },
|
|
drop : function(row, orientation) {},
|
|
setTree : function(out) { this.treebox = out; },
|
|
getParentIndex: function(rowIndex) { return -1; },
|
|
hasNextSibling: function(rowIndex, afterIndex) { return false; },
|
|
getLevel: function(index) { return 1; },
|
|
getImageSrc: function(row, col) { return ""; },
|
|
toggleOpenState : function(index) {},
|
|
cycleHeader: function(col) {
|
|
var index = this.selection.currentIndex;
|
|
if (col.id == gSortedColumn)
|
|
gSortDirection = -gSortDirection;
|
|
if (col.id == gSortedColumn && gFastIndex == gPrefArray.length) {
|
|
gPrefArray.reverse();
|
|
if (gPrefView != gPrefArray)
|
|
gPrefView.reverse();
|
|
if (index >= 0)
|
|
index = gPrefView.length - index - 1;
|
|
}
|
|
else {
|
|
var pref = null;
|
|
if (index >= 0) {
|
|
if (gPrefArray != gPrefView)
|
|
index = gPrefView.length - index - 1;
|
|
else
|
|
pref = gPrefArray[index];
|
|
}
|
|
var old = document.getElementById(gSortedColumn);
|
|
old.setAttribute("sortDirection", "");
|
|
gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
|
|
if (gPrefView != gPrefArray) {
|
|
if (col.id == gSortedColumn)
|
|
gPrefView.reverse();
|
|
else
|
|
gPrefView.sort(gSortFunction);
|
|
}
|
|
gSortedColumn = col.id;
|
|
if (pref)
|
|
index = getIndexOfPref(pref);
|
|
}
|
|
col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
|
|
this.treebox.invalidate();
|
|
if (index >= 0) {
|
|
this.selection.select(index);
|
|
this.treebox.ensureRowIsVisible(index);
|
|
}
|
|
gFastIndex = gPrefArray.length;
|
|
},
|
|
selectionChanged : function() {},
|
|
cycleCell: function(row, col) {},
|
|
isEditable: function(row, col) {return false; },
|
|
setCellValue: function(row, col, value) {},
|
|
setCellText: function(row, col, value) {},
|
|
performAction: function(action) {},
|
|
performActionOnRow: function(action, row) {},
|
|
performActionOnCell: function(action, row, col) {},
|
|
isSeparator: function(index) {return false; }
|
|
};
|
|
|
|
// find the index in gPrefView of a pref object
|
|
// or -1 if it does not exist in the filtered view
|
|
function getViewIndexOfPref(pref)
|
|
{
|
|
var low = -1, high = gPrefView.length;
|
|
var index = (low + high) >> 1;
|
|
while (index > low) {
|
|
var mid = gPrefView[index];
|
|
if (mid == pref)
|
|
return index;
|
|
if (gSortFunction(mid, pref) < 0)
|
|
low = index;
|
|
else
|
|
high = index;
|
|
index = (low + high) >> 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// find the index in gPrefArray of a pref object
|
|
// either one that was looked up in gPrefHash
|
|
// or in case it was moved after sorting
|
|
function getIndexOfPref(pref)
|
|
{
|
|
var low = -1, high = gFastIndex;
|
|
var index = (low + high) >> 1;
|
|
while (index > low) {
|
|
var mid = gPrefArray[index];
|
|
if (mid == pref)
|
|
return index;
|
|
if (gSortFunction(mid, pref) < 0)
|
|
low = index;
|
|
else
|
|
high = index;
|
|
index = (low + high) >> 1;
|
|
}
|
|
|
|
for (index = gFastIndex; index < gPrefArray.length; ++index)
|
|
if (gPrefArray[index] == pref)
|
|
break;
|
|
return index;
|
|
}
|
|
|
|
function getNearestIndexOfPref(pref)
|
|
{
|
|
var low = -1, high = gFastIndex;
|
|
var index = (low + high) >> 1;
|
|
while (index > low) {
|
|
if (gSortFunction(gPrefArray[index], pref) < 0)
|
|
low = index;
|
|
else
|
|
high = index;
|
|
index = (low + high) >> 1;
|
|
}
|
|
return high;
|
|
}
|
|
|
|
var gPrefListener =
|
|
{
|
|
observe: function(subject, topic, prefName)
|
|
{
|
|
if (topic != "nsPref:changed")
|
|
return;
|
|
|
|
if (/^capability\./.test(prefName)) // avoid displaying "private" preferences
|
|
return;
|
|
|
|
var index = gPrefArray.length;
|
|
if (prefName in gPrefHash) {
|
|
index = getViewIndexOfPref(gPrefHash[prefName]);
|
|
fetchPref(prefName, getIndexOfPref(gPrefHash[prefName]));
|
|
if (index >= 0) {
|
|
// Might need to update the filtered view
|
|
gPrefView[index] = gPrefHash[prefName];
|
|
view.treebox.invalidateRow(index);
|
|
}
|
|
if (gSortedColumn == "lockCol" || gSortedColumn == "valueCol")
|
|
gFastIndex = 1; // TODO: reinsert and invalidate range
|
|
} else {
|
|
fetchPref(prefName, index);
|
|
if (index == gFastIndex) {
|
|
// Keep the array sorted by reinserting the pref object
|
|
var pref = gPrefArray.pop();
|
|
index = getNearestIndexOfPref(pref);
|
|
gPrefArray.splice(index, 0, pref);
|
|
gFastIndex = gPrefArray.length;
|
|
}
|
|
if (gPrefView == gPrefArray)
|
|
view.treebox.rowCountChanged(index, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
function prefObject(prefName, prefIndex)
|
|
{
|
|
this.prefCol = prefName;
|
|
}
|
|
|
|
prefObject.prototype =
|
|
{
|
|
lockCol: PREF_IS_DEFAULT_VALUE,
|
|
typeCol: nsIPrefBranch.PREF_STRING,
|
|
valueCol: ""
|
|
};
|
|
|
|
function fetchPref(prefName, prefIndex)
|
|
{
|
|
var pref = new prefObject(prefName);
|
|
|
|
gPrefHash[prefName] = pref;
|
|
gPrefArray[prefIndex] = pref;
|
|
|
|
if (gPrefBranch.prefIsLocked(prefName))
|
|
pref.lockCol = PREF_IS_LOCKED;
|
|
else if (gPrefBranch.prefHasUserValue(prefName))
|
|
pref.lockCol = PREF_IS_USER_SET;
|
|
|
|
try {
|
|
switch (gPrefBranch.getPrefType(prefName)) {
|
|
case gPrefBranch.PREF_BOOL:
|
|
pref.typeCol = gPrefBranch.PREF_BOOL;
|
|
// convert to a string
|
|
pref.valueCol = gPrefBranch.getBoolPref(prefName).toString();
|
|
break;
|
|
case gPrefBranch.PREF_INT:
|
|
pref.typeCol = gPrefBranch.PREF_INT;
|
|
// convert to a string
|
|
pref.valueCol = gPrefBranch.getIntPref(prefName).toString();
|
|
break;
|
|
default:
|
|
case gPrefBranch.PREF_STRING:
|
|
pref.valueCol = gPrefBranch.getComplexValue(prefName, nsISupportsString).data;
|
|
// Try in case it's a localized string (will throw an exception if not)
|
|
if (pref.lockCol == PREF_IS_DEFAULT_VALUE &&
|
|
/^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.valueCol))
|
|
pref.valueCol = gPrefBranch.getComplexValue(prefName, nsIPrefLocalizedString).data;
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
// Also catch obscure cases in which you can't tell in advance
|
|
// that the pref exists but has no user or default value...
|
|
}
|
|
}
|
|
|
|
function onConfigLoad()
|
|
{
|
|
// Load strings
|
|
gConfigBundle = document.getElementById("configBundle");
|
|
|
|
gLockStrs[PREF_IS_DEFAULT_VALUE] = gConfigBundle.getString("default");
|
|
gLockStrs[PREF_IS_USER_SET] = gConfigBundle.getString("user");
|
|
gLockStrs[PREF_IS_LOCKED] = gConfigBundle.getString("locked");
|
|
|
|
gTypeStrs[nsIPrefBranch.PREF_STRING] = gConfigBundle.getString("string");
|
|
gTypeStrs[nsIPrefBranch.PREF_INT] = gConfigBundle.getString("int");
|
|
gTypeStrs[nsIPrefBranch.PREF_BOOL] = gConfigBundle.getString("bool");
|
|
|
|
var showWarning = gPrefBranch.getBoolPref("general.warnOnAboutConfig");
|
|
|
|
if (showWarning)
|
|
document.getElementById("warningButton").focus();
|
|
else
|
|
ShowPrefs();
|
|
}
|
|
|
|
// Unhide the warning message
|
|
function ShowPrefs()
|
|
{
|
|
var prefCount = { value: 0 };
|
|
var prefArray = gPrefBranch.getChildList("", prefCount);
|
|
|
|
for (var i = 0; i < prefCount.value; ++i)
|
|
{
|
|
var prefName = prefArray[i];
|
|
if (/^capability\./.test(prefName)) // avoid displaying "private" preferences
|
|
continue;
|
|
|
|
fetchPref(prefName, gPrefArray.length);
|
|
}
|
|
|
|
var descending = document.getElementsByAttribute("sortDirection", "descending");
|
|
if (descending.item(0)) {
|
|
gSortedColumn = descending[0].id;
|
|
gSortDirection = -1;
|
|
}
|
|
else {
|
|
var ascending = document.getElementsByAttribute("sortDirection", "ascending");
|
|
if (ascending.item(0))
|
|
gSortedColumn = ascending[0].id;
|
|
else
|
|
document.getElementById(gSortedColumn).setAttribute("sortDirection", "ascending");
|
|
}
|
|
gSortFunction = gSortFunctions[gSortedColumn];
|
|
gPrefArray.sort(gSortFunction);
|
|
gFastIndex = gPrefArray.length;
|
|
|
|
gPrefBranch.addObserver("", gPrefListener, false);
|
|
|
|
document.getElementById("configTree").view = view;
|
|
|
|
document.getElementById("configDeck").setAttribute("selectedIndex", 1);
|
|
if (!document.getElementById("showWarningNextTime").checked)
|
|
gPrefBranch.setBoolPref("general.warnOnAboutConfig", false);
|
|
|
|
document.getElementById("textbox").focus();
|
|
}
|
|
|
|
function onConfigUnload()
|
|
{
|
|
if (document.getElementById("configDeck").getAttribute("selectedIndex") == 1) {
|
|
gPrefBranch.removeObserver("", gPrefListener);
|
|
document.getElementById("configTree").view = null;
|
|
}
|
|
}
|
|
|
|
function FilterPrefs()
|
|
{
|
|
var substring = document.getElementById("textbox").value.toLowerCase();
|
|
var prefCol = view.selection.currentIndex < 0 ? null : gPrefView[view.selection.currentIndex].prefCol;
|
|
var array = gPrefView;
|
|
gPrefView = gPrefArray;
|
|
if (substring) {
|
|
gPrefView = [];
|
|
for (var i = 0; i < gPrefArray.length; ++i)
|
|
if (gPrefArray[i].prefCol.toLowerCase().indexOf(substring) >= 0)
|
|
gPrefView.push(gPrefArray[i]);
|
|
if (gFastIndex < gPrefArray.length)
|
|
gPrefView.sort(gSortFunction);
|
|
}
|
|
view.treebox.invalidate();
|
|
view.treebox.rowCountChanged(array.length, gPrefView.length - array.length);
|
|
gotoPref(prefCol);
|
|
document.getElementById("button").disabled = !substring;
|
|
}
|
|
|
|
function ClearFilter(button)
|
|
{
|
|
var textbox = document.getElementById("textbox");
|
|
textbox.value = "";
|
|
textbox.focus();
|
|
FilterPrefs();
|
|
}
|
|
|
|
function prefColSortFunction(x, y)
|
|
{
|
|
if (x.prefCol > y.prefCol)
|
|
return gSortDirection;
|
|
if (x.prefCol < y.prefCol)
|
|
return -gSortDirection;
|
|
return 0;
|
|
}
|
|
|
|
function lockColSortFunction(x, y)
|
|
{
|
|
if (x.lockCol != y.lockCol)
|
|
return gSortDirection * (y.lockCol - x.lockCol);
|
|
return prefColSortFunction(x, y);
|
|
}
|
|
|
|
function typeColSortFunction(x, y)
|
|
{
|
|
if (x.typeCol != y.typeCol)
|
|
return gSortDirection * (y.typeCol - x.typeCol);
|
|
return prefColSortFunction(x, y);
|
|
}
|
|
|
|
function valueColSortFunction(x, y)
|
|
{
|
|
if (x.valueCol > y.valueCol)
|
|
return gSortDirection;
|
|
if (x.valueCol < y.valueCol)
|
|
return -gSortDirection;
|
|
return prefColSortFunction(x, y);
|
|
}
|
|
|
|
const gSortFunctions =
|
|
{
|
|
prefCol: prefColSortFunction,
|
|
lockCol: lockColSortFunction,
|
|
typeCol: typeColSortFunction,
|
|
valueCol: valueColSortFunction
|
|
};
|
|
|
|
function updateContextMenu()
|
|
{
|
|
var lockCol = PREF_IS_LOCKED;
|
|
var typeCol = nsIPrefBranch.PREF_STRING;
|
|
var valueCol = "";
|
|
var copyDisabled = true;
|
|
|
|
if (view.selection.currentIndex >= 0) {
|
|
var prefRow = gPrefView[view.selection.currentIndex];
|
|
lockCol = prefRow.lockCol;
|
|
typeCol = prefRow.typeCol;
|
|
valueCol = prefRow.valueCol;
|
|
copyDisabled = false;
|
|
}
|
|
|
|
var copyName = document.getElementById("copyName");
|
|
copyName.setAttribute("disabled", copyDisabled);
|
|
|
|
var copyValue = document.getElementById("copyValue");
|
|
copyValue.setAttribute("disabled", copyDisabled);
|
|
|
|
var resetSelected = document.getElementById("resetSelected");
|
|
resetSelected.setAttribute("disabled", lockCol != PREF_IS_USER_SET);
|
|
|
|
var canToggle = typeCol == nsIPrefBranch.PREF_BOOL && valueCol != "";
|
|
|
|
var modifySelected = document.getElementById("modifySelected");
|
|
modifySelected.setAttribute("disabled", lockCol == PREF_IS_LOCKED);
|
|
modifySelected.hidden = canToggle;
|
|
|
|
var toggleSelected = document.getElementById("toggleSelected");
|
|
toggleSelected.setAttribute("disabled", lockCol == PREF_IS_LOCKED);
|
|
toggleSelected.hidden = !canToggle;
|
|
}
|
|
|
|
function copyName()
|
|
{
|
|
gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].prefCol);
|
|
}
|
|
|
|
function copyValue()
|
|
{
|
|
gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].valueCol);
|
|
}
|
|
|
|
function ModifySelected()
|
|
{
|
|
if (view.selection.currentIndex >= 0)
|
|
ModifyPref(gPrefView[view.selection.currentIndex]);
|
|
}
|
|
|
|
function ResetSelected()
|
|
{
|
|
var entry = gPrefView[view.selection.currentIndex];
|
|
gPrefBranch.clearUserPref(entry.prefCol);
|
|
}
|
|
|
|
function NewPref(type)
|
|
{
|
|
var result = { value: "" };
|
|
var dummy = { value: 0 };
|
|
if (gPromptService.prompt(window,
|
|
gConfigBundle.getFormattedString("new_title", [gTypeStrs[type]]),
|
|
gConfigBundle.getString("new_prompt"),
|
|
result,
|
|
null,
|
|
dummy) && result.value) {
|
|
var pref;
|
|
if (result.value in gPrefHash)
|
|
pref = gPrefHash[result.value];
|
|
else
|
|
pref = { prefCol: result.value, lockCol: PREF_IS_DEFAULT_VALUE, typeCol: type, valueCol: "" };
|
|
if (ModifyPref(pref))
|
|
setTimeout(gotoPref, 0, result.value);
|
|
}
|
|
}
|
|
|
|
function gotoPref(pref)
|
|
{
|
|
// make sure the pref exists and is displayed in the current view
|
|
var index = pref in gPrefHash ? getViewIndexOfPref(gPrefHash[pref]) : -1;
|
|
if (index >= 0) {
|
|
view.selection.select(index);
|
|
view.treebox.ensureRowIsVisible(index);
|
|
} else {
|
|
view.selection.clearSelection();
|
|
view.selection.currentIndex = -1;
|
|
}
|
|
}
|
|
|
|
function ModifyPref(entry)
|
|
{
|
|
if (entry.lockCol == PREF_IS_LOCKED)
|
|
return false;
|
|
var title = gConfigBundle.getFormattedString("modify_title", [gTypeStrs[entry.typeCol]]);
|
|
if (entry.typeCol == nsIPrefBranch.PREF_BOOL) {
|
|
var check = { value: entry.valueCol == "false" };
|
|
if (!entry.valueCol && !gPromptService.select(window, title, entry.prefCol, 2, [false, true], check))
|
|
return false;
|
|
gPrefBranch.setBoolPref(entry.prefCol, check.value);
|
|
} else {
|
|
var result = { value: entry.valueCol };
|
|
var dummy = { value: 0 };
|
|
if (!gPromptService.prompt(window, title, entry.prefCol, result, null, dummy))
|
|
return false;
|
|
if (entry.typeCol == nsIPrefBranch.PREF_INT) {
|
|
// | 0 converts to integer or 0; - 0 to float or NaN.
|
|
// Thus, this check should catch all cases.
|
|
var val = result.value | 0;
|
|
if (val != result.value - 0) {
|
|
var err_title = gConfigBundle.getString("nan_title");
|
|
var err_text = gConfigBundle.getString("nan_text");
|
|
gPromptService.alert(window, err_title, err_text);
|
|
return false;
|
|
}
|
|
gPrefBranch.setIntPref(entry.prefCol, val);
|
|
} else {
|
|
var supportsString = Components.classes[nsSupportsString_CONTRACTID].createInstance(nsISupportsString);
|
|
supportsString.data = result.value;
|
|
gPrefBranch.setComplexValue(entry.prefCol, nsISupportsString, supportsString);
|
|
}
|
|
}
|
|
|
|
gPrefService.savePrefFile(null);
|
|
|
|
// Fire event for accessibility
|
|
var event = document.createEvent('Events');
|
|
event.initEvent('NameChange', false, true);
|
|
document.getElementById("configTree").dispatchEvent(event);
|
|
|
|
return true;
|
|
}
|