comparison Libs/AceHook-3.0/AceHook-3.0.lua @ 0:98c6f55e6619

First commit
author Xiiph
date Sat, 05 Feb 2011 16:45:02 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:98c6f55e6619
1 --- **AceHook-3.0** offers safe Hooking/Unhooking of functions, methods and frame scripts.
2 -- Using AceHook-3.0 is recommended when you need to unhook your hooks again, so the hook chain isn't broken
3 -- when you manually restore the original function.
4 --
5 -- **AceHook-3.0** can be embeded into your addon, either explicitly by calling AceHook:Embed(MyAddon) or by
6 -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
7 -- and can be accessed directly, without having to explicitly call AceHook itself.\\
8 -- It is recommended to embed AceHook, otherwise you'll have to specify a custom `self` on all calls you
9 -- make into AceHook.
10 -- @class file
11 -- @name AceHook-3.0
12 -- @release $Id: AceHook-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $
13 local ACEHOOK_MAJOR, ACEHOOK_MINOR = "AceHook-3.0", 5
14 local AceHook, oldminor = LibStub:NewLibrary(ACEHOOK_MAJOR, ACEHOOK_MINOR)
15
16 if not AceHook then return end -- No upgrade needed
17
18 AceHook.embeded = AceHook.embeded or {}
19 AceHook.registry = AceHook.registry or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end })
20 AceHook.handlers = AceHook.handlers or {}
21 AceHook.actives = AceHook.actives or {}
22 AceHook.scripts = AceHook.scripts or {}
23 AceHook.onceSecure = AceHook.onceSecure or {}
24 AceHook.hooks = AceHook.hooks or {}
25
26 -- local upvalues
27 local registry = AceHook.registry
28 local handlers = AceHook.handlers
29 local actives = AceHook.actives
30 local scripts = AceHook.scripts
31 local onceSecure = AceHook.onceSecure
32
33 -- Lua APIs
34 local pairs, next, type = pairs, next, type
35 local format = string.format
36 local assert, error = assert, error
37
38 -- WoW APIs
39 local issecurevariable, hooksecurefunc = issecurevariable, hooksecurefunc
40 local _G = _G
41
42 -- functions for later definition
43 local donothing, createHook, hook
44
45 local protectedScripts = {
46 OnClick = true,
47 }
48
49 -- upgrading of embeded is done at the bottom of the file
50
51 local mixins = {
52 "Hook", "SecureHook",
53 "HookScript", "SecureHookScript",
54 "Unhook", "UnhookAll",
55 "IsHooked",
56 "RawHook", "RawHookScript"
57 }
58
59 -- AceHook:Embed( target )
60 -- target (object) - target object to embed AceHook in
61 --
62 -- Embeds AceEevent into the target object making the functions from the mixins list available on target:..
63 function AceHook:Embed( target )
64 for k, v in pairs( mixins ) do
65 target[v] = self[v]
66 end
67 self.embeded[target] = true
68 -- inject the hooks table safely
69 target.hooks = target.hooks or {}
70 return target
71 end
72
73 -- AceHook:OnEmbedDisable( target )
74 -- target (object) - target object that is being disabled
75 --
76 -- Unhooks all hooks when the target disables.
77 -- this method should be called by the target manually or by an addon framework
78 function AceHook:OnEmbedDisable( target )
79 target:UnhookAll()
80 end
81
82 function createHook(self, handler, orig, secure, failsafe)
83 local uid
84 local method = type(handler) == "string"
85 if failsafe and not secure then
86 -- failsafe hook creation
87 uid = function(...)
88 if actives[uid] then
89 if method then
90 self[handler](self, ...)
91 else
92 handler(...)
93 end
94 end
95 return orig(...)
96 end
97 -- /failsafe hook
98 else
99 -- all other hooks
100 uid = function(...)
101 if actives[uid] then
102 if method then
103 return self[handler](self, ...)
104 else
105 return handler(...)
106 end
107 elseif not secure then -- backup on non secure
108 return orig(...)
109 end
110 end
111 -- /hook
112 end
113 return uid
114 end
115
116 function donothing() end
117
118 function hook(self, obj, method, handler, script, secure, raw, forceSecure, usage)
119 if not handler then handler = method end
120
121 -- These asserts make sure AceHooks's devs play by the rules.
122 assert(not script or type(script) == "boolean")
123 assert(not secure or type(secure) == "boolean")
124 assert(not raw or type(raw) == "boolean")
125 assert(not forceSecure or type(forceSecure) == "boolean")
126 assert(usage)
127
128 -- Error checking Battery!
129 if obj and type(obj) ~= "table" then
130 error(format("%s: 'object' - nil or table expected got %s", usage, type(obj)), 3)
131 end
132 if type(method) ~= "string" then
133 error(format("%s: 'method' - string expected got %s", usage, type(method)), 3)
134 end
135 if type(handler) ~= "string" and type(handler) ~= "function" then
136 error(format("%s: 'handler' - nil, string, or function expected got %s", usage, type(handler)), 3)
137 end
138 if type(handler) == "string" and type(self[handler]) ~= "function" then
139 error(format("%s: 'handler' - Handler specified does not exist at self[handler]", usage), 3)
140 end
141 if script then
142 if not secure and obj:IsProtected() and protectedScripts[method] then
143 error(format("Cannot hook secure script %q; Use SecureHookScript(obj, method, [handler]) instead.", method), 3)
144 end
145 if not obj or not obj.GetScript or not obj:HasScript(method) then
146 error(format("%s: You can only hook a script on a frame object", usage), 3)
147 end
148 else
149 local issecure
150 if obj then
151 issecure = onceSecure[obj] and onceSecure[obj][method] or issecurevariable(obj, method)
152 else
153 issecure = onceSecure[method] or issecurevariable(method)
154 end
155 if issecure then
156 if forceSecure then
157 if obj then
158 onceSecure[obj] = onceSecure[obj] or {}
159 onceSecure[obj][method] = true
160 else
161 onceSecure[method] = true
162 end
163 elseif not secure then
164 error(format("%s: Attempt to hook secure function %s. Use `SecureHook' or add `true' to the argument list to override.", usage, method), 3)
165 end
166 end
167 end
168
169 local uid
170 if obj then
171 uid = registry[self][obj] and registry[self][obj][method]
172 else
173 uid = registry[self][method]
174 end
175
176 if uid then
177 if actives[uid] then
178 -- Only two sane choices exist here. We either a) error 100% of the time or b) always unhook and then hook
179 -- choice b would likely lead to odd debuging conditions or other mysteries so we're going with a.
180 error(format("Attempting to rehook already active hook %s.", method))
181 end
182
183 if handlers[uid] == handler then -- turn on a decative hook, note enclosures break this ability, small memory leak
184 actives[uid] = true
185 return
186 elseif obj then -- is there any reason not to call unhook instead of doing the following several lines?
187 if self.hooks and self.hooks[obj] then
188 self.hooks[obj][method] = nil
189 end
190 registry[self][obj][method] = nil
191 else
192 if self.hooks then
193 self.hooks[method] = nil
194 end
195 registry[self][method] = nil
196 end
197 handlers[uid], actives[uid], scripts[uid] = nil, nil, nil
198 uid = nil
199 end
200
201 local orig
202 if script then
203 orig = obj:GetScript(method) or donothing
204 elseif obj then
205 orig = obj[method]
206 else
207 orig = _G[method]
208 end
209
210 if not orig then
211 error(format("%s: Attempting to hook a non existing target", usage), 3)
212 end
213
214 uid = createHook(self, handler, orig, secure, not (raw or secure))
215
216 if obj then
217 self.hooks[obj] = self.hooks[obj] or {}
218 registry[self][obj] = registry[self][obj] or {}
219 registry[self][obj][method] = uid
220
221 if not secure then
222 self.hooks[obj][method] = orig
223 end
224
225 if script then
226 -- If the script is empty before, HookScript will not work, so use SetScript instead
227 -- This will make the hook insecure, but shouldnt matter, since it was empty before.
228 -- It does not taint the full frame.
229 if not secure or orig == donothing then
230 obj:SetScript(method, uid)
231 elseif secure then
232 obj:HookScript(method, uid)
233 end
234 else
235 if not secure then
236 obj[method] = uid
237 else
238 hooksecurefunc(obj, method, uid)
239 end
240 end
241 else
242 registry[self][method] = uid
243
244 if not secure then
245 _G[method] = uid
246 self.hooks[method] = orig
247 else
248 hooksecurefunc(method, uid)
249 end
250 end
251
252 actives[uid], handlers[uid], scripts[uid] = true, handler, script and true or nil
253 end
254
255 --- Hook a function or a method on an object.
256 -- The hook created will be a "safe hook", that means that your handler will be called
257 -- before the hooked function ("Pre-Hook"), and you don't have to call the original function yourself,
258 -- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
259 -- This type of hook is typically used if you need to know if some function got called, and don't want to modify it.
260 -- @paramsig [object], method, [handler], [hookSecure]
261 -- @param object The object to hook a method from
262 -- @param method If object was specified, the name of the method, or the name of the function to hook.
263 -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
264 -- @param hookSecure If true, AceHook will allow hooking of secure functions.
265 -- @usage
266 -- -- create an addon with AceHook embeded
267 -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
268 --
269 -- function MyAddon:OnEnable()
270 -- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
271 -- self:Hook("ActionButton_UpdateHotkeys", true)
272 -- end
273 --
274 -- function MyAddon:ActionButton_UpdateHotkeys(button, type)
275 -- print(button:GetName() .. " is updating its HotKey")
276 -- end
277 function AceHook:Hook(object, method, handler, hookSecure)
278 if type(object) == "string" then
279 method, handler, hookSecure, object = object, method, handler, nil
280 end
281
282 if handler == true then
283 handler, hookSecure = nil, true
284 end
285
286 hook(self, object, method, handler, false, false, false, hookSecure or false, "Usage: Hook([object], method, [handler], [hookSecure])")
287 end
288
289 --- RawHook a function or a method on an object.
290 -- The hook created will be a "raw hook", that means that your handler will completly replace
291 -- the original function, and your handler has to call the original function (or not, depending on your intentions).\\
292 -- The original function will be stored in `self.hooks[object][method]` or `self.hooks[functionName]` respectively.\\
293 -- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
294 -- or want to control execution of the original function.
295 -- @paramsig [object], method, [handler], [hookSecure]
296 -- @param object The object to hook a method from
297 -- @param method If object was specified, the name of the method, or the name of the function to hook.
298 -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
299 -- @param hookSecure If true, AceHook will allow hooking of secure functions.
300 -- @usage
301 -- -- create an addon with AceHook embeded
302 -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
303 --
304 -- function MyAddon:OnEnable()
305 -- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
306 -- self:RawHook("ActionButton_UpdateHotkeys", true)
307 -- end
308 --
309 -- function MyAddon:ActionButton_UpdateHotkeys(button, type)
310 -- if button:GetName() == "MyButton" then
311 -- -- do stuff here
312 -- else
313 -- self.hooks.ActionButton_UpdateHotkeys(button, type)
314 -- end
315 -- end
316 function AceHook:RawHook(object, method, handler, hookSecure)
317 if type(object) == "string" then
318 method, handler, hookSecure, object = object, method, handler, nil
319 end
320
321 if handler == true then
322 handler, hookSecure = nil, true
323 end
324
325 hook(self, object, method, handler, false, false, true, hookSecure or false, "Usage: RawHook([object], method, [handler], [hookSecure])")
326 end
327
328 --- SecureHook a function or a method on an object.
329 -- This function is a wrapper around the `hooksecurefunc` function in the WoW API. Using AceHook
330 -- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
331 -- required anymore, or the addon is being disabled.\\
332 -- Secure Hooks should be used if the secure-status of the function is vital to its function,
333 -- and taint would block execution. Secure Hooks are always called after the original function was called
334 -- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
335 -- @paramsig [object], method, [handler]
336 -- @param object The object to hook a method from
337 -- @param method If object was specified, the name of the method, or the name of the function to hook.
338 -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
339 function AceHook:SecureHook(object, method, handler)
340 if type(object) == "string" then
341 method, handler, object = object, method, nil
342 end
343
344 hook(self, object, method, handler, false, true, false, false, "Usage: SecureHook([object], method, [handler])")
345 end
346
347 --- Hook a script handler on a frame.
348 -- The hook created will be a "safe hook", that means that your handler will be called
349 -- before the hooked script ("Pre-Hook"), and you don't have to call the original function yourself,
350 -- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
351 -- This is the frame script equivalent of the :Hook safe-hook. It would typically be used to be notified
352 -- when a certain event happens to a frame.
353 -- @paramsig frame, script, [handler]
354 -- @param frame The Frame to hook the script on
355 -- @param script The script to hook
356 -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
357 -- @usage
358 -- -- create an addon with AceHook embeded
359 -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
360 --
361 -- function MyAddon:OnEnable()
362 -- -- Hook the OnShow of FriendsFrame
363 -- self:HookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
364 -- end
365 --
366 -- function MyAddon:FriendsFrameOnShow(frame)
367 -- print("The FriendsFrame was shown!")
368 -- end
369 function AceHook:HookScript(frame, script, handler)
370 hook(self, frame, script, handler, true, false, false, false, "Usage: HookScript(object, method, [handler])")
371 end
372
373 --- RawHook a script handler on a frame.
374 -- The hook created will be a "raw hook", that means that your handler will completly replace
375 -- the original script, and your handler has to call the original script (or not, depending on your intentions).\\
376 -- The original script will be stored in `self.hooks[frame][script]`.\\
377 -- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
378 -- or want to control execution of the original script.
379 -- @paramsig frame, script, [handler]
380 -- @param frame The Frame to hook the script on
381 -- @param script The script to hook
382 -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
383 -- @usage
384 -- -- create an addon with AceHook embeded
385 -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
386 --
387 -- function MyAddon:OnEnable()
388 -- -- Hook the OnShow of FriendsFrame
389 -- self:RawHookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
390 -- end
391 --
392 -- function MyAddon:FriendsFrameOnShow(frame)
393 -- -- Call the original function
394 -- self.hooks[frame].OnShow(frame)
395 -- -- Do our processing
396 -- -- .. stuff
397 -- end
398 function AceHook:RawHookScript(frame, script, handler)
399 hook(self, frame, script, handler, true, false, true, false, "Usage: RawHookScript(object, method, [handler])")
400 end
401
402 --- SecureHook a script handler on a frame.
403 -- This function is a wrapper around the `frame:HookScript` function in the WoW API. Using AceHook
404 -- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
405 -- required anymore, or the addon is being disabled.\\
406 -- Secure Hooks should be used if the secure-status of the function is vital to its function,
407 -- and taint would block execution. Secure Hooks are always called after the original function was called
408 -- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
409 -- @paramsig frame, script, [handler]
410 -- @param frame The Frame to hook the script on
411 -- @param script The script to hook
412 -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
413 function AceHook:SecureHookScript(frame, script, handler)
414 hook(self, frame, script, handler, true, true, false, false, "Usage: SecureHookScript(object, method, [handler])")
415 end
416
417 --- Unhook from the specified function, method or script.
418 -- @paramsig [obj], method
419 -- @param obj The object or frame to unhook from
420 -- @param method The name of the method, function or script to unhook from.
421 function AceHook:Unhook(obj, method)
422 local usage = "Usage: Unhook([obj], method)"
423 if type(obj) == "string" then
424 method, obj = obj, nil
425 end
426
427 if obj and type(obj) ~= "table" then
428 error(format("%s: 'obj' - expecting nil or table got %s", usage, type(obj)), 2)
429 end
430 if type(method) ~= "string" then
431 error(format("%s: 'method' - expeting string got %s", usage, type(method)), 2)
432 end
433
434 local uid
435 if obj then
436 uid = registry[self][obj] and registry[self][obj][method]
437 else
438 uid = registry[self][method]
439 end
440
441 if not uid or not actives[uid] then
442 -- Declining to error on an unneeded unhook since the end effect is the same and this would just be annoying.
443 return false
444 end
445
446 actives[uid], handlers[uid] = nil, nil
447
448 if obj then
449 registry[self][obj][method] = nil
450 registry[self][obj] = next(registry[self][obj]) and registry[self][obj] or nil
451
452 -- if the hook reference doesnt exist, then its a secure hook, just bail out and dont do any unhooking
453 if not self.hooks[obj] or not self.hooks[obj][method] then return true end
454
455 if scripts[uid] and obj:GetScript(method) == uid then -- unhooks scripts
456 obj:SetScript(method, self.hooks[obj][method] ~= donothing and self.hooks[obj][method] or nil)
457 scripts[uid] = nil
458 elseif obj and self.hooks[obj] and self.hooks[obj][method] and obj[method] == uid then -- unhooks methods
459 obj[method] = self.hooks[obj][method]
460 end
461
462 self.hooks[obj][method] = nil
463 self.hooks[obj] = next(self.hooks[obj]) and self.hooks[obj] or nil
464 else
465 registry[self][method] = nil
466
467 -- if self.hooks[method] doesn't exist, then this is a SecureHook, just bail out
468 if not self.hooks[method] then return true end
469
470 if self.hooks[method] and _G[method] == uid then -- unhooks functions
471 _G[method] = self.hooks[method]
472 end
473
474 self.hooks[method] = nil
475 end
476 return true
477 end
478
479 --- Unhook all existing hooks for this addon.
480 function AceHook:UnhookAll()
481 for key, value in pairs(registry[self]) do
482 if type(key) == "table" then
483 for method in pairs(value) do
484 self:Unhook(key, method)
485 end
486 else
487 self:Unhook(key)
488 end
489 end
490 end
491
492 --- Check if the specific function, method or script is already hooked.
493 -- @paramsig [obj], method
494 -- @param obj The object or frame to unhook from
495 -- @param method The name of the method, function or script to unhook from.
496 function AceHook:IsHooked(obj, method)
497 -- we don't check if registry[self] exists, this is done by evil magicks in the metatable
498 if type(obj) == "string" then
499 if registry[self][obj] and actives[registry[self][obj]] then
500 return true, handlers[registry[self][obj]]
501 end
502 else
503 if registry[self][obj] and registry[self][obj][method] and actives[registry[self][obj][method]] then
504 return true, handlers[registry[self][obj][method]]
505 end
506 end
507
508 return false, nil
509 end
510
511 --- Upgrade our old embeded
512 for target, v in pairs( AceHook.embeded ) do
513 AceHook:Embed( target )
514 end