/* ***** 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): * Pete Collins * Brian King * Daniel Glazman * * 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 ***** */ /**** NAMESPACES ****/ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; // Each editor window must include this file // Variables shared by all dialogs: // Object to attach commonly-used widgets (all dialogs should use this) var gDialog = {}; // Bummer! Can't get at enums from nsIDocumentEncoder.h // http://lxr.mozilla.org/seamonkey/source/content/base/public/nsIDocumentEncoder.h#111 var gStringBundle; var gIOService; var gPrefsService; var gPrefsBranch; var gFilePickerDirectory; var gOS = ""; const gWin = "Win"; const gUNIX = "UNIX"; const gMac = "Mac"; const kWebComposerWindowID = "editorWindow"; const kMailComposerWindowID = "msgcomposeWindow"; var gIsHTMLEditor; /************* Message dialogs ***************/ function AlertWithTitle(title, message, parentWindow) { if (!parentWindow) parentWindow = window; var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(); promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService); if (promptService) { if (!title) title = GetString("Alert"); // "window" is the calling dialog window promptService.alert(parentWindow, title, message); } } // Optional: Caller may supply text to substitue for "Ok" and/or "Cancel" function ConfirmWithTitle(title, message, okButtonText, cancelButtonText) { var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(); promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService); if (promptService) { var okFlag = okButtonText ? promptService.BUTTON_TITLE_IS_STRING : promptService.BUTTON_TITLE_OK; var cancelFlag = cancelButtonText ? promptService.BUTTON_TITLE_IS_STRING : promptService.BUTTON_TITLE_CANCEL; return promptService.confirmEx(window, title, message, (okFlag * promptService.BUTTON_POS_0) + (cancelFlag * promptService.BUTTON_POS_1), okButtonText, cancelButtonText, null, null, {value:0}) == 0; } return false; } /************* String Utilities ***************/ function GetString(name) { if (!gStringBundle) { try { var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(); strBundleService = strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService); gStringBundle = strBundleService.createBundle("chrome://editor/locale/editor.properties"); } catch (ex) {} } if (gStringBundle) { try { return gStringBundle.GetStringFromName(name); } catch (e) {} } return null; } function TrimStringLeft(string) { if(!string) return ""; return string.replace(/^\s+/, ""); } function TrimStringRight(string) { if (!string) return ""; return string.replace(/\s+$/, ''); } // Remove whitespace from both ends of a string function TrimString(string) { if (!string) return ""; return string.replace(/(^\s+)|(\s+$)/g, '') } function IsWhitespace(string) { return /^\s/.test(string); } function TruncateStringAtWordEnd(string, maxLength, addEllipses) { // Return empty if string is null, undefined, or the empty string if (!string) return ""; // We assume they probably don't want whitespace at the beginning string = string.replace(/^\s+/, ''); if (string.length <= maxLength) return string; // We need to truncate the string to maxLength or fewer chars if (addEllipses) maxLength -= 3; string = string.replace(RegExp("(.{0," + maxLength + "})\\s.*"), "$1") if (string.length > maxLength) string = string.slice(0, maxLength); if (addEllipses) string += "..."; return string; } // Replace all whitespace characters with supplied character // E.g.: Use charReplace = " ", to "unwrap" the string by removing line-end chars // Use charReplace = "_" when you don't want spaces (like in a URL) function ReplaceWhitespace(string, charReplace) { return string.replace(/(^\s+)|(\s+$)/g,'').replace(/\s+/g,charReplace) } // Replace whitespace with "_" and allow only HTML CDATA // characters: "a"-"z","A"-"Z","0"-"9", "_", ":", "-", ".", // and characters above ASCII 127 function ConvertToCDATAString(string) { return string.replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g,''); } function GetSelectionAsText() { try { return GetCurrentEditor().outputToString("text/plain", 1); // OutputSelectionOnly } catch (e) {} return ""; } /************* Get Current Editor and associated interfaces or info ***************/ const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor; const nsIHTMLEditor = Components.interfaces.nsIHTMLEditor; const nsITableEditor = Components.interfaces.nsITableEditor; const nsIEditorStyleSheets = Components.interfaces.nsIEditorStyleSheets; const nsIEditingSession = Components.interfaces.nsIEditingSession; function GetCurrentEditor() { // Get the active editor from the tag // XXX This will probably change if we support > 1 editor in main Composer window // (e.g. a plaintext editor for HTMLSource) // For dialogs: Search up parent chain to find top window with editor var editor; try { var editorElement = GetCurrentEditorElement(); editor = editorElement.getEditor(editorElement.contentWindow); // Do QIs now so editor users won't have to figure out which interface to use // Using "instanceof" does the QI for us. editor instanceof Components.interfaces.nsIPlaintextEditor; editor instanceof Components.interfaces.nsIHTMLEditor; } catch (e) { dump (e)+"\n"; } return editor; } function GetCurrentTableEditor() { var editor = GetCurrentEditor(); return (editor && (editor instanceof nsITableEditor)) ? editor : null; } function GetCurrentEditorElement() { var tmpWindow = window; do { // Get the element(s) var editorList = tmpWindow.document.getElementsByTagName("editor"); // This will change if we support > 1 editor element if (editorList.item(0)) return editorList.item(0); tmpWindow = tmpWindow.opener; } while (tmpWindow); return null; } function GetCurrentCommandManager() { try { return GetCurrentEditorElement().commandManager; } catch (e) { dump (e)+"\n"; } return null; } function GetCurrentEditorType() { try { return GetCurrentEditorElement().editortype; } catch (e) { dump (e)+"\n"; } return ""; } function IsHTMLEditor() { // We don't have an editorElement, just return false if (!GetCurrentEditorElement()) return false; var editortype = GetCurrentEditorType(); switch (editortype) { case "html": case "htmlmail": return true; case "text": case "textmail": return false default: dump("INVALID EDITOR TYPE: " + editortype + "\n"); break; } return false; } function PageIsEmptyAndUntouched() { return IsDocumentEmpty() && !IsDocumentModified() && !IsHTMLSourceChanged(); } function IsInHTMLSourceMode() { return (gEditorDisplayMode == kDisplayModeSource); } // are we editing HTML (i.e. neither in HTML source mode, nor editing a text file) function IsEditingRenderedHTML() { return IsHTMLEditor() && !IsInHTMLSourceMode(); } function IsWebComposer() { return document.documentElement.id == "editorWindow"; } function IsDocumentEditable() { try { return GetCurrentEditor().isDocumentEditable; } catch (e) {} return false; } function IsDocumentEmpty() { try { return GetCurrentEditor().documentIsEmpty; } catch (e) {} return false; } function IsDocumentModified() { try { return GetCurrentEditor().documentModified; } catch (e) {} return false; } function IsHTMLSourceChanged() { return gSourceTextEditor.documentModified; } function newCommandParams() { try { return Components.classes["@mozilla.org/embedcomp/command-params;1"].createInstance(Components.interfaces.nsICommandParams); } catch(e) { dump("error thrown in newCommandParams: "+e+"\n"); } return null; } /************* General editing command utilities ***************/ function GetDocumentTitle() { try { return new XPCNativeWrapper(GetCurrentEditor().document, "title").title; } catch (e) {} return ""; } function SetDocumentTitle(title) { try { GetCurrentEditor().setDocumentTitle(title); // Update window title (doesn't work if called from a dialog) if ("UpdateWindowTitle" in window) window.UpdateWindowTitle(); } catch (e) {} } var gAtomService; function GetAtomService() { gAtomService = Components.classes["@mozilla.org/atom-service;1"].getService(Components.interfaces.nsIAtomService); } function EditorGetTextProperty(property, attribute, value, firstHas, anyHas, allHas) { try { if (!gAtomService) GetAtomService(); var propAtom = gAtomService.getAtom(property); GetCurrentEditor().getInlineProperty(propAtom, attribute, value, firstHas, anyHas, allHas); } catch(e) {} } function EditorSetTextProperty(property, attribute, value) { try { if (!gAtomService) GetAtomService(); var propAtom = gAtomService.getAtom(property); GetCurrentEditor().setInlineProperty(propAtom, attribute, value); if ("gContentWindow" in window) window.gContentWindow.focus(); } catch(e) {} } function EditorRemoveTextProperty(property, attribute) { try { if (!gAtomService) GetAtomService(); var propAtom = gAtomService.getAtom(property); GetCurrentEditor().removeInlineProperty(propAtom, attribute); if ("gContentWindow" in window) window.gContentWindow.focus(); } catch(e) {} } /************* Element enbabling/disabling ***************/ // this function takes an elementID and a flag // if the element can be found by ID, then it is either enabled (by removing "disabled" attr) // or disabled (setAttribute) as specified in the "doEnable" parameter function SetElementEnabledById(elementID, doEnable) { SetElementEnabled(document.getElementById(elementID), doEnable); } function SetElementEnabled(element, doEnable) { if ( element ) { if ( doEnable ) element.removeAttribute("disabled"); else element.setAttribute("disabled", "true"); } else { dump("Element not found in SetElementEnabled\n"); } } /************* Services / Prefs ***************/ function GetIOService() { if (gIOService) return gIOService; gIOService = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); return gIOService; } function GetFileProtocolHandler() { var ios = GetIOService(); var handler = ios.getProtocolHandler("file"); return handler.QueryInterface(Components.interfaces.nsIFileProtocolHandler); } function GetPrefsService() { if (gPrefsService) return gPrefsService; try { gPrefsService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService); } catch(ex) { dump("failed to get prefs service!\n"); } return gPrefsService; } function GetPrefs() { if (gPrefsBranch) return gPrefsBranch; try { var prefService = GetPrefsService(); if (prefService) gPrefsBranch = prefService.getBranch(null); if (gPrefsBranch) return gPrefsBranch; else dump("failed to get root prefs!\n"); } catch(ex) { dump("failed to get root prefs!\n"); } return null; } function GetStringPref(name) { try { return GetPrefs().getComplexValue(name, Components.interfaces.nsISupportsString).data; } catch (e) {} return ""; } function GetBoolPref(name) { try { return GetPrefs().getBoolPref(name); } catch (e) {} return false; } function SetUnicharPref(aPrefName, aPrefValue) { var prefs = GetPrefs(); if (prefs) { try { var str = Components.classes["@mozilla.org/supports-string;1"] .createInstance(Components.interfaces.nsISupportsString); str.data = aPrefValue; prefs.setComplexValue(aPrefName, Components.interfaces.nsISupportsString, str); } catch(e) {} } } function GetUnicharPref(aPrefName, aDefVal) { var prefs = GetPrefs(); if (prefs) { try { return prefs.getComplexValue(aPrefName, Components.interfaces.nsISupportsString).data; } catch(e) {} } return ""; } // Set initial directory for a filepicker from URLs saved in prefs function SetFilePickerDirectory(filePicker, fileType) { if (filePicker) { try { var prefBranch = GetPrefs(); if (prefBranch) { // Save current directory so we can reset it in SaveFilePickerDirectory gFilePickerDirectory = filePicker.displayDirectory; var location = prefBranch.getComplexValue("editor.lastFileLocation."+fileType, Components.interfaces.nsILocalFile); if (location) filePicker.displayDirectory = location; } } catch(e) {} } } // Save the directory of the selected file to prefs function SaveFilePickerDirectory(filePicker, fileType) { if (filePicker && filePicker.file) { try { var prefBranch = GetPrefs(); var fileDir; if (filePicker.file.parent) fileDir = filePicker.file.parent.QueryInterface(Components.interfaces.nsILocalFile); if (prefBranch) prefBranch.setComplexValue("editor.lastFileLocation."+fileType, Components.interfaces.nsILocalFile, fileDir); var prefsService = GetPrefsService(); prefsService.savePrefFile(null); } catch (e) {} } // Restore the directory used before SetFilePickerDirectory was called; // This reduces interference with Browser and other module directory defaults if (gFilePickerDirectory) filePicker.displayDirectory = gFilePickerDirectory; gFilePickerDirectory = null; } function GetDefaultBrowserColors() { var prefs = GetPrefs(); var colors = { TextColor:0, BackgroundColor:0, LinkColor:0, ActiveLinkColor:0 , VisitedLinkColor:0 }; var useSysColors = false; try { useSysColors = prefs.getBoolPref("browser.display.use_system_colors"); } catch (e) {} if (!useSysColors) { try { colors.TextColor = prefs.getCharPref("browser.display.foreground_color"); } catch (e) {} try { colors.BackgroundColor = prefs.getCharPref("browser.display.background_color"); } catch (e) {} } // Use OS colors for text and background if explicitly asked or pref is not set if (!colors.TextColor) colors.TextColor = "windowtext"; if (!colors.BackgroundColor) colors.BackgroundColor = "window"; colors.LinkColor = prefs.getCharPref("browser.anchor_color"); colors.ActiveLinkColor = prefs.getCharPref("browser.active_color"); colors.VisitedLinkColor = prefs.getCharPref("browser.visited_color"); return colors; } /************* URL handling ***************/ function TextIsURI(selectedText) { return selectedText && /^http:\/\/|^https:\/\/|^file:\/\/|\ ^ftp:\/\/|^about:|^mailto:|^news:|^snews:|^telnet:|^ldap:|\ ^ldaps:|^gopher:|^finger:|^javascript:/i.test(selectedText); } function IsUrlAboutBlank(urlString) { return (urlString == "about:blank"); } function MakeRelativeUrl(url) { var inputUrl = TrimString(url); if (!inputUrl) return inputUrl; // Get the filespec relative to current document's location // NOTE: Can't do this if file isn't saved yet! var docUrl = GetDocumentBaseUrl(); var docScheme = GetScheme(docUrl); // Can't relativize if no doc scheme (page hasn't been saved) if (!docScheme) return inputUrl; var urlScheme = GetScheme(inputUrl); // Do nothing if not the same scheme or url is already relativized if (docScheme != urlScheme) return inputUrl; var IOService = GetIOService(); if (!IOService) return inputUrl; // Host must be the same var docHost = GetHost(docUrl); var urlHost = GetHost(inputUrl); if (docHost != urlHost) return inputUrl; // Get just the file path part of the urls // XXX Should we use GetCurrentEditor().documentCharacterSet for 2nd param ? var docPath = IOService.newURI(docUrl, GetCurrentEditor().documentCharacterSet, null).path; var urlPath = IOService.newURI(inputUrl, GetCurrentEditor().documentCharacterSet, null).path; // We only return "urlPath", so we can convert // the entire docPath for case-insensitive comparisons var os = GetOS(); var doCaseInsensitive = (docScheme == "file" && os == gWin); if (doCaseInsensitive) docPath = docPath.toLowerCase(); // Get document filename before we start chopping up the docPath var docFilename = GetFilename(docPath); // Both url and doc paths now begin with "/" // Look for shared dirs starting after that urlPath = urlPath.slice(1); docPath = docPath.slice(1); var firstDirTest = true; var nextDocSlash = 0; var done = false; // Remove all matching subdirs common to both doc and input urls do { nextDocSlash = docPath.indexOf("\/"); var nextUrlSlash = urlPath.indexOf("\/"); if (nextUrlSlash == -1) { // We're done matching and all dirs in url // what's left is the filename done = true; // Remove filename for named anchors in the same file if (nextDocSlash == -1 && docFilename) { var anchorIndex = urlPath.indexOf("#"); if (anchorIndex > 0) { var urlFilename = doCaseInsensitive ? urlPath.toLowerCase() : urlPath; if (urlFilename.indexOf(docFilename) == 0) urlPath = urlPath.slice(anchorIndex); } } } else if (nextDocSlash >= 0) { // Test for matching subdir var docDir = docPath.slice(0, nextDocSlash); var urlDir = urlPath.slice(0, nextUrlSlash); if (doCaseInsensitive) urlDir = urlDir.toLowerCase(); if (urlDir == docDir) { // Remove matching dir+"/" from each path // and continue to next dir docPath = docPath.slice(nextDocSlash+1); urlPath = urlPath.slice(nextUrlSlash+1); } else { // No match, we're done done = true; // Be sure we are on the same local drive or volume // (the first "dir" in the path) because we can't // relativize to different drives/volumes. // UNIX doesn't have volumes, so we must not do this else // the first directory will be misinterpreted as a volume name if (firstDirTest && docScheme == "file" && os != gUNIX) return inputUrl; } } else // No more doc dirs left, we're done done = true; firstDirTest = false; } while (!done); // Add "../" for each dir left in docPath while (nextDocSlash > 0) { urlPath = "../" + urlPath; nextDocSlash = docPath.indexOf("\/", nextDocSlash+1); } return urlPath; } function MakeAbsoluteUrl(url) { var resultUrl = TrimString(url); if (!resultUrl) return resultUrl; // Check if URL is already absolute, i.e., it has a scheme var urlScheme = GetScheme(resultUrl); if (urlScheme) return resultUrl; var docUrl = GetDocumentBaseUrl(); var docScheme = GetScheme(docUrl); // Can't relativize if no doc scheme (page hasn't been saved) if (!docScheme) return resultUrl; var IOService = GetIOService(); if (!IOService) return resultUrl; // Make a URI object to use its "resolve" method var absoluteUrl = resultUrl; var docUri = IOService.newURI(docUrl, GetCurrentEditor().documentCharacterSet, null); try { absoluteUrl = docUri.resolve(resultUrl); // This is deprecated and buggy! // If used, we must make it a path for the parent directory (remove filename) //absoluteUrl = IOService.resolveRelativePath(resultUrl, docUrl); } catch (e) {} return absoluteUrl; } // Get the HREF of the page's tag or the document location // returns empty string if no base href and document hasn't been saved yet function GetDocumentBaseUrl() { try { var docUrl; // if document supplies a tag, use that URL instead var baseList = GetCurrentEditor().document.getElementsByTagName("base"); if (baseList) { var base = baseList.item(0); if (base) docUrl = base.getAttribute("href"); } if (!docUrl) docUrl = GetDocumentUrl(); if (!IsUrlAboutBlank(docUrl)) return docUrl; } catch (e) {} return ""; } function GetDocumentUrl() { try { var aDOMHTMLDoc = GetCurrentEditor().document.QueryInterface(Components.interfaces.nsIDOMHTMLDocument); return aDOMHTMLDoc.URL; } catch (e) {} return ""; } // Extract the scheme (e.g., 'file', 'http') from a URL string function GetScheme(urlspec) { var resultUrl = TrimString(urlspec); // Unsaved document URL has no acceptable scheme yet if (!resultUrl || IsUrlAboutBlank(resultUrl)) return ""; var IOService = GetIOService(); if (!IOService) return ""; var scheme = ""; try { // This fails if there's no scheme scheme = IOService.extractScheme(resultUrl); } catch (e) {} return scheme ? scheme.toLowerCase() : ""; } function GetHost(urlspec) { if (!urlspec) return ""; var IOService = GetIOService(); if (!IOService) return ""; var host = ""; try { host = IOService.newURI(urlspec, null, null).host; } catch (e) {} return host; } function GetUsername(urlspec) { if (!urlspec) return ""; var IOService = GetIOService(); if (!IOService) return ""; var username = ""; try { username = IOService.newURI(urlspec, null, null).username; } catch (e) {} return username; } function GetFilename(urlspec) { if (!urlspec || IsUrlAboutBlank(urlspec)) return ""; var IOService = GetIOService(); if (!IOService) return ""; var filename; try { var uri = IOService.newURI(urlspec, null, null); if (uri) { var url = uri.QueryInterface(Components.interfaces.nsIURL); if (url) filename = url.fileName; } } catch (e) {} return filename ? filename : ""; } // Return the url without username and password // Optional output objects return extracted username and password strings // This uses just string routines via nsIIOServices function StripUsernamePassword(urlspec, usernameObj, passwordObj) { urlspec = TrimString(urlspec); if (!urlspec || IsUrlAboutBlank(urlspec)) return urlspec; if (usernameObj) usernameObj.value = ""; if (passwordObj) passwordObj.value = ""; // "@" must exist else we will never detect username or password var atIndex = urlspec.indexOf("@"); if (atIndex > 0) { try { var IOService = GetIOService(); if (!IOService) return urlspec; var uri = IOService.newURI(urlspec, null, null); var username = uri.username; var password = uri.password; if (usernameObj && username) usernameObj.value = username; if (passwordObj && password) passwordObj.value = password; if (username) { var usernameStart = urlspec.indexOf(username); if (usernameStart != -1) return urlspec.slice(0, usernameStart) + urlspec.slice(atIndex+1); } } catch (e) {} } return urlspec; } function StripPassword(urlspec, passwordObj) { urlspec = TrimString(urlspec); if (!urlspec || IsUrlAboutBlank(urlspec)) return urlspec; if (passwordObj) passwordObj.value = ""; // "@" must exist else we will never detect password var atIndex = urlspec.indexOf("@"); if (atIndex > 0) { try { var IOService = GetIOService(); if (!IOService) return urlspec; var password = IOService.newURI(urlspec, null, null).password; if (passwordObj && password) passwordObj.value = password; if (password) { // Find last ":" before "@" var colon = urlspec.lastIndexOf(":", atIndex); if (colon != -1) { // Include the "@" return urlspec.slice(0, colon) + urlspec.slice(atIndex); } } } catch (e) {} } return urlspec; } // Version to use when you have an nsIURI object function StripUsernamePasswordFromURI(uri) { var urlspec = ""; if (uri) { try { urlspec = uri.spec; var userPass = uri.userPass; if (userPass) { start = urlspec.indexOf(userPass); urlspec = urlspec.slice(0, start) + urlspec.slice(start+userPass.length+1); } } catch (e) {} } return urlspec; } function InsertUsernameIntoUrl(urlspec, username) { if (!urlspec || !username) return urlspec; try { var ioService = GetIOService(); var URI = ioService.newURI(urlspec, GetCurrentEditor().documentCharacterSet, null); URI.username = username; return URI.spec; } catch (e) {} return urlspec; } function GetOS() { if (gOS) return gOS; var platform = navigator.platform.toLowerCase(); if (platform.indexOf("win") != -1) gOS = gWin; else if (platform.indexOf("mac") != -1) gOS = gMac; else if (platform.indexOf("unix") != -1 || platform.indexOf("linux") != -1 || platform.indexOf("sun") != -1) gOS = gUNIX; else gOS = ""; // Add other tests? return gOS; } function ConvertRGBColorIntoHEXColor(color) { if ( /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.test(color) ) { var r = Number(RegExp.$1).toString(16); if (r.length == 1) r = "0"+r; var g = Number(RegExp.$2).toString(16); if (g.length == 1) g = "0"+g; var b = Number(RegExp.$3).toString(16); if (b.length == 1) b = "0"+b; return "#"+r+g+b; } else { return color; } } /************* CSS ***************/ function GetHTMLOrCSSStyleValue(element, attrName, cssPropertyName) { var prefs = GetPrefs(); var IsCSSPrefChecked = prefs.getBoolPref("editor.use_css"); var value; if (IsCSSPrefChecked && IsHTMLEditor()) value = element.style.getPropertyValue(cssPropertyName); if (!value) value = element.getAttribute(attrName); if (!value) return ""; return value; } /************* Miscellaneous ***************/ // Clone simple JS objects function Clone(obj) { var clone = {}; for (var i in obj) { if( typeof obj[i] == 'object') clone[i] = Clone(obj[i]); else clone[i] = obj[i]; } return clone; }