TradeSkillMaster/LibTSM/Service/ErrorHandler.lua

770 lines
26 KiB
Lua
Raw Normal View History

2020-11-13 14:13:12 -05:00
-- ------------------------------------------------------------------------------ --
-- 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, " = <function> defined @", "@")
locals = gsub(locals, "<table> {", "{")
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, "<unnamed> {}$") 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 = <userdata>$") 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 "<name>", addonFunc or "<func>")
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