## page was renamed from WritingPageModes = Basic Page Mode = We will walk through building a whiz-bang little page mode for that most ubiquitous of Internet grottos, example.com. == define_page_mode == The main form of any page mode is the `define_page_mode` form. Its two required arguments are the symbolic name of the page mode, and the human-readable name. In addition to the required arguments, it accepts several optional keyword arguments. Typically, you will use both `$enable` and `$disable`, which give the setup and teardown functions for your mode. Each takes a buffer as its single argument. {{{ define_page_mode("example_mode", "Example", $enable = function (buffer) { // setup }, $disable = function (buffer) { // teardown }); }}} == auto_mode_list == Telling Conkeror when to turn on your page mode is as simple as adding a handler to `auto_mode_list`. You make a regular expression that matches the urls for which your page mode is intended, and push that, in association with your page mode function, onto `auto_mode_list`. This registration should be the last thing in the javascript file for your page mode. We'll start with a short example, then we'll pick it apart. {{{ let (re = build_url_regex($domain = "example", $tlds = ["com", "net", "org"], $allow_www = true)) { auto_mode_list.push([re, example_mode]); }; }}} === build_url_regex === Since creating regular expressions to match urls has patterns that are used over and over again, Conkeror has a function to simplify the creation of these regular expressions, called `build_url_regex`. It takes several keyword arguments that specify parts of an url, and produces a regexp. $domain:: :: A regexp or a literal string to match the domain name, not including the top-level domain (.com, .net, .org, etc), and not including `www.`, unless the `www.` is ''required''. $allow_www:: :: Boolean where `true` means that the domain name may optionally have the subdomain `www.`. Default is false. $tlds:: :: A list of allowed top-level domains. The default is the list `["com"]`. $path:: :: A regexp or a literal string to match against the path portion of the url, excluding the initial `/`. The default matches any path. = Adding a Keymap = If you go to example.com and hit tab, you will see Conkeror's normal error message, `No form field found` because tab is bound to `browser-focus-next-form-field`. But in the underlying Gecko engine, the tab key will also move focus among links. For reasons we need not explain, we will add a key binding to example-mode that lets the tab key fall through to Gecko, instead of letting it be processed by Conkeror. First we make a keymap with our binding. We set the `$parent` of our keymap to `content_buffer_normal_keymap` because we will be overriding that keymap, and we want any keys not bound in our new keymap to still be looked up in that one. {{{ define_keymap("example_keymap", $parent = content_buffer_normal_keymap); define_key(example_keymap, "tab", null, $fallthrough); }}} Now we will modify the `define_page_mode` form that we gave earlier to declare this keymap as an override for `normal_input_mode`. The `$enable` and `$disable` functions have been removed, because we are not using them. {{{ define_page_mode("example_mode", "Example", $keymaps = {normal_input_mode: example_keymap}); }}} = Invocation of Page Modes = If you are going to write a page mode, you will very well want to know when and how a page mode gets run. == Auto-Enabling == In Conkeror, web browsing gets done in ''content buffers''. There are other kinds of buffers, like those for displaying progress of downloads, and for showing help, but page modes only exist in content buffers. 1. The `content_buffer` data type has a method called `onLocationChange`, which is an event handler that gets called by Mozilla when the location of the buffer's `xul:browser` element changes. 1. The `onLocationChange` event handler runs the hook `content_buffer_location_change_hook`. 1. One of the functions attached to `content_buffer_location_change_hook` is `page_mode_auto_update`. 1. `page_mode_auto_update` searches the associative list `auto_mode_list` for a regexp key which matches the new url of the buffer. When it finds one, it calls the function associated with that key to enable the page mode. == Auto-Disabling == Page modes are a type of buffer-mode. (The mode-class is `page_mode`.) Buffer modes within any given mode-class are mutually exclusive. Thus if you enable one page mode, the underlying buffer-mode mechanism makes sure that any other page mode gets disabled first. But there is one special corner case particular to page modes. If `page_mode_auto_update` did not find a page mode to enable, then the mutual exclusivity mechanism of buffer modes cannot clean up the old mode. In that case, `page_mode_auto_update` will disable the old mode itself. Thus only one page-mode can be current in a buffer at one time, and it is also possible to have no current page mode. = Tips for Writing Good Page Modes = Don't make assumptions about the page being completely downloaded:: :: Try to make your page mode handle partially-downloaded content reasonably. Waiting until the entire page has loaded before allowing basic functionality should be considered the ''nuclear option'', and only used when absolutely necessary. Avoid using wrappedJSObject:: :: Whatever you are trying to do, there is probably a better way. Any use of `wrappedJSObject` on content DOM can provide a vector of attack from malicious content. Even if you trust the site in question, steering clear of dodgy techniques like this makes your code more robust and trustworthy.