JS and DOM for Gecko Hackers
Quick introduction and disclaimer
I assume webdev familiarity of JS and the DOM
Start by kicking off a build of mozilla-central
Shiny new ES6 classes
- First shipped in Firefox 45, so this is pretty new stuff
- If you've ever done OOP in other languages, this should feel familiar
- Typical approach for defining a class:
Extending, and static class methods
Expect to see more and more of this in the tree.
- For a while, we're going to have two different ways of creating classes in JavaScript in the tree. It's the circle of life.
- Probably best to use this newer style in new, green-field code.
- I've just scratched the surface here. Here's more documentation.
Old-school prototype-based inheritance...
- Usually abstracted away by libraries like Underscore.js or lodash
- Up until recently, this was how you did OOP and inheritance in JavaScript. Lots of Mozilla code still uses this.
- Typical approach for defining a "class":
Prototype-based inheritance...
let
IMO, let
performs like you'd expect, and sidesteps var
's footguns.
You might see var
's being used in new code being defined in the global scope.
Some scripts will have var
's exclusively. This is usually an age hint.
Object
and Array
destructuring
- Fancy shorthand for multi-variable assignment
- Useful if you have a function that returns an
Object
or an Array
that you only want a part of.
- Example:
Object
and Array
destructuring
Whew!
- Hopefully we're about 1 hour in, or thereabouts.
- Let's hear some questions, and do some exercises.
Asynchronicity and Concurrency
Asynchronous things in JavaScript
- JavaScript is single-threaded and event driven
- "Asynchronous" things queue a function to fire at a later time, but still on the main thread
- One can use
setTimeout
to break up heavy work into chunks, but there are better ways
Communicating with Workers
- Communication is done with message passing
- You can't send a function over unless you somehow
eval
it on the other side
- But if it can be serialized to JSON, then it can be sent
- Here's worker_example_1.js:
Communicating with Workers
ChromeWorkers
- Extends Workers with more powers, and ES6 stuff (like
let
)
- Non-standard, and not exposed to web content
- Has access to js-ctypes via a global
ctypes
.
Promises
- Asynchronous code can result in callback hell:
- You still see this pattern in older parts of our code, like old tests
Promises
- Promises reorient things by adding an intermediary "then-able" object
- Difference is subtle, but the results can be profound
- If
doFirstPart
, doSecondPart
and doLastPart
returned Promises, we could do:
- That might seem like a small win, but it gets better with
yield
and Tasks
Promises
- Let's write a function that returns a
Promise
- Questions about Promises? Because we're about to crank it up...
Tasks
- ES6 gives us the
yield
keyword
- Used for Generators, allows us to effectively halt and resume a function
- Coupled with Promises, this leads us to Tasks!
- Not sure how often they're used on the web, but used extensively in Firefox
- This was task.js, not Task.jsm, because this slideshow is in web content
Task.jsm
- Available as a JSM at resource://gre/modules/Task.jsm
- Control flow and error handling example:
Arrow functions
- Sometimes you're not in a position to use Tasks, so you need to pass a callback to a Promise's
then()
function.
- When you're doing this inside an Object method, it's often useful for the callback to bind
this
to the Object
- Before arrow functions, there were several ways to do this:
Whew!
- Hopefully we're about 2 hours in, or thereabouts (if not, we're in trouble, and I need to move faster).
- Let's hear some questions, and do some exercises.
Browser Tools and Extension-writing
The Browser Toolbox
- If you didn't already know, Firefox's UI ("chrome") uses the same rendering and JS engine as web content
- This means that our web developer tools are re-usable for the Firefox UI!
- The ability to inspect, manipulate and debug chrome code is disabled by default
A Tour of the Browser Toolbox
A brief introduction to WebExtensions
manifest.json
- This is where your WebExtension describes itself
- Here's some boilerplate for you:
{
"name": "My WebExtension",
"version": "1.0",
"manifest_version": 2,
"description": "Tooling around with WebExtensions",
"permissions": [],
"applications": {
"gecko": {
"id": "my-webextension@webextensions"
}
}
}
- Let's run it!
- It's installed, but it's boring. Let's do something neat...
Modifying pages with content scripts...
- Content scripts are injected into web content, and get an X-ray view of the DOM and JS environment of a page
- They have limited privileges, but can communicate things to the parent process where there are elevated privileges
- Open up manifest.json again...
Now our content script...
Packaging and signing
- Firefox add-ons are XPI files, which are really just ZIP files
- ZIP up the contents of that folder we just made, and you've got an (unsigned) add-on
xpinstall.signatures.required
- You can sign an add-on using the jpm tool, or by uploading to AMO.
We use a number of test frameworks
- My guess is that we discover that the existing framework is insufficient so we build a new one and never fully migrate
- There are other reasons - like we might want some tests to be cross-browser compatible
- Regardless, we have several different "types" of tests
Our testing frameworks:
- xpcshell: This test runs in a JavaScript "shell" with XPCOM access. No windows, and no DOM. Good for testing JS, or bits that don't require DOM.
- reftests: Tests that do some layout and / or rendering, which we then compare against a reference image. Good for testing graphics and layout.
- mochitest-plain: Tests web content with web content privleges. Good for testing for compliance with web specs.
- mochitest-chrome: Privileged tests running in a bare window. Good for testing XUL and layout.
- mochitest-browser: Privileged tests running in a browser. Good for exercising Firefox to do Firefox things.
- marionette: Our newest browser automation framework. Similar to Selenium. Can drive Firefox for Desktop, as well as B2G. This is probably the future.
- wpt: Similar to mochitest-plain, but actually being used between browsers (like Firefox and Servo).
- Here's a decision chart to help you determine which one you should use when.
How to run tests
./mach test [path-to-test]
./mach test browser/base/content/test/general/browser_audioTabIcon.js
# ^-- This will automatically choose the right framework,
# and is good if you just want to run the test simply.
# Note also that the test starts with browser_. This is a browser mochitest.
# xpcshell, mochitest-plain and mochitest-chrome start with test_.
./mach mochitest browser/base/content/test/general/browser_audioTabIcon.js
# Functionally equivalent to the first one
./mach mochitest browser/base/content/test/general/browser_audioTabIcon.js --disable-e10s
# --disable-e10s = Run without out-of-process tabs
# Check out --help for more options. I like --jsdebugger, --run-until-failure,
# --no-autorun, and --keep-open
Let's write a really simple mochitest-browser test!
- Firefox will open, and our test will start
- Our test will open a new tab
- Our test will make sure there are now 2 tabs
- Our test will close the tab it had opened
Get the test registered
- Find out where to put it. Let's put it under
browser/base/content/test/general
for now
- Give it a name. Let's call it
browser_simpletest.js
-
add_task(function*() {
ok(true, "My test didn't run.");
});
- Find the browser.ini
- Add browser_simpletest.js to browser.ini
- Make sure our test will run
./mach test browser/base/content/test/general/browser_simpletest.js
Let's write our actual test
Let's run it!
./mach test browser/base/content/test/general/browser_simpletest.js
Let's debug it if necessary
/