Firebug internals

  • Revision slug: Firebug_internals
  • Revision title: Firebug internals
  • Revision id: 437477
  • Created:
  • Creator: Jonathan_Watt
  • Is current revision? No
  • Comment

Revision Content

The information on this page is years out of date. The page has been migrated to http://getfirebug.com/wiki/index.php/Firebug_Internals 

 

Get Firebug

Source Code

Firebug source is available via subversion

Alternative:

  1. Get Firebug; The source is included.
  2. Find the installed firebug in the Mozilla extensions directory, eg
C:\Documents and Settings\John J. Barton\Application Data\Mozilla\Firefox\Profiles\w5tmpjcs.test\extensions\firebug@software.joehewitt.com\
  1. Copy the directory to your workspace
  2. find firebug.jar in the chrome directory.
  3. rename it to firebug.zip.
  4. unzip it such that chrome directory contains the content of the jar,eg
  • chrome/content
  • chrome/icons
  • chrome/locale
  • chrome/skin

Rezip the files into a jar for testing.

Major Components

  • Panels - these are the tabs in the firebug UI
  • Modules - non-UI, per tab code
  • lib aka FBL aka Firebug Library - common functions used in many panels
  • firebug-service - javascript - implemented XPCOM component, in firebug/components.
    • Since this is implemented as an XPCOM service, there is one FBS per Firefox executable. Everything else is one per tab or window.
  • sourceCache - stores .htm, .js content read for source views
  • reps.js - representations usually used by multiple panels.
  • error.js - console observer for nsIScriptError and nsIConsoleMessage
  • XUL files - structure for the UI
    • Run Firefox->Tools->DOM Inspector, then File->InspectAWindow-> your FF page. This points the Inspector at main-window. Expand the + signs until you get to main-window->browser-stack->appcontent. There you will see fbConentSplitter and fbContextBox
  • css files - decorations
  • firebug.js
    • Globals like top.Firebug: version state
    • Base objects:
      • Firebug.Module, base for eg debugger
      • Firebug.Panel, interface for panels

Extending Firebug

Checkout Christoph Dorn's awesome tutorial on Extending Firebug. Also Jan Odvarko has written a series of detailed tutorials on the toolbar, options, localization and domplate system.

Using Console Groups

Firebug.Console.openGroup([title], null, "group", null, false);
Firebug.Console.log(variable);
Firebug.Console.closeGroup();

Listening to NetMonitor (FB 1.2)

Firebug.MonitorModel.HttpMonitor = extend(Firebug.Module,
{
    initializeUI: function(detachArgs)
    {
        Firebug.NetMonitor.addListener(this);
    },

    shutdown: function()
    {
        Firebug.NetMonitor.removeListener(this);
    },

    onLoad: function(context, file)
    {
        if (FBTrace.DBG_MONITOR_NET)
            FBTrace.dumpProperties("--> " + file.method + " " + file.href, file);
    }
});

Glue

  • context ~ a web page state manager, passed to many functions.
    • A context is Firebug's container for all the state it needs for a web page.
    • Every page with active Firebug has one.
    • Every panel is initialized with a context, held by the panel in this.context.
    • Inside of this.context is this.context.window, a ref to the web page window.
    • Each web page has its own copy of the panels
  • Firebug.registerModule(Errors);
    • Errors extends Firebug.Module
  • Firebug.registerPanel(ScriptPanel);
    • ScriptPanel extends Panel

PluginPanel

PluginPanel defined in plugin.js

  • looks like a base for tabContext.createPanelType
  • getOptionsMenuItems is the options filling function, shows up on all panels
  • script panel is in debugger.js

debugger.js

Interface to firebug-service and implementation of the "script" panel.

debugger.showStackFrame

  • puts up the stack across the top of the script panel.

debuggr.onError(frame) called by FBS.onDebug(frame, type, rv)

  • so the frame should be a jsdIStackFrame
  • only called if reportNextError true, a flag set by onError when this.showStackTrace (console prefs)
  • sets Firebug.errorStackTrace (not per context?)

 

Firebug Service

fbs is FirebugService implemented in firebug-serivces.js, under components dir

 const fbs = CCSV("@joehewitt.com/firebug;1", "nsIFireBug");
 this.fbs = this.CCSV("@joehewitt.com/firebug;1", "nsIFireBug");
 this.jsd = this.CCSV("@mozilla.org/js/jsd/debugger-service;1", "jsdIDebuggerService");

Debugger objects can be registered with the service, then the service sets hooks (callbacks) into jsdIDebuggerService (jsd) and routes the results to the correct debugger for a given web page. The routing mechanism is a bit tricky: FBS calls supportsWindow for each debugger until one says "true"; as a side-effect of this call, the debugger sets breakContext equal to the window. Then FBS calls the debugger function (eg onXXX()) and the debugger (in its onXXX()) sets its context to breakContext. (The JS engine is thankfully single threaded).

interface nsIFireBugDebugger : nsISupports
{
  boolean supportsWindow(in nsIDOMWindow window);
  void onLock(in boolean state); 
  unsigned long onBreak(in jsdIStackFrame frame);
  unsigned long onHalt(in jsdIStackFrame frame);
  void onCall(in jsdIStackFrame frame);
  void onError(in jsdIStackFrame frame);
  void onResume();
  void onToggleBreakpoint(in string url, in unsigned long lineNo, in boolean isSet);
  void onToggleBreakpointCondition(in string url, in unsigned long lineNo, in boolean isSet);
  void onToggleBreakpointDisabled(in string url, in unsigned long lineNo, in boolean disabled);
  void onToggleErrorBreakpoint(in string url, in unsigned long lineNo, in boolean isSet);
  void onToggleMonitor(in string url, in unsigned long lineNo, in boolean isSet);
};

The service is in a different execution space from the UI; data only flows through the interfaces. This keeps jsd objects out of the UI for the most part. The all important stack is the major exception.

To change the service you must delete the file compreg.dat in the FF extension directory to force it to re-read the service definitions.

Extending nsIFirebug.idl

The firebug-service is an XPCOM component implemented in javascript. To change its API you need to create a new .idl file, compile it with xpidl.exe, and put the new .xpt file in to your .xpi file. Your interface should inherit from the ones in nsIFireBug.idl and your idl file should include nsIFireBug.idl You will also need two other idl files from XPCOM. These files and the xpcom.exe driver are available in the Gecko SDK.

The idl tools are rather terse so here are some hints for checking that you are making progress:

  • after you create your .xpt file, eg ./xpidl.exe -m typelib foo.idl, open the .xpt file, eg foo.xpt, with an edit like emacs that can show binary content. You should be able to make out the strings from your interface amongst the bits.
  • after you place your .xpt file in the components directory of your extension, delete xpti.dat from the Mozilla Firefox profile you are using. Run Firefox then open the xpti.dat file with a real editor. You should see your new interface(s) lists.
  • if you define a service, do the same check with compreg.dat.

Useful high-level questions

Here's a list of questions and grey areas that programmers coming new into Firebug's internals might benefit from having clarified. (Section added at John Barton's request).

Where is the entrypoint?

Starting up and tracking windows is one of the complex areas of Firebug.

  1. Firefox starts, loads its browser.xul into an OS window
  2. Firefox reads firebug extension directory under profile, finds chrome.manifest, sees an overlay.
  3. Firefox overlays browser.xul with chrome://firebug/content/browserOverlay.xul
  4. browserOverlay.xul calls in chrome://firebug/content/firebugOverlay.xul
  5. firebugOverlay.xul calls in chrome://firebug/content/firebug.css
  6. firebug.css calls in -moz-binding: url("chrome://firebug/content/bindings.xml#initializer");
  7. In bindings.xml the first call into Javascript is FirebugChrome.panelBarReady(this);

You can learn more about the rest of the action by setting FBTrace panel options INITIALIZE and PANELS

Firebug's event model: how does it work?

I don't understand the question. Are you asking about the dispatch mechanism for panel integration?

  • a brief high-level description of the event model. (Kernel of an answer: It is not the DOM model of events; rather, each object in the set of potentially interested objects gets its event handler invoked if it has one.)
  • which events happen when, on which objects
  • clear documentation of parameters

 

Window terminology

Often (for example, in the Module prototype), "window" is used somewhat ambiguously: does it mean browser window (something holding a tabbedbrowser) or does it mean DOM Window (the global context of in-web-page JS)?

I know of at least 5 meanings of "window":

  1. The user interface rectangle visible through the OS window management.
  2. The top level nsIXULWindow displaying browser.xul
  3. Sub-windows of browser.xul
  4. The nsIDOMWindow objects containing web pages
  5. sub-windows of the web page, eg iframes.

The vast majority of the time in Firebug the variable "window" refers to one of these last two things. In general you can't know which of the last two you have, so you'll see code that walks up or down the chain windows.

Contexts

  • what is the set of things they always contain (window is one)
    • browser, ref to the object containing the users web page
    • Add FBTrace.dumpProperties("file.methodname: ", context); to a panel method with a context argument and see what you see.
  • can I hang onto a reference to one?
    • yes, panels have this.context, but you can imagine that there is a set of panels for each context managed by firebug. Inside panel code you just use this.context.
  • can I put my own properties on it?
    • yes, on this.context in panel for example. There is a persistence mechanism as well.
  • what should contexts contain.
    • per-page firebug state
  • why is onMonitorEvent a method of a context (and not in a module) ?
    • Modules are one per Firefox; panels and context are one per user web page.

State

brief description of how panel state is (supposed to be) persisted

  • Relationship among objects...
    • panels have a context, and this context has a (dom) window
    • do panels have modules or vice versa or both?

Misc

Why the different platform code?

  • I don't know what you mean.

DOMPlate

Seems very useful, but it's a little cryptic.

  • Yep.

There is a great introduction by Jan Odvarko and Christoph Dorn has started a separate project to enable the use of the domplate system in any web page.

Namespaces

  • A note for them to go understand the semantics of "Function.apply()" and "this" if they don't understand how the (clever) namespacing works

Revision Source

<div class="warning">
  <p><strong>The information on this page is years out of date. The page has been migrated to <a class="external" href="http://getfirebug.com/wiki/index.php/Firebug_Internals" title="http://getfirebug.com/wiki/index.php/Firebug_Internals">http://getfirebug.com/wiki/index.php/Firebug_Internals&nbsp; </a></strong></p>
</div>
<p>&nbsp;</p>
<p><a class="external" href="http://getfirebug.com/">Get Firebug</a></p>
<h3 id="Source_Code" name="Source_Code">Source Code</h3>
<p>Firebug source is available <a class="external" href="http://code.google.com/p/fbug/source">via subversion </a></p>
<p>Alternative:</p>
<ol>
  <li><a class="external" href="http://getfirebug.com/">Get Firebug</a>; The source is included.</li>
  <li>Find the installed firebug in the Mozilla extensions directory, eg</li>
</ol>
<pre class="eval">
C:\Documents and Settings\John J. Barton\Application Data\Mozilla\Firefox\Profiles\w5tmpjcs.test\extensions\<a class="link-mailto" href="mailto:firebug@software.joehewitt.com" rel="freelink">firebug@software.joehewitt.com</a>\
</pre>
<ol>
  <li>Copy the directory to your workspace</li>
  <li>find firebug.jar in the chrome directory.</li>
  <li>rename it to firebug.zip.</li>
  <li>unzip it such that chrome directory contains the content of the jar,eg</li>
</ol>
<ul>
  <li>chrome/content</li>
  <li>chrome/icons</li>
  <li>chrome/locale</li>
  <li>chrome/skin</li>
</ul>
<p>Rezip the files into a jar for testing.</p>
<h3 id="Major_Components" name="Major_Components">Major Components</h3>
<ul>
  <li>Panels - these are the tabs in the firebug UI</li>
  <li>Modules - non-UI, per tab code</li>
  <li>lib aka FBL aka Firebug Library - common functions used in many panels</li>
  <li><a href="#Firebug_Service">firebug-service</a> - javascript - implemented XPCOM component, in firebug/components.
    <ul>
      <li>Since this is implemented as an XPCOM service, there is one FBS per Firefox executable. Everything else is one per tab or window.</li>
    </ul>
  </li>
</ul>
<ul>
  <li>sourceCache - stores .htm, .js content read for source views</li>
  <li>reps.js - representations usually used by multiple panels.</li>
  <li>error.js - console observer for nsIScriptError and nsIConsoleMessage</li>
  <li>XUL files - structure for the UI
    <ul>
      <li>Run Firefox-&gt;Tools-&gt;DOM Inspector, then File-&gt;InspectAWindow-&gt; your FF page. This points the Inspector at main-window. Expand the + signs until you get to main-window-&gt;browser-stack-&gt;appcontent. There you will see fbConentSplitter and fbContextBox</li>
    </ul>
  </li>
  <li>css files - decorations</li>
  <li>firebug.js
    <ul>
      <li>Globals like top.Firebug: version state</li>
      <li>Base objects:
        <ul>
          <li>Firebug.Module, base for eg debugger</li>
          <li>Firebug.Panel, interface for panels</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
<h3 id="Extending_Firebug" name="Extending_Firebug">Extending Firebug</h3>
<p>Checkout Christoph Dorn's awesome tutorial on <a class="external" href="http://www.firephp.org/Reference/Developers/ExtendingFirebug.htm">Extending Firebug</a>. Also <a class="external" href="http://www.softwareishard.com/blog/firebug-tutorial/extending-firebug-hello-world-part-i/">Jan Odvarko</a> has written a series of detailed tutorials on the toolbar, options, localization and domplate system.</p>
<h4 id="Using_Console_Groups" name="Using_Console_Groups">Using Console Groups</h4>
<pre>
Firebug.Console.openGroup([title], null, "group", null, false);
Firebug.Console.log(variable);
Firebug.Console.closeGroup();
</pre>
<h4 id="Listening_to_NetMonitor_.28FB_1.2.29" name="Listening_to_NetMonitor_.28FB_1.2.29">Listening to NetMonitor (FB 1.2)</h4>
<pre>
Firebug.MonitorModel.HttpMonitor = extend(Firebug.Module,
{
    initializeUI: function(detachArgs)
    {
        Firebug.NetMonitor.addListener(this);
    },

    shutdown: function()
    {
        Firebug.NetMonitor.removeListener(this);
    },

    onLoad: function(context, file)
    {
        if (FBTrace.DBG_MONITOR_NET)
            FBTrace.dumpProperties("--&gt; " + file.method + " " + file.href, file);
    }
});
</pre>
<h3 id="Glue" name="Glue">Glue</h3>
<ul>
  <li>context ~ a web page state manager, passed to many functions.
    <ul>
      <li>A context is Firebug's container for all the state it needs for a web page.</li>
      <li>Every page with active Firebug has one.</li>
      <li>Every panel is initialized with a <em>context</em>, held by the panel in <em>this.context</em>.</li>
      <li>Inside of <em>this.context</em> is <em>this.context.window</em>, a ref to the web page window.</li>
      <li>Each web page has its own copy of the panels</li>
    </ul>
  </li>
  <li>Firebug.registerModule(Errors);
    <ul>
      <li>Errors extends Firebug.Module</li>
    </ul>
  </li>
  <li>Firebug.registerPanel(ScriptPanel);
    <ul>
      <li>ScriptPanel extends Panel</li>
    </ul>
  </li>
</ul>
<h3 id="PluginPanel" name="PluginPanel">PluginPanel</h3>
<p>PluginPanel defined in plugin.js</p>
<ul>
  <li>looks like a base for tabContext.createPanelType</li>
  <li>getOptionsMenuItems is the options filling function, shows up on all panels</li>
  <li>script panel is in debugger.js</li>
</ul>
<h3 id="debugger.js" name="debugger.js">debugger.js</h3>
<p>Interface to <a href="#Firebug_Service"> firebug-service</a> and implementation of the "script" panel.</p>
<p>debugger.showStackFrame</p>
<ul>
  <li>puts up the stack across the top of the script panel.</li>
</ul>
<p>debuggr.onError(frame) called by FBS.onDebug(frame, type, rv)</p>
<ul>
  <li>so the frame should be a jsdIStackFrame</li>
  <li>only called if reportNextError true, a flag set by onError when this.showStackTrace (console prefs)</li>
  <li>sets Firebug.errorStackTrace (not per context?)</li>
</ul>
<p>&nbsp;</p>
<h3 id="Firebug_Service" name="Firebug_Service">Firebug Service</h3>
<p>fbs is FirebugService implemented in firebug-serivces.js, under components dir</p>
<pre class="eval">
 const fbs = CCSV("@joehewitt.com/firebug;1", "nsIFireBug");
 this.fbs = this.CCSV("@joehewitt.com/firebug;1", "nsIFireBug");
 this.jsd = this.CCSV("@mozilla.org/js/jsd/debugger-service;1", "jsdIDebuggerService");
</pre>
<p>Debugger objects can be registered with the service, then the service sets hooks (callbacks) into jsdIDebuggerService (jsd) and routes the results to the correct debugger for a given web page. The routing mechanism is a bit tricky: FBS calls <code>supportsWindow</code> for each debugger until one says "true"; as a side-effect of this call, the debugger sets breakContext equal to the window. Then FBS calls the debugger function (eg onXXX()) and the debugger (in its onXXX()) sets its context to breakContext. (The JS engine is thankfully single threaded).</p>
<pre class="eval">
interface nsIFireBugDebugger&nbsp;: nsISupports
{
  boolean supportsWindow(in nsIDOMWindow window);
  void onLock(in boolean state); 
  unsigned long onBreak(in jsdIStackFrame frame);
  unsigned long onHalt(in jsdIStackFrame frame);
  void onCall(in jsdIStackFrame frame);
  void onError(in jsdIStackFrame frame);
  void onResume();
  void onToggleBreakpoint(in string url, in unsigned long lineNo, in boolean isSet);
  void onToggleBreakpointCondition(in string url, in unsigned long lineNo, in boolean isSet);
  void onToggleBreakpointDisabled(in string url, in unsigned long lineNo, in boolean disabled);
  void onToggleErrorBreakpoint(in string url, in unsigned long lineNo, in boolean isSet);
  void onToggleMonitor(in string url, in unsigned long lineNo, in boolean isSet);
};
</pre>
<p>The service is in a different execution space from the UI; data only flows through the interfaces. This keeps jsd objects out of the UI for the most part. The all important stack is the major exception.</p>
<p>To change the service you must delete the file compreg.dat in the FF extension directory to force it to re-read the service definitions.</p>
<h3 id="Extending_nsIFirebug.idl" name="Extending_nsIFirebug.idl">Extending nsIFirebug.idl</h3>
<p>The firebug-service is an XPCOM component implemented in javascript. To change its API you need to create a new .idl file, compile it with xpidl.exe, and put the new .xpt file in to your .xpi file. Your interface should inherit from the ones in nsIFireBug.idl and your idl file should include nsIFireBug.idl You will also need two other idl files from XPCOM. These files and the xpcom.exe driver are available in the <a class="external" href="http://developer.mozilla.org/en/docs/Gecko_SDK">Gecko SDK</a>.</p>
<p>The idl tools are rather terse so here are some hints for checking that you are making progress:</p>
<ul>
  <li>after you create your .xpt file, eg ./xpidl.exe -m typelib foo.idl, open the .xpt file, eg foo.xpt, with an edit like emacs that can show binary content. You should be able to make out the strings from your interface amongst the bits.</li>
  <li>after you place your .xpt file in the components directory of your extension, delete <code>xpti.dat</code> from the Mozilla Firefox profile you are using. Run Firefox then open the <code>xpti.dat</code> file with a real editor. You should see your new interface(s) lists.</li>
  <li>if you define a service, do the same check with <code>compreg.dat</code>.</li>
</ul>
<h3 id="Useful_high-level_questions" name="Useful_high-level_questions">Useful high-level questions</h3>
<p>Here's a list of questions and grey areas that programmers coming new into Firebug's internals might benefit from having clarified. (Section added at John Barton's request).</p>
<h4 id="Where_is_the_entrypoint.3F" name="Where_is_the_entrypoint.3F">Where is the entrypoint?</h4>
<p>Starting up and tracking windows is one of the complex areas of Firebug.</p>
<ol>
  <li>Firefox starts, loads its browser.xul into an OS window</li>
  <li>Firefox reads firebug extension directory under profile, finds chrome.manifest, sees an overlay.</li>
  <li>Firefox overlays browser.xul with <a class="external" href="chrome://firebug/content/browserOverlay.xul" rel="freelink">chrome://firebug/content/browserOverlay.xul</a></li>
  <li>browserOverlay.xul calls in <a class="external" href="chrome://firebug/content/firebugOverlay.xul" rel="freelink">chrome://firebug/content/firebugOverlay.xul</a></li>
  <li>firebugOverlay.xul calls in <a class="external" href="chrome://firebug/content/firebug.css" rel="freelink">chrome://firebug/content/firebug.css</a></li>
  <li>firebug.css calls in -moz-binding: url("<a class="external" href="chrome://firebug/content/bindings.xml#initializer" rel="freelink">chrome://firebug/content/bindings.xml#initializer</a>");</li>
  <li>In bindings.xml the first call into Javascript is FirebugChrome.panelBarReady(this);</li>
</ol>
<p>You can learn more about the rest of the action by setting FBTrace panel options INITIALIZE and PANELS</p>
<h4 id="Firebug.27s_event_model:_how_does_it_work.3F" name="Firebug.27s_event_model:_how_does_it_work.3F">Firebug's event model: how does it work?</h4>
<p><em>I don't understand the question. Are you asking about the dispatch mechanism for panel integration?</em></p>
<ul>
  <li>a brief high-level description of the event model. (Kernel of an answer: It is not the DOM model of events; rather, each object in the set of potentially interested objects gets its event handler invoked if it has one.)</li>
  <li>which events happen when, on which objects</li>
  <li>clear documentation of parameters</li>
</ul>
<p>&nbsp;</p>
<h4 id="Window_terminology" name="Window_terminology">Window terminology</h4>
<p>Often (for example, in the Module prototype), "window" is used somewhat ambiguously: does it mean browser window (something holding a tabbedbrowser) or does it mean DOM Window (the global context of in-web-page JS)?</p>
<p>I know of at least 5 meanings of "window":</p>
<ol>
  <li>The user interface rectangle visible through the OS window management.</li>
  <li>The top level nsIXULWindow displaying browser.xul</li>
  <li>Sub-windows of browser.xul</li>
  <li>The nsIDOMWindow objects containing web pages</li>
  <li>sub-windows of the web page, eg iframes.</li>
</ol>
<p>The vast majority of the time in Firebug the variable "window" refers to one of these last two things. In general you can't know which of the last two you have, so you'll see code that walks up or down the chain windows.</p>
<h4 id="Contexts" name="Contexts">Contexts</h4>
<ul>
  <li>what is the set of things they always contain (<code>window</code> is one)
    <ul>
      <li><code>browser</code>, ref to the object containing the users web page</li>
      <li>Add <code>FBTrace.dumpProperties("file.methodname: ", context);</code> to a panel method with a context argument and see what you see.</li>
    </ul>
  </li>
  <li>can I hang onto a reference to one?
    <ul>
      <li>yes, panels have <code>this.context</code>, but you can imagine that there is a set of panels for each <code>context</code> managed by firebug. Inside panel code you just use <code>this.context</code>.</li>
    </ul>
  </li>
  <li>can I put my own properties on it?
    <ul>
      <li>yes, on <code>this.context</code> in panel for example. There is a persistence mechanism as well.</li>
    </ul>
  </li>
  <li>what should contexts contain.
    <ul>
      <li>per-page firebug state</li>
    </ul>
  </li>
  <li>why is <code>onMonitorEvent</code> a method of a context (and not in a module)&nbsp;?
    <ul>
      <li>Modules are one per Firefox; panels and context are one per user web page.</li>
    </ul>
  </li>
</ul>
<h4 id="State" name="State">State</h4>
<p>brief description of how panel state is (supposed to be) persisted</p>
<ul>
  <li>Relationship among objects...
    <ul>
      <li>panels have a context, and this context has a (dom) window</li>
      <li>do panels have modules or vice versa or both?</li>
    </ul>
  </li>
</ul>
<h4 id="Misc" name="Misc">Misc</h4>
<p>Why the different platform code?</p>
<ul>
  <li><em>I don't know what you mean.</em></li>
</ul>
<h4 id="DOMPlate" name="DOMPlate">DOMPlate</h4>
<p>Seems very useful, but it's a little cryptic.</p>
<ul>
  <li>Yep.</li>
</ul>
<p>There is a great introduction by <a class="external" href="http://www.softwareishard.com/blog/firebug-tutorial/extending-firebug-domplate-part-v/">Jan Odvarko</a> and Christoph Dorn has started a <a class="external" href="http://code.google.com/p/domplate/">separate project</a> to enable the use of the domplate system in any web page.</p>
<h4 id="Namespaces" name="Namespaces">Namespaces</h4>
<ul>
  <li>A note for them to go understand the semantics of "Function.apply()" and "this" if they don't understand how the (clever) namespacing works</li>
</ul>
Revert to this revision