-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- -- TSM's error handler local _, TSM = ... local ErrorHandler = TSM.Init("Service.ErrorHandler") local Log = TSM.Include("Util.Log") local String = TSM.Include("Util.String") local Event = TSM.Include("Util.Event") local JSON = TSM.Include("Util.JSON") local TempTable = TSM.Include("Util.TempTable") local L = TSM.Include("Locale").GetTable() local private = { origErrorHandler = nil, errorFrame = nil, isSilent = nil, errorSuppressed = nil, errorReports = {}, num = 0, localLinesTemp = {}, hitInternalError = false, isManual = nil, ignoreErrors = false, globalNameTranslation = {}, } local MAX_ERROR_REPORT_AGE = 7 * 24 * 60 * 60 -- 1 week local MAX_STACK_DEPTH = 50 local ADDON_SUITES = { "ArkInventory", "AtlasLoot", "Altoholic", "Auc-", "Bagnon", "BigWigs", "Broker", "ButtonFacade", "Carbonite", "DataStore", "DBM", "Dominos", "DXE", "EveryQuest", "Forte", "FuBar", "GatherMate2", "Grid", "LightHeaded", "LittleWigs", "Masque", "MogIt", "Odyssey", "Overachiever", "PitBull4", "Prat-3.0", "RaidAchievement", "Skada", "SpellFlash", "TidyPlates", "TipTac", "Titan", "UnderHood", "WowPro", "ZOMGBuffs", } local OLD_TSM_MODULES = { "TradeSkillMaster_Accounting", "TradeSkillMaster_AuctionDB", "TradeSkillMaster_Auctioning", "TradeSkillMaster_Crafting", "TradeSkillMaster_Destroying", "TradeSkillMaster_Mailing", "TradeSkillMaster_Shopping", "TradeSkillMaster_Vendoring", "TradeSkillMaster_Warehousing", } local PRINT_PREFIX = "|cffff0000TSM:|r " -- ============================================================================ -- Module Functions -- ============================================================================ function ErrorHandler.ShowForThread(err, thread) local stackLine = debugstack(thread, 0, 1, 0) local oldModule = strmatch(stackLine, "(lMaster_[A-Za-z]+)") if oldModule and tContains(OLD_TSM_MODULES, "TradeSkil"..oldModule) then -- ignore errors from old modules return end -- show an error, but don't cause an exception to be thrown private.isSilent = true private.ErrorHandler(err, thread) end function ErrorHandler.ShowManual() private.isManual = true -- show an error, but don't cause an exception to be thrown private.isSilent = true private.ErrorHandler("Manually triggered error") end function ErrorHandler.SaveReports(appDB) if private.errorFrame then private.errorFrame:Hide() end appDB.errorReports = appDB.errorReports or { updateTime = 0, data = {} } if #private.errorReports > 0 then appDB.errorReports.updateTime = private.errorReports[#private.errorReports].timestamp end -- remove any events which are too old for i = #appDB.errorReports.data, 1, -1 do local timestamp = strmatch(appDB.errorReports.data[i], "([0-9]+)%]$") or "" if (tonumber(timestamp) or 0) < time() - MAX_ERROR_REPORT_AGE then tremove(appDB.errorReports.data, i) end end for _, report in ipairs(private.errorReports) do local line = format("[%s,\"%s\",%d]", JSON.Encode(report.errorInfo), report.details, report.timestamp) tinsert(appDB.errorReports.data, line) end end -- ============================================================================ -- Error Handler -- ============================================================================ function private.ErrorHandler(msg, thread) -- ignore errors while we are handling this error private.ignoreErrors = true local isSilent = private.isSilent private.isSilent = nil local isManual = private.isManual private.isManual = nil private.CreateErrorFrame() if type(thread) ~= "thread" then thread = nil end if private.errorFrame:IsVisible() and private.errorSuppressed then -- already showing an error and suppressed another one, so silently ignore this one private.ignoreErrors = false return true end -- shorten the paths in the error message msg = gsub(msg, "%.%.%.T?r?a?d?e?S?k?i?l?l?M?a?ster([_A-Za-z]*)\\", "TradeSkillMaster%1\\") msg = strsub(msg, strfind(msg, "TradeSkillMaster") or 1) msg = gsub(msg, "TradeSkillMaster([^%.])", "TSM%1") -- build our global name translation table wipe(private.globalNameTranslation) pcall(function() local UIElements = TSM.Include("UI.UIElements") local temp = {} UIElements.GetDebugNameTranslation(temp) for k, v in pairs(temp) do private.globalNameTranslation[String.Escape(k)] = v end end) -- build stack trace with locals and get addon name local stackInfo = private.GetStackInfo(msg, thread) local addonName = isSilent and "TradeSkillMaster" or nil for _, info in ipairs(stackInfo) do if not addonName then addonName = strmatch(info.file, "[A-Za-z]+%.lua") and private.IsTSMAddon(info.file) or nil end end if not isManual and addonName ~= "TradeSkillMaster" then -- not a TSM error private.ignoreErrors = false return false end if not TSM.IsDevVersion() and not isManual then -- log the error (use a format string in case there are '%' characters in the msg) Log.Err("%s", msg) end if private.errorFrame:IsVisible() then -- already showing an error, so suppress this one and return private.errorSuppressed = true print(PRINT_PREFIX..L["Additional error suppressed"]) return true end private.num = private.num + 1 local clientVersion, clientBuild = GetBuildInfo() local errorInfo = { msg = #stackInfo > 0 and gsub(msg, String.Escape(stackInfo[1].file)..":"..stackInfo[1].line..": ", "") or msg, stackInfo = stackInfo, time = time(), debugTime = floor(debugprofilestop()), client = format("%s (%s)", clientVersion, clientBuild), locale = GetLocale(), inCombat = tostring(InCombatLockdown() and true or false), version = TSM.GetVersion(), } -- temp table info local tempTableLines = {} for _, info in ipairs(TempTable.GetDebugInfo()) do tinsert(tempTableLines, info) end errorInfo.tempTableStr = table.concat(tempTableLines, "\n") -- object pool info local status, objectPoolInfo = pcall(function() return TSM.Include("Util.ObjectPool").GetDebugInfo() end) local objectPoolLines = {} if status then for name, objectInfo in pairs(objectPoolInfo) do tinsert(objectPoolLines, format("%s (%d created, %d in use)", name, objectInfo.numCreated, objectInfo.numInUse)) for _, info in ipairs(objectInfo.info) do tinsert(objectPoolLines, " "..info) end end end errorInfo.objectPoolStr = status and table.concat(objectPoolLines, "\n") or tostring(objectPoolInfo) -- TSM thread info local threadInfoStr = nil status, threadInfoStr = pcall(function() return TSM.Include("Service.Threading").GetDebugStr() end) errorInfo.threadInfoStr = tostring(threadInfoStr) -- recent debug log entries local entries = {} for i = Log.Length(), 1, -1 do local severity, location, timeStr, logMsg = Log.Get(i) tinsert(entries, format("%s [%s] {%s} %s", timeStr, severity, location, logMsg)) end errorInfo.debugLogStr = table.concat(entries, "\n") -- addons local hasAddonSuite = {} local addonsLines = {} for i = 1, GetNumAddOns() do local name, _, _, loadable = GetAddOnInfo(i) if loadable then local version = strtrim(GetAddOnMetadata(name, "X-Curse-Packaged-Version") or GetAddOnMetadata(name, "Version") or "") local loaded = IsAddOnLoaded(i) local isSuite = nil for _, commonTerm in ipairs(ADDON_SUITES) do if strsub(name, 1, #commonTerm) == commonTerm then isSuite = commonTerm break end end local commonTerm = "TradeSkillMaster" if isSuite then if not hasAddonSuite[isSuite] then tinsert(addonsLines, name.." ("..version..")"..(loaded and "" or " [Not Loaded]")) hasAddonSuite[isSuite] = true end elseif strsub(name, 1, #commonTerm) == commonTerm then name = gsub(name, "TradeSkillMaster", "TSM") tinsert(addonsLines, name.." ("..version..")"..(loaded and "" or " [Not Loaded]")) else tinsert(addonsLines, name.." ("..version..")"..(loaded and "" or " [Not Loaded]")) end end end errorInfo.addonsStr = table.concat(addonsLines, "\n") -- show this error local stackInfoLines = {} for _, info in ipairs(errorInfo.stackInfo) do local localsStr = info.locals ~= "" and ("\n |cffaaaaaa"..gsub(info.locals, "\n", "\n ").."|r") or "" local locationStr = info.line ~= 0 and strjoin(":", info.file, info.line) or info.file tinsert(stackInfoLines, locationStr.." <"..info.func..">"..localsStr) end private.errorFrame.errorStr = strjoin("\n", private.FormatErrorMessageSection("Message", msg), private.FormatErrorMessageSection("Time", date("%m/%d/%y %H:%M:%S", errorInfo.time).." ("..floor(errorInfo.debugTime)..")"), private.FormatErrorMessageSection("Client", errorInfo.client), private.FormatErrorMessageSection("Locale", errorInfo.locale), private.FormatErrorMessageSection("Combat", errorInfo.inCombat), private.FormatErrorMessageSection("Error Count", private.num), private.FormatErrorMessageSection("Stack Trace", table.concat(stackInfoLines, "\n"), true), private.FormatErrorMessageSection("Temp Tables", errorInfo.tempTableStr, true), private.FormatErrorMessageSection("Object Pools", errorInfo.objectPoolStr, true), private.FormatErrorMessageSection("Running Threads", errorInfo.threadInfoStr, true), private.FormatErrorMessageSection("Debug Log", errorInfo.debugLogStr, true), private.FormatErrorMessageSection("Addons", errorInfo.addonsStr, true) ) -- remove unprintable characters private.errorFrame.errorStr = gsub(private.errorFrame.errorStr, "[%z\001-\008\011-\031]", "?") private.errorFrame.errorInfo = errorInfo private.errorFrame.isManual = isManual private.errorFrame:Show() print(PRINT_PREFIX..L["Looks like TradeSkillMaster has encountered an error. Please help the author fix this error by following the instructions shown."]) if TSM.__IS_TEST_ENV then print(private.errorFrame.errorStr) end private.ignoreErrors = false return true end -- ============================================================================ -- Private Helper Functions -- ============================================================================ function private.GetStackInfo(msg, thread) local errLocation = strmatch(msg, "[A-Za-z]+%.lua:[0-9]+") local stackInfo = {} local stackStarted = false for i = 0, MAX_STACK_DEPTH do local prevStackFunc = #stackInfo > 0 and stackInfo[#stackInfo].func or nil local file, line, func, localsStr, newPrevStackFunc = private.GetStackLevelInfo(i, thread, prevStackFunc) if newPrevStackFunc then stackInfo[#stackInfo].func = newPrevStackFunc end if file then if not stackStarted then if errLocation then stackStarted = strmatch(file..":"..line, "[A-Za-z]+%.lua:[0-9]+") == errLocation else stackStarted = i > (thread and 1 or 4) and file ~= "[C]" end end if stackStarted then tinsert(stackInfo, { file = file, line = line, func = func, locals = localsStr, }) end end end return stackInfo end function private.GetStackLevelInfo(level, thread, prevStackFunc) local stackLine = nil if thread then stackLine = debugstack(thread, level, 1, 0) else level = level + 1 stackLine = debugstack(level, 1, 0) end stackLine = gsub(stackLine, "^%[string \"@([^%.]+%.lua)\"%]", "%1") local locals = debuglocals(level) stackLine = gsub(stackLine, "%.%.%.T?r?a?d?e?S?k?i?l?l?M?a?ster([_A-Za-z]*)\\", "TradeSkillMaster%1\\") stackLine = gsub(stackLine, "%.%.%.", "") stackLine = gsub(stackLine, "`", "<", 1) stackLine = gsub(stackLine, "'", ">", 1) stackLine = strtrim(stackLine) if stackLine == "" then return end -- Parse out the file, line, and function name local locationStr, functionStr = strmatch(stackLine, "^(.-): in function (<[^\n]*>)") if not locationStr then locationStr, functionStr = strmatch(stackLine, "^(.-): in (main chunk)") end if not locationStr then return end locationStr = strsub(locationStr, strfind(locationStr, "TradeSkillMaster") or 1) locationStr = gsub(locationStr, "TradeSkillMaster([^%.])", "TSM%1") functionStr = functionStr and gsub(gsub(functionStr, ".*\\", ""), "[<>]", "") or "" local file, line = strmatch(locationStr, "^(.+):(%d+)$") file = file or locationStr line = tonumber(line) or 0 local func = strsub(functionStr, strfind(functionStr, "`") and 2 or 1, -1) or "?" func = func ~= "" and func or "?" if strfind(locationStr, "LibTSMClass%.lua:") then -- ignore stack frames from the class code's wrapper function if func ~= "?" and prevStackFunc and not strmatch(func, "^.+:[0-9]+$") and strmatch(prevStackFunc, "^.+:[0-9]+$") then -- this stack frame includes the class method we were accessing in the previous one, so go back and fix it up local className = locals and strmatch(locals, "\n +str = \"([A-Za-z_0-9]+):[0-9A-F]+\"\n") or "?" prevStackFunc = className.."."..func end return nil, nil, nil, nil, prevStackFunc end -- add locals for addon functions (debuglocals() doesn't always work - or ever for threads) local localsStr = locals and private.ParseLocals(locals, file) or "" return file, line, func, localsStr, nil end function private.ParseLocals(locals, file) if strmatch(file, "^%[") then return end local fileName = strmatch(file, "([A-Za-z%-_0-9]+)%.lua") local isBlizzardFile = strmatch(file, "Interface\\FrameXML\\") local isPrivateTable, isLocaleTable, isPackageTable, isSelfTable = false, false, false, false wipe(private.localLinesTemp) locals = gsub(locals, "<([a-z]+)> {[\n\t ]+}", "<%1> {}") locals = gsub(locals, " = defined @", "@") locals = gsub(locals, " {", "{") for localLine in gmatch(locals, "[^\n]+") do local shouldIgnoreLine = false if strmatch(localLine, "^ *%(") then shouldIgnoreLine = true elseif strmatch(localLine, "LibTSMClass%.lua:") then -- ignore class methods shouldIgnoreLine = true elseif strmatch(localLine, " {}$") then -- ignore internal WoW frame members shouldIgnoreLine = true end if not shouldIgnoreLine then local level = #strmatch(localLine, "^ *") localLine = strrep(" ", level)..strtrim(localLine) localLine = gsub(localLine, "Interface\\[Aa]dd[Oo]ns\\TradeSkillMaster", "TSM") localLine = gsub(localLine, "\124", "\\124") for matchStr, replaceStr in pairs(private.globalNameTranslation) do localLine = gsub(localLine, matchStr, replaceStr) end if level > 0 then if isBlizzardFile then -- for Blizzard stack frames, only include level 0 locals shouldIgnoreLine = true elseif strmatch(localLine, "^ *[_]*[A-Z].+@TSM") then -- ignore table methods (based on their name being UpperCamelCase - potentially with leading underscores) shouldIgnoreLine = true elseif isLocaleTable then -- ignore everything within the locale table shouldIgnoreLine = true elseif isPackageTable then -- ignore the package table completely shouldIgnoreLine = true elseif (isSelfTable or isPrivateTable) and strmatch(localLine, "^ *[_a-zA-Z0-9]+ = {}") then -- ignore empty tables within objects or the private table shouldIgnoreLine = true elseif strmatch(localLine, "^%s+0 = $") then -- remove userdata table entries shouldIgnoreLine = true end end if not shouldIgnoreLine then tinsert(private.localLinesTemp, localLine) end if level == 0 then isPackageTable = strmatch(localLine, "%s*"..fileName.." = {") and true or false isPrivateTable = strmatch(localLine, "%s*private = {") and true or false isLocaleTable = strmatch(localLine, "%s*L = {") and true or false isSelfTable = strmatch(localLine, "%s*self = {") and true or false end end end -- remove any top-level empty tables local i = #private.localLinesTemp while i > 0 do if i > 1 and private.localLinesTemp[i] == "}" and strmatch(private.localLinesTemp[i - 1], "^[A-Za-z_].* = {$") then tremove(private.localLinesTemp, i) tremove(private.localLinesTemp, i - 1) i = i - 2 elseif strmatch(private.localLinesTemp[i], "^[A-Za-z_].* = {}$") then tremove(private.localLinesTemp, i) i = i - 1 else i = i - 1 end end return #private.localLinesTemp > 0 and table.concat(private.localLinesTemp, "\n") or nil end function private.IsTSMAddon(str) if strfind(str, "Auc-Adcanced\\CoreScan.lua") then -- ignore auctioneer errors return nil elseif strfind(str, "Master\\External\\") then -- ignore errors from libraries return nil elseif strfind(str, "Master\\Core\\API.lua") then -- ignore errors from public APIs return nil elseif strfind(str, "Master_AppHelper\\") then return "TradeSkillMaster_AppHelper" elseif strfind(str, "lMaster\\") then return "TradeSkillMaster" elseif strfind(str, "ster\\Core\\UI\\") then return "TradeSkillMaster" elseif strfind(str, "r\\LibTSM\\") then return "TradeSkillMaster" elseif strfind(str, "^TSM\\") then return "TradeSkillMaster" end return nil end function private.AddonBlockedHandler(event, addonName, addonFunc) if not strmatch(addonName, "TradeSkillMaster") then return end -- just log it - it might not be TSM that cause the taint Log.Err("[%s] AddOn '%s' tried to call the protected function '%s'.", event, addonName or "", addonFunc or "") end function private.SanitizeString(str) str = gsub(str, "\124cff[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]([^\124]+)\124r", "%1") str = gsub(str, "[\\]+", "/") str = gsub(str, "\"", "'") return str end function private.FormatErrorMessageSection(heading, info, isMultiLine) -- replace unprintable characters with "?" info = gsub(info, "[^\t\n -~]", "?") local prefix = nil if isMultiLine then prefix = info ~= "" and "\n " or "" info = gsub(info, "\n", "\n ") else prefix = info ~= "" and " " or "" end return "|cff99ffff"..heading..":|r"..prefix..info end -- ============================================================================ -- Error Frame -- ============================================================================ function private.CreateErrorFrame() if private.errorFrame then return end local STEPS_TEXT = "Steps leading up to the error:\n1) List\n2) Steps\n3) Here" local frame = CreateFrame("Frame", nil, UIParent, TSM.IsShadowlands() and "BackdropTemplate" or nil) private.errorFrame = frame frame:Hide() frame:SetWidth(500) frame:SetHeight(400) frame:SetFrameStrata("FULLSCREEN_DIALOG") frame:SetPoint("RIGHT", -100, 0) frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2, }) frame:SetBackdropColor(0, 0, 0, 1) frame:SetBackdropBorderColor(0.3, 0.3, 0.3, 1) frame:SetScript("OnShow", function(self) self.showingError = self.isManual or TSM.IsDevVersion() self.details = STEPS_TEXT if self.showingError then -- this is a dev version so show the error (only) self.text:SetText("Looks like TradeSkillMaster has encountered an error.") self.switchBtn:SetText("Hide Error") self.editBox:SetText(self.errorStr) else self.text:SetText("Looks like TradeSkillMaster has encountered an error. Please provide the steps which lead to this error to help the TSM team fix it, then click either button at the bottom of the window to automatically report this error.") self.switchBtn:SetText("Show Error") self.editBox:SetText(self.details) end end) frame:SetScript("OnHide", function() local details = private.errorFrame.showingError and private.errorFrame.details or private.errorFrame.editBox:GetText() local changedDetails = details ~= STEPS_TEXT if (not TSM.IsDevVersion() and not private.errorFrame.isManual and (changedDetails or private.num == 1)) or IsShiftKeyDown() then tinsert(private.errorReports, { errorInfo = private.errorFrame.errorInfo, details = private.SanitizeString(details), timestamp = time(), }) end private.errorSuppressed = nil end) local title = frame:CreateFontString() title:SetHeight(20) title:SetPoint("TOPLEFT", 0, -10) title:SetPoint("TOPRIGHT", 0, -10) title:SetFontObject(GameFontNormalLarge) title:SetTextColor(1, 1, 1, 1) title:SetJustifyH("CENTER") title:SetJustifyV("MIDDLE") local status, versionText = pcall(TSM.GetVersion) versionText = status and versionText or "?" title:SetText("TSM Error Window ("..versionText..")") local hLine = frame:CreateTexture(nil, "ARTWORK") hLine:SetHeight(2) hLine:SetColorTexture(0.3, 0.3, 0.3, 1) hLine:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10) hLine:SetPoint("TOPRIGHT", title, "BOTTOMRIGHT", 0, -10) local text = frame:CreateFontString() frame.text = text text:SetHeight(45) text:SetPoint("TOPLEFT", hLine, "BOTTOMLEFT", 8, -8) text:SetPoint("TOPRIGHT", hLine, "BOTTOMRIGHT", -8, -8) text:SetFontObject(GameFontNormal) text:SetTextColor(1, 1, 1, 1) text:SetJustifyH("LEFT") text:SetJustifyV("MIDDLE") local switchBtn = CreateFrame("Button", nil, frame) frame.switchBtn = switchBtn switchBtn:SetPoint("TOPRIGHT", -4, -10) switchBtn:SetWidth(100) switchBtn:SetHeight(20) local fontString = switchBtn:CreateFontString() fontString:SetFontObject(GameFontNormalSmall) fontString:SetJustifyH("CENTER") fontString:SetJustifyV("MIDDLE") switchBtn:SetFontString(fontString) switchBtn:SetScript("OnClick", function(self) private.errorFrame.showingError = not private.errorFrame.showingError if private.errorFrame.showingError then private.errorFrame.details = private.errorFrame.editBox:GetText() self:SetText("Hide Error") private.errorFrame.editBox:SetText(private.errorFrame.errorStr) else self:SetText("Show Error") private.errorFrame.editBox:SetText(private.errorFrame.details) end end) local hLine2 = frame:CreateTexture(nil, "ARTWORK") hLine2:SetHeight(2) hLine2:SetColorTexture(0.3, 0.3, 0.3, 1) hLine2:SetPoint("TOPLEFT", text, "BOTTOMLEFT", -8, -4) hLine2:SetPoint("TOPRIGHT", text, "BOTTOMRIGHT", 8, -4) local scrollFrame = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TOPLEFT", hLine2, "BOTTOMLEFT", 8, -4) scrollFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -26, 38) local editBox = CreateFrame("EditBox", nil, scrollFrame) frame.editBox = editBox editBox:SetWidth(scrollFrame:GetWidth()) editBox:SetFontObject(ChatFontNormal) editBox:SetMultiLine(true) editBox:SetAutoFocus(false) editBox:SetMaxLetters(0) editBox:SetTextColor(1, 1, 1, 1) editBox:SetScript("OnUpdate", function(self) local offset = scrollFrame:GetVerticalScroll() self:SetHitRectInsets(0, 0, offset, self:GetHeight() - offset - scrollFrame:GetHeight()) end) editBox:SetScript("OnEditFocusGained", function(self) self:HighlightText() end) editBox:SetScript("OnCursorChanged", function(self) if private.errorFrame.showingError and self:HasFocus() then self:HighlightText() end end) editBox:SetScript("OnEscapePressed", function(self) if private.errorFrame.showingError then self:HighlightText(0, 0) end self:ClearFocus() end) scrollFrame:SetScrollChild(editBox) local hLine3 = frame:CreateTexture(nil, "ARTWORK") hLine3:SetHeight(2) hLine3:SetColorTexture(0.3, 0.3, 0.3, 1) hLine3:SetPoint("BOTTOMLEFT", frame, 0, 35) hLine3:SetPoint("BOTTOMRIGHT", frame, 0, 35) local reloadBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") frame.reloadBtn = reloadBtn reloadBtn:SetPoint("BOTTOMLEFT", 4, 4) reloadBtn:SetWidth(120) reloadBtn:SetHeight(30) reloadBtn:SetText(RELOADUI) reloadBtn:SetScript("OnClick", function() frame:Hide() ReloadUI() end) local closeBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") frame.closeBtn = closeBtn closeBtn:SetPoint("BOTTOMRIGHT", -4, 4) closeBtn:SetWidth(120) closeBtn:SetHeight(30) closeBtn:SetText(DONE) closeBtn:SetScript("OnClick", function() frame:Hide() end) local stepsText = frame:CreateFontString() frame.stepsText = stepsText stepsText:SetWidth(200) stepsText:SetHeight(30) stepsText:SetPoint("BOTTOM", 0, 4) stepsText:SetFontObject(GameFontNormal) stepsText:SetTextColor(1, 0, 0, 1) stepsText:SetJustifyH("CENTER") stepsText:SetJustifyV("MIDDLE") stepsText:SetText("Please enter steps before submitting") end -- ============================================================================ -- Register Error Handler -- ============================================================================ do private.origErrorHandler = geterrorhandler() local function ErrorHandlerFunc(errMsg, isBugGrabber) local tsmErrMsg = strtrim(tostring(errMsg)) if private.ignoreErrors then -- we're ignoring errors tsmErrMsg = nil elseif strmatch(tsmErrMsg, "auc%-stat%-wowuction") or strmatch(tsmErrMsg, "TheUndermineJournal%.lua") or strmatch(tsmErrMsg, "\\SavedVariables\\TradeSkillMaster") or strmatch(tsmErrMsg, "AddOn TradeSkillMaster[_a-zA-Z]* attempted") or (strmatch(tsmErrMsg, "ItemTooltipClasses\\Wrapper%.lua:98") and strmatch(tsmErrMsg, "SetQuest")) then -- explicitly ignore these errors tsmErrMsg = nil end if tsmErrMsg then -- look at the stack trace to see if this is a TSM error for i = 2, MAX_STACK_DEPTH do local stackLine = debugstack(i, 1, 0) local oldModule = strmatch(stackLine, "(lMaster_[A-Za-z]+)") if oldModule and tContains(OLD_TSM_MODULES, "TradeSkil"..oldModule) then -- ignore errors from old modules return end if not strmatch(stackLine, "^%[C%]:") and not strmatch(stackLine, "%(tail call%):") and not strmatch(stackLine, "^%[string \"[^@]") and not strmatch(stackLine, "lMaster\\External\\[A-Za-z0-9%-_%.]+\\") and not strmatch(stackLine, "SharedXML") and not strmatch(stackLine, "CallbackHandler") and not strmatch(stackLine, "!BugGrabber") and not strmatch(stackLine, "ErrorHandler%.lua") then if not private.IsTSMAddon(stackLine) then tsmErrMsg = nil end break end end end if tsmErrMsg then local status, ret = pcall(private.ErrorHandler, tsmErrMsg) if status and ret then return ret elseif not status and not private.hitInternalError then private.hitInternalError = true print("Internal TSM error: "..tostring(ret)) end end local oldModule = strmatch(errMsg, "(lMaster_[A-Za-z]+)") if oldModule and tContains(OLD_TSM_MODULES, "TradeSkil"..oldModule) then -- ignore errors from old modules return end if not isBugGrabber then return private.origErrorHandler and private.origErrorHandler(errMsg) or nil end end seterrorhandler(ErrorHandlerFunc) if BugGrabber and BugGrabber.RegisterCallback then BugGrabber.RegisterCallback({}, "BugGrabber_BugGrabbed", function(_, errObj) ErrorHandlerFunc(errObj.message, true) end) end Event.Register("ADDON_ACTION_FORBIDDEN", private.AddonBlockedHandler) Event.Register("ADDON_ACTION_BLOCKED", private.AddonBlockedHandler) end