diff lisp/progmodes/js.el @ 104272:ea67ac46d172

* progmodes/js2-mode.el: File removed. * Makefile.in (ELCFILES): Add js.el, and remove js2-mode.el. * speedbar.el (speedbar-supported-extension-expressions): Add .js. * progmodes/hideshow.el (hs-special-modes-alist): Add js-mode entry. * progmodes/js.el: New file.
author Chong Yidong <cyd@stupidchicken.com>
date Fri, 14 Aug 2009 23:02:38 +0000
parents
children 3bf815184a6c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/progmodes/js.el	Fri Aug 14 23:02:38 2009 +0000
@@ -0,0 +1,3391 @@
+;;; js.el --- Major mode for editing JavaScript source text
+
+;; Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+;; Author: Karl Landstrom <karl.landstrom@brgeight.se>
+;;         Daniel Colascione <dan.colascione@gmail.com>
+;; Maintainer: Daniel Colascione <dan.colascione@gmail.com>
+;; Version: 9
+;; Date: 2009-07-25
+;; Keywords: languages, oop, javascript
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary
+
+;; This is based on Karl Landstrom's barebones javascript-mode. This
+;; is much more robust and works with cc-mode's comment filling
+;; (mostly).
+;;
+;; The main features of this JavaScript mode are syntactic
+;; highlighting (enabled with `font-lock-mode' or
+;; `global-font-lock-mode'), automatic indentation and filling of
+;; comments, C preprocessor fontification, and MozRepl integration.
+;;
+;; General Remarks:
+;;
+;; XXX: This mode assumes that block comments are not nested inside block
+;; XXX: comments
+;;
+;; Exported names start with "js-"; private names start with
+;; "js--".
+
+;;; Code:
+
+(eval-and-compile
+  (require 'cc-mode)
+  (require 'font-lock)
+  (require 'newcomment)
+  (require 'imenu)
+  (require 'etags)
+  (require 'thingatpt)
+  (require 'easymenu)
+  (require 'moz nil t)
+  (require 'json nil t))
+
+(eval-when-compile
+  (require 'cl)
+  (require 'comint)
+  (require 'ido)
+  ;; Swap the speed and safety values for debugging
+  (proclaim '(optimize (speed 3) (safety 0))))
+
+(defvar inferior-moz-buffer)
+(defvar moz-repl-name)
+(defvar ido-cur-list)
+(declare-function ido-mode "ido")
+(declare-function inferior-moz-process "mozrepl")
+
+;;; Constants
+
+(defconst js--name-start-re "[a-zA-Z_$]"
+  "Matches the first character of a Javascript identifier. No grouping")
+
+(defconst js--stmt-delim-chars "^;{}?:")
+
+(defconst js--name-re (concat js--name-start-re
+                              "\\(?:\\s_\\|\\sw\\)*")
+  "Matches a Javascript identifier. No grouping.")
+
+(defconst js--objfield-re (concat js--name-re ":")
+  "Matches a Javascript object field start")
+
+(defconst js--dotted-name-re
+  (concat js--name-re "\\(?:\\." js--name-re "\\)*")
+  "Matches a dot-separated sequence of Javascript names")
+
+(defconst js--cpp-name-re js--name-re
+  "Matches a C preprocessor name")
+
+(defconst js--opt-cpp-start "^\\s-*#\\s-*\\([[:alnum:]]+\\)"
+  "Regexp matching the prefix of a cpp directive including the directive
+name, or nil in languages without preprocessor support.  The first
+submatch surrounds the directive name.")
+
+(defconst js--plain-method-re
+  (concat "^\\s-*?\\(" js--dotted-name-re "\\)\\.prototype"
+          "\\.\\(" js--name-re "\\)\\s-*?=\\s-*?\\(function\\)\\_>")
+  "Regexp matching an old-fashioned explicit prototype \"method\"
+  declaration. Group 1 is a (possibly-dotted) class name, group 2
+  is a method name, and group 3 is the 'function' keyword." )
+
+(defconst js--plain-class-re
+  (concat "^\\s-*\\(" js--dotted-name-re "\\)\\.prototype"
+          "\\s-*=\\s-*{")
+  "Regexp matching an old-fashioned explicit prototype \"class\"
+  declaration, as in Class.prototype = { method1: ...} ")
+
+(defconst js--mp-class-decl-re
+  (concat "^\\s-*var\\s-+"
+          "\\(" js--name-re "\\)"
+          "\\s-*=\\s-*"
+          "\\(" js--dotted-name-re
+          "\\)\\.extend\\(?:Final\\)?\\s-*(\\s-*{?\\s-*$")
+  "var NewClass = BaseClass.extend(")
+
+(defconst js--prototype-obsolete-class-decl-re
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" js--dotted-name-re "\\)"
+          "\\s-*=\\s-*Class\\.create()")
+  "var NewClass = Class.create()")
+
+(defconst js--prototype-objextend-class-decl-re-1
+  (concat "^\\s-*Object\\.extend\\s-*("
+          "\\(" js--dotted-name-re "\\)"
+          "\\s-*,\\s-*{"))
+
+(defconst js--prototype-objextend-class-decl-re-2
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" js--dotted-name-re "\\)"
+          "\\s-*=\\s-*Object\\.extend\\s-*\("))
+
+(defconst js--prototype-class-decl-re
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" js--name-re "\\)"
+          "\\s-*=\\s-*Class\\.create\\s-*(\\s-*"
+          "\\(?:\\(" js--dotted-name-re "\\)\\s-*,\\s-*\\)?{?")
+  "var NewClass = Class.create({")
+
+;; Parent class name(s) (yes, multiple inheritance in Javascript) are
+;; matched with dedicated font-lock matchers
+(defconst js--dojo-class-decl-re
+  (concat "^\\s-*dojo\\.declare\\s-*(\"\\(" js--dotted-name-re "\\)"))
+
+(defconst js--extjs-class-decl-re-1
+  (concat "^\\s-*Ext\\.extend\\s-*("
+          "\\s-*\\(" js--dotted-name-re "\\)"
+          "\\s-*,\\s-*\\(" js--dotted-name-re "\\)")
+  "ExtJS class declaration (style 1)")
+
+(defconst js--extjs-class-decl-re-2
+  (concat "^\\s-*\\(?:var\\s-+\\)?"
+          "\\(" js--name-re "\\)"
+          "\\s-*=\\s-*Ext\\.extend\\s-*(\\s-*"
+          "\\(" js--dotted-name-re "\\)")
+  "ExtJS class declaration (style 2)")
+
+(defconst js--mochikit-class-re
+  (concat "^\\s-*MochiKit\\.Base\\.update\\s-*(\\s-*"
+          "\\(" js--dotted-name-re "\\)")
+  "MochiKit class declaration?")
+
+(defconst js--dummy-class-style
+  '(:name "[Automatically Generated Class]"))
+
+(defconst js--class-styles
+  `((:name            "Plain"
+     :class-decl      ,js--plain-class-re
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       javascript)
+
+    (:name            "MochiKit"
+     :class-decl      ,js--mochikit-class-re
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       mochikit)
+
+    (:name            "Prototype (Obsolete)"
+     :class-decl      ,js--prototype-obsolete-class-decl-re
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Prototype (Modern)"
+     :class-decl      ,js--prototype-class-decl-re
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Prototype (Object.extend)"
+     :class-decl      ,js--prototype-objextend-class-decl-re-1
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Prototype (Object.extend) 2"
+     :class-decl      ,js--prototype-objextend-class-decl-re-2
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       prototype)
+
+    (:name            "Dojo"
+     :class-decl      ,js--dojo-class-decl-re
+     :contexts        (toplevel)
+     :framework       dojo)
+
+    (:name            "ExtJS (style 1)"
+     :class-decl      ,js--extjs-class-decl-re-1
+     :prototype       t
+     :contexts        (toplevel)
+     :framework       extjs)
+
+    (:name            "ExtJS (style 2)"
+     :class-decl      ,js--extjs-class-decl-re-2
+     :contexts        (toplevel)
+     :framework       extjs)
+
+    (:name            "Merrill Press"
+     :class-decl      ,js--mp-class-decl-re
+     :contexts        (toplevel)
+     :framework       merrillpress))
+
+  "A list of class definition styles.
+
+A class definition style is a plist with the following keys:
+
+:name is a human-readable name of the class type
+
+:class-decl is a regular expression giving the start of the
+class. Its first group must match the name of its class. If there
+is a parent class, the second group should match, and it should
+be the name of the class.
+
+If :prototype is present and non-nil, the parser will merge
+declarations for this constructs with others at the same lexical
+level that have the same name. Otherwise, multiple definitions
+will create multiple top-level entries. Don't use :prototype
+unnecessarily: it has an associated cost in performance.
+
+If :strip-prototype is present and non-nil, then if the class
+name as matched contains
+")
+
+(defconst js--available-frameworks
+  (loop with available-frameworks
+        for style in js--class-styles
+        for framework = (plist-get style :framework)
+        unless (memq framework available-frameworks)
+        collect framework into available-frameworks
+        finally return available-frameworks)
+
+  "List of available frameworks symbols")
+
+(defconst js--function-heading-1-re
+  (concat
+   "^\\s-*function\\s-+\\(" js--name-re "\\)")
+  "Regular expression matching the start of a function header. Match group 1
+is the name of the function.")
+
+(defconst js--function-heading-2-re
+  (concat
+   "^\\s-*\\(" js--name-re "\\)\\s-*:\\s-*function\\_>")
+  "Regular expression matching the start of a function entry in
+  an associative array. Match group 1 is the name of the function.")
+
+(defconst js--function-heading-3-re
+  (concat
+   "^\\s-*\\(?:var\\s-+\\)?\\(" js--dotted-name-re "\\)"
+   "\\s-*=\\s-*function\\_>")
+  "Matches a line in the form var MUMBLE = function. Match group
+  1 is MUMBLE.")
+
+(defconst js--macro-decl-re
+  (concat "^\\s-*#\\s-*define\\s-+\\(" js--cpp-name-re "\\)\\s-*(")
+  "Regular expression matching a CPP macro definition up to the opening
+parenthesis. Match group 1 is the name of the function.")
+
+(defun js--regexp-opt-symbol (list)
+  "Like regexp-opt, but surround the optimized regular expression
+with `\\\\_<' and `\\\\_>'."
+  (concat "\\_<" (regexp-opt list t) "\\_>"))
+
+(defconst js--keyword-re
+  (js--regexp-opt-symbol
+   '("abstract" "break" "case" "catch" "class" "const"
+     "continue" "debugger" "default" "delete" "do" "else"
+     "enum" "export" "extends" "final" "finally" "for"
+     "function" "goto" "if" "implements" "import" "in"
+     "instanceof" "interface" "native" "new" "package"
+     "private" "protected" "public" "return" "static"
+     "super" "switch" "synchronized" "throw"
+     "throws" "transient" "try" "typeof" "var" "void" "let"
+     "yield" "volatile" "while" "with"))
+  "Regular expression matching any JavaScript keyword.")
+
+(defconst js--basic-type-re
+  (js--regexp-opt-symbol
+   '("boolean" "byte" "char" "double" "float" "int" "long"
+     "short" "void"))
+  "Regular expression matching any predefined type in JavaScript.")
+
+(defconst js--constant-re
+  (js--regexp-opt-symbol '("false" "null" "undefined"
+                                 "Infinity" "NaN"
+                                 "true" "arguments" "this"))
+  "Regular expression matching any future reserved words in JavaScript.")
+
+
+(defconst js--font-lock-keywords-1
+  (list
+   "\\_<import\\_>"
+   (list js--function-heading-1-re 1 font-lock-function-name-face)
+   (list js--function-heading-2-re 1 font-lock-function-name-face))
+  "Level one font lock.")
+
+(defconst js--font-lock-keywords-2
+  (append js--font-lock-keywords-1
+          (list (list js--keyword-re 1 font-lock-keyword-face)
+                (list "\\_<for\\_>"
+                      "\\s-+\\(each\\)\\_>" nil nil
+                      (list 1 'font-lock-keyword-face))
+                (cons js--basic-type-re font-lock-type-face)
+                (cons js--constant-re font-lock-constant-face)))
+  "Level two font lock.")
+
+;; js--pitem is the basic building block of the lexical
+;; database. When one refers to a real part of the buffer, the region
+;; of text to which it refers is split into a conceptual header and
+;; body. Consider the (very short) block described by a hypothetical
+;; js--pitem:
+;;
+;;   function foo(a,b,c) { return 42; }
+;;   ^                    ^            ^
+;;   |                    |            |
+;;   +- h-begin           +- h-end     +- b-end
+;;
+;; (Remember that these are buffer positions, and therefore point
+;; between characters, not at them. An arrow drawn to a character
+;; indicates the corresponding position is between that character and
+;; the one immediately preceding it.)
+;;
+;; The header is the region of text [h-begin, h-end], and is
+;; the text needed to unambiguously recognize the start of the
+;; construct. If the entire header is not present, the construct is
+;; not recognized at all. No other pitems may be nested inside the
+;; header.
+;;
+;; The body is the region [h-end, b-end]. It may contain nested
+;; js--pitem instances. The body of a pitem may be empty: in
+;; that case, b-end is equal to header-end.
+;;
+;; The three points obey the following relationship:
+;;
+;;   h-begin < h-end <= b-end
+;;
+;; We put a text property in the buffer on the character *before*
+;; h-end, and if we see it, on the character *before* b-end.
+;;
+;; The text property for h-end, js--pstate, is actually a list
+;; of all js--pitem instances open after the marked character.
+;;
+;; The text property for b-end, js--pend, is simply the
+;; js--pitem that ends after the marked character. (Because
+;; pitems always end when the paren-depth drops below a critical
+;; value, and because we can only drop one level per character, only
+;; one pitem may end at a given character.)
+;;
+;; In the structure below, we only store h-begin and (sometimes)
+;; b-end. We can trivially and quickly find h-end by going to h-begin
+;; and searching for an js--pstate text property. Since no other
+;; js--pitem instances can be nested inside the header of a
+;; pitem, the location after the character with this text property
+;; must be h-end.
+;;
+;; js--pitem instances are never modified (with the exception
+;; of the b-end field). Instead, modified copies are added at subseqnce parse points.
+;; (The exception for b-end and its caveats is described below.)
+;;
+
+(defstruct (js--pitem (:type list))
+  ;; IMPORTANT: Do not alter the position of fields within the list.
+  ;; Various bits of code depend on their positions, particularly
+  ;; anything that manipulates the list of children.
+
+  ;; List of children inside this pitem's body
+  (children nil :read-only t)
+
+  ;; When we reach this paren depth after h-end, the pitem ends
+  (paren-depth nil :read-only t)
+
+  ;; Symbol or class-style plist if this is a class
+  (type nil :read-only t)
+
+  ;; See above
+  (h-begin nil :read-only t)
+
+  ;; List of strings giving the parts of the name of this pitem (e.g.,
+  ;; '("MyClass" "myMethod"), or t if this pitem is anonymous
+  (name nil :read-only t)
+
+  ;; THIS FIELD IS MUTATED, and its value is shared by all copies of
+  ;; this pitem: when we copy-and-modify pitem instances, we share
+  ;; their tail structures, so all the copies actually have the same
+  ;; terminating cons cell. We modify that shared cons cell directly.
+  ;;
+  ;; The field value is either a number (buffer location) or nil if
+  ;; unknown.
+  ;;
+  ;; If the field's value is greater than `js--cache-end', the
+  ;; value is stale and must be treated as if it were nil. Conversely,
+  ;; if this field is nil, it is guaranteed that this pitem is open up
+  ;; to at least `js--cache-end'. (This property is handy when
+  ;; computing whether we're inside a given pitem.)
+  ;;
+  (b-end nil))
+
+(defconst js--initial-pitem
+  (make-js--pitem
+   :paren-depth most-negative-fixnum
+   :type 'toplevel)
+
+  "The pitem we start parsing with")
+
+;;; User Customization
+
+(defgroup js nil
+  "Customization variables for `js-mode'."
+  :tag "JavaScript"
+  :group 'languages)
+
+(defcustom js-indent-level 4
+  "Number of spaces for each indentation step."
+  :type 'integer
+  :group 'js)
+
+(defcustom js-expr-indent-offset 0
+  "Number of additional spaces used for indentation of continued
+expressions. The value must be no less than minus
+`js-indent-level'."
+  :type 'integer
+  :group 'js)
+
+(defcustom js-auto-indent-flag t
+  "Automatic indentation with punctuation characters. If non-nil, the
+current line is indented when certain punctuations are inserted."
+  :type 'boolean
+  :group 'js)
+
+(defcustom js-flat-functions nil
+  "Treat nested functions as if they were top-level functions for
+function movement, marking, and so on."
+  :type 'boolean
+  :group 'js)
+
+(defcustom js-comment-lineup-func #'c-lineup-C-comments
+  "cc-mode-style lineup function for C comments"
+  :type 'function
+  :group 'js)
+
+(defcustom js-enabled-frameworks js--available-frameworks
+  "Select which frameworks js-mode will recognize.
+
+Turn off some frameworks you seldom use to improve performance.
+The set of recognized frameworks can also be overriden on a
+per-buffer basis."
+  :type (cons 'set (mapcar (lambda (x)
+                             (list 'const x))
+                           js--available-frameworks))
+  :group 'js)
+
+(defcustom js-js-switch-tabs
+  (and (memq system-type '(darwin)) t)
+  "Non-nil if Emacs should display tabs while selecting them.
+  Useful only if the windowing system has a good mechanism for
+  preventing Firefox from stealing the keyboard focus."
+  :type 'boolean
+  :group 'js)
+
+(defcustom js-js-tmpdir
+  "~/.emacs.d/js/js"
+  "Temporary directory used for communicating with Mozilla. It
+  must be readable and writable by both Mozilla and Emacs."
+  :type 'directory
+  :group 'js)
+
+(defcustom js-js-timeout 5
+  "Wait this many seconds for a reply from Mozilla when executing
+commands. Increase this value if you are getting timeout
+messages."
+  :type 'integer
+  :group 'js)
+
+;;; KeyMap
+
+(defvar js-mode-map
+  (let ((keymap (make-sparse-keymap)))
+    (mapc (lambda (key)
+            (define-key keymap key #'js-insert-and-indent))
+          '("+" "-" "*" "{" "}" "(" ")" ":" ";" ","))
+    (define-key keymap [(control ?c) (meta ?:)] #'js-eval)
+    (define-key keymap [(control ?c) (control ?j)] #'js-set-js-context)
+    (define-key keymap [(control meta ?x)] #'js-eval-defun)
+    (define-key keymap [(meta ?.)] #'js-find-symbol)
+
+    (easy-menu-define nil keymap "Javascript Menu"
+      '("Javascript"
+        ["Select new Mozilla context…" js-set-js-context
+         (fboundp #'inferior-moz-process)]
+        ["Evaluate expression in Mozilla context…" js-eval
+         (fboundp #'inferior-moz-process)]
+        ["Send current function to Mozilla…" js-eval-defun
+         (fboundp #'inferior-moz-process)]
+        )
+      )
+
+    keymap)
+  "Keymap for js-mode")
+
+(defun js-insert-and-indent (key)
+  "Runs the command bound to KEY in the global keymap, and if
+we're not in a string or comment, indents the current line."
+  (interactive (list (this-command-keys)))
+  (call-interactively (lookup-key (current-global-map) key))
+  (let ((syntax (save-restriction (widen) (syntax-ppss))))
+    (when (or (and (not (nth 8 syntax))
+                   js-auto-indent-flag)
+              (and (nth 4 syntax)
+                   (eq (current-column)
+                       (1+ (current-indentation)))))
+
+         (indent-according-to-mode))))
+
+;;; Syntax table and parsing
+
+(defvar js-mode-syntax-table
+  (let ((table (make-syntax-table)))
+    (c-populate-syntax-table table)
+    (modify-syntax-entry ?$ "_" table)
+    table)
+  "Syntax table used in JS mode.")
+
+(defvar js--quick-match-re nil
+  "Autogenerated regular expression to match buffer constructs")
+
+(defvar js--quick-match-re-func nil
+  "Autogenerated regular expression to match buffer constructs
+and functions")
+
+(make-variable-buffer-local 'js--quick-match-re)
+(make-variable-buffer-local 'js--quick-match-re-func)
+
+(defvar js--cache-end 1
+  "Last place in the buffer the function cache is valid")
+(make-variable-buffer-local 'js--cache-end)
+
+(defvar js--last-parse-pos nil
+  "Last place we parsed up to in js--ensure-cache")
+(make-variable-buffer-local 'js--last-parse-pos)
+
+(defvar js--state-at-last-parse-pos nil
+  "pstate at js--last-parse-pos")
+(make-variable-buffer-local 'js--state-at-last-parse-pos)
+
+(defun js--flatten-list (list)
+  (loop for item in list
+        nconc (cond ((consp item)
+                     (js--flatten-list item))
+
+                    (item (list item)))))
+
+(defun js--maybe-join (prefix separator suffix &rest list)
+  "If LIST contains any element that is not nil, return its
+non-nil elements, separated by SEPARATOR, prefixed by PREFIX, and
+ended with SUFFIX as with `concat'. Otherwise, if LIST is empty,
+return nil. If any element in LIST is itself a list, flatten that
+element."
+  (setq list (js--flatten-list list))
+  (when list
+    (concat prefix (mapconcat #'identity list separator) suffix)))
+
+(defun js--update-quick-match-re ()
+  "Update js--quick-match-re based on the current set of
+enabled frameworks"
+  (setq js--quick-match-re
+        (js--maybe-join
+         "^[ \t]*\\(?:" "\\|" "\\)"
+
+         ;; #define mumble
+         "#define[ \t]+[a-zA-Z_]"
+
+         (when (memq 'extjs js-enabled-frameworks)
+           "Ext\\.extend")
+
+         (when (memq 'prototype js-enabled-frameworks)
+           "Object\\.extend")
+
+          ;; var mumble = THING (
+         (js--maybe-join
+          "\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=[ \t]*\\(?:"
+          "\\|"
+          "\\)[ \t]*\("
+
+          (when (memq 'prototype js-enabled-frameworks)
+                    "Class\\.create")
+
+          (when (memq 'extjs js-enabled-frameworks)
+            "Ext\\.extend")
+
+          (when (memq 'merrillpress js-enabled-frameworks)
+            "[a-zA-Z_$0-9]+\\.extend\\(?:Final\\)?"))
+
+         (when (memq 'dojo js-enabled-frameworks)
+           "dojo\\.declare[ \t]*\(")
+
+         (when (memq 'mochikit js-enabled-frameworks)
+           "MochiKit\\.Base\\.update[ \t]*\(")
+
+         ;; mumble.prototypeTHING
+         (js--maybe-join
+          "[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"
+
+          (when (memq 'javascript js-enabled-frameworks)
+            '( ;; foo.prototype.bar = function(
+              "\\.[a-zA-Z_$0-9]+[ \t]*=[ \t]*function[ \t]*\("
+
+              ;; mumble.prototype = {
+              "[ \t]*=[ \t]*{")))))
+
+  (setq js--quick-match-re-func
+        (concat "function\\|" js--quick-match-re)))
+
+(defun js--forward-text-property (propname)
+  "Move over the next value of PROPNAME in the buffer. If found,
+return that value and leave point after the character having that
+value, otherwise return nil and leave point at EOB."
+  (let ((next-value (get-text-property (point) propname)))
+    (if next-value
+        (forward-char)
+
+      (goto-char (next-single-property-change
+                  (point) propname nil (point-max)))
+      (unless (eobp)
+        (setq next-value (get-text-property (point) propname))
+        (forward-char)))
+
+    next-value))
+
+(defun js--backward-text-property (propname)
+    "Move over the previous value of PROPNAME in the buffer. If found,
+return that value and leave point just before the character that
+has that value, otherwise return nil and leave point at BOB."
+    (unless (bobp)
+      (let ((prev-value (get-text-property (1- (point)) propname)))
+        (if prev-value
+            (backward-char)
+
+          (goto-char (previous-single-property-change
+                      (point) propname nil (point-min)))
+
+          (unless (bobp)
+            (backward-char)
+            (setq prev-value (get-text-property (point) propname))))
+
+        prev-value)))
+
+(defsubst js--forward-pstate ()
+  (js--forward-text-property 'js--pstate))
+
+(defsubst js--backward-pstate ()
+  (js--backward-text-property 'js--pstate))
+
+(defun js--pitem-goto-h-end (pitem)
+  (goto-char (js--pitem-h-begin pitem))
+  (js--forward-pstate))
+
+(defun js--re-search-forward-inner (regexp &optional bound count)
+  "Auxiliary function for `js--re-search-forward'."
+  (let ((parse)
+        str-terminator
+        (orig-macro-end (save-excursion
+                          (when (js--beginning-of-macro)
+                            (c-end-of-macro)
+                            (point)))))
+    (while (> count 0)
+      (re-search-forward regexp bound)
+      (setq parse (syntax-ppss))
+      (cond ((setq str-terminator (nth 3 parse))
+             (when (eq str-terminator t)
+               (setq str-terminator ?/))
+             (re-search-forward
+              (concat "\\([^\\]\\|^\\)" (string str-terminator))
+              (save-excursion (end-of-line) (point)) t))
+            ((nth 7 parse)
+             (forward-line))
+            ((or (nth 4 parse)
+                 (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
+             (re-search-forward "\\*/"))
+            ((and (not (and orig-macro-end
+                            (<= (point) orig-macro-end)))
+                  (js--beginning-of-macro))
+             (c-end-of-macro))
+            (t
+             (setq count (1- count))))))
+  (point))
+
+
+(defun js--re-search-forward (regexp &optional bound noerror count)
+  "Search forward but ignore strings, cpp macros, and comments.
+Invokes `re-search-forward' but treats the buffer as if strings,
+cpp macros, and comments have been removed.
+
+If invoked while inside a macro, treat the contents of the macro
+as normal text.
+
+"
+  (let ((saved-point (point))
+        (search-expr
+         (cond ((null count)
+                '(js--re-search-forward-inner regexp bound 1))
+               ((< count 0)
+                '(js--re-search-backward-inner regexp bound (- count)))
+               ((> count 0)
+                '(js--re-search-forward-inner regexp bound count)))))
+    (condition-case err
+        (eval search-expr)
+      (search-failed
+       (goto-char saved-point)
+       (unless noerror
+         (error (error-message-string err)))))))
+
+
+(defun js--re-search-backward-inner (regexp &optional bound count)
+  "Auxiliary function for `js--re-search-backward'."
+  (let ((parse)
+        str-terminator
+        (orig-macro-start
+         (save-excursion
+           (and (js--beginning-of-macro)
+                (point)))))
+    (while (> count 0)
+      (re-search-backward regexp bound)
+      (when (and (> (point) (point-min))
+                 (save-excursion (backward-char) (looking-at "/[/*]")))
+        (forward-char))
+      (setq parse (syntax-ppss))
+      (cond ((setq str-terminator (nth 3 parse))
+             (when (eq str-terminator t)
+               (setq str-terminator ?/))
+             (re-search-backward
+              (concat "\\([^\\]\\|^\\)" (string str-terminator))
+              (save-excursion (beginning-of-line) (point)) t))
+            ((nth 7 parse)
+             (goto-char (nth 8 parse)))
+            ((or (nth 4 parse)
+                 (and (eq (char-before) ?/) (eq (char-after) ?*)))
+             (re-search-backward "/\\*"))
+            ((and (not (and orig-macro-start
+                            (>= (point) orig-macro-start)))
+                  (js--beginning-of-macro)))
+            (t
+             (setq count (1- count))))))
+  (point))
+
+
+(defun js--re-search-backward (regexp &optional bound noerror count)
+  "Search backward but ignore strings, preprocessor macros, and
+comments. Invokes `re-search-backward' but treats the buffer as
+if strings, preprocessor macros, and comments have been removed.
+
+If inside a macro when called, treat the macro as normal text.
+"
+  (let ((saved-point (point))
+        (search-expr
+         (cond ((null count)
+                '(js--re-search-backward-inner regexp bound 1))
+               ((< count 0)
+                '(js--re-search-forward-inner regexp bound (- count)))
+               ((> count 0)
+                '(js--re-search-backward-inner regexp bound count)))))
+    (condition-case err
+        (eval search-expr)
+      (search-failed
+       (goto-char saved-point)
+       (unless noerror
+         (error (error-message-string err)))))))
+
+(defun js--forward-expression ()
+  "Move forward over a whole expression. Doesn't move over
+expressions continued across lines, but we don't actually care"
+  (loop
+   ;; non-continued case; simplistic, but good enough?
+   do (loop until (or (eolp)
+                      (progn
+                        (forward-comment most-positive-fixnum)
+                        (memq (char-after) '(?\, ?\; ?\] ?\) ?\}))))
+            do (forward-sexp))
+
+   while (and (eq (char-after) ?\n)
+              (save-excursion
+                (forward-char)
+                (js--continued-expression-p)))))
+
+(defun js--forward-function-decl ()
+  "Move forward over a function declaration with point at the
+'function' keyword. Return non-nil if this is a
+syntactically-correct non-expression function, nil otherwise.
+Specifically, return the name of the function, or t if the name
+could not be determined."
+  (assert (looking-at "\\_<function\\_>"))
+  (let ((name t))
+    (forward-word)
+    (forward-comment most-positive-fixnum)
+    (when (looking-at js--name-re)
+      (setq name (match-string-no-properties 0))
+      (goto-char (match-end 0)))
+    (forward-comment most-positive-fixnum)
+    (and (eq (char-after) ?\( )
+         (ignore-errors (forward-list) t)
+         (progn (forward-comment most-positive-fixnum)
+                (and (eq (char-after) ?{)
+                     name)))))
+
+(defun js--function-prologue-beginning (&optional pos)
+  "Return the start of the function prologue that contains POS,
+or nil if we're not in a function prologue. A function prologue
+is everything from start of the definition up to and including
+the opening brace. POS defaults to point."
+
+  (let (prologue-begin)
+    (save-excursion
+      (if pos
+          (goto-char pos)
+        (setq pos (point)))
+
+      (when (save-excursion
+              (forward-line 0)
+              (or (looking-at js--function-heading-2-re)
+                  (looking-at js--function-heading-3-re)))
+
+        (setq prologue-begin (match-beginning 1))
+        (when (<= prologue-begin pos)
+          (goto-char (match-end 0))))
+
+      (skip-syntax-backward "w_")
+      (and (or (looking-at "\\_<function\\_>")
+               (js--re-search-backward "\\_<function\\_>" nil t))
+
+           (save-match-data (goto-char (match-beginning 0))
+                            (js--forward-function-decl))
+
+           (<= pos (point))
+           (or prologue-begin (match-beginning 0))))))
+
+(defun js--beginning-of-defun-raw ()
+  "Internal helper for js--beginning-of-defun. Go to
+previous defun-beginning and return the parse state for it, or
+nil if we went all the way back to bob and don't find anything."
+  (js--ensure-cache)
+  (let (pstate)
+    (while (and (setq pstate (js--backward-pstate))
+                (not (eq 'function (js--pitem-type (car pstate))))))
+    (and (not (bobp)) pstate)))
+
+(defun js--pstate-is-toplevel-defun (pstate)
+  "If PSTATE represents a non-empty top-level defun, return the
+top-most pitem. Otherwise, return nil."
+  (loop for pitem in pstate
+        with func-depth = 0
+        with func-pitem
+        if (eq 'function (js--pitem-type pitem))
+        do (incf func-depth)
+        and do (setq func-pitem pitem)
+        finally return (if (eq func-depth 1) func-pitem)))
+
+(defun js--beginning-of-defun-nested ()
+  "Internal helper for js--beginning-of-defun. Returns the
+pitem of the function we went to the beginning of."
+
+  (or
+   ;; Look for the smallest function that encloses point...
+   (loop for pitem in (js--parse-state-at-point)
+         if (and (eq 'function (js--pitem-type pitem))
+                 (js--inside-pitem-p pitem))
+         do (goto-char (js--pitem-h-begin pitem))
+         and return pitem)
+
+   ;; ...and if that isn't found, look for the previous top-level
+   ;; defun
+   (loop for pstate = (js--backward-pstate)
+         while pstate
+         if (js--pstate-is-toplevel-defun pstate)
+         do (goto-char (js--pitem-h-begin it))
+         and return it)))
+
+(defun js--beginning-of-defun-flat ()
+  "Internal helper for js--beginning-of-defun"
+
+  (let ((pstate (js--beginning-of-defun-raw)))
+    (when pstate
+      (goto-char (js--pitem-h-begin (car pstate))))))
+
+(defun js--beginning-of-defun (&optional arg)
+  "Used as beginning-of-defun-function"
+
+  (setq arg (or arg 1))
+  (while (and (not (eobp)) (< arg 0))
+    (incf arg)
+    (when (and (not js-flat-functions)
+               (or (eq (js-syntactic-context) 'function)
+                   (js--function-prologue-beginning)))
+      (js--end-of-defun))
+
+    (if (js--re-search-forward
+         "\\_<function\\_>" nil t)
+        (goto-char (js--function-prologue-beginning))
+      (goto-char (point-max))))
+
+  (while (> arg 0)
+    (decf arg)
+    ;; If we're just past the end of a function, the user probably wants
+    ;; to go to the beginning of *that* function
+    (when (eq (char-before) ?})
+      (backward-char))
+
+    (let ((prologue-begin (js--function-prologue-beginning)))
+      (cond ((and prologue-begin (< prologue-begin (point)))
+             (goto-char prologue-begin))
+
+            (js-flat-functions
+             (js--beginning-of-defun-flat))
+            (t
+             (js--beginning-of-defun-nested))))))
+
+(defun js--flush-caches (&optional beg ignored)
+  "Flush syntax cache info after position BEG. BEG defaults to
+point-min, flushing the entire cache."
+  (interactive)
+  (setq beg (or beg (save-restriction (widen) (point-min))))
+  (setq js--cache-end (min js--cache-end beg)))
+
+(defmacro js--debug (&rest arguments)
+  ;; `(message ,@arguments)
+  )
+
+(defun js--ensure-cache--pop-if-ended (open-items paren-depth)
+  (let ((top-item (car open-items)))
+    (when (<= paren-depth (js--pitem-paren-depth top-item))
+      (assert (not (get-text-property (1- (point)) 'js-pend)))
+      (put-text-property (1- (point)) (point) 'js--pend top-item)
+      (setf (js--pitem-b-end top-item) (point))
+      (setq open-items
+            ;; open-items must contain at least two items for this to
+            ;; work, but because we push a dummy item to start with,
+            ;; that assumption holds.
+            (cons (js--pitem-add-child (second open-items) top-item)
+                  (cddr open-items)))))
+
+  open-items)
+
+(defmacro js--ensure-cache--update-parse ()
+  "Helper for use inside js--ensure-cache. Updates parsing
+information up to point. Refers to parse, prev-parse-point,
+goal-point, and open-items bound lexically in the body of
+`js--ensure-cache'."
+  `(progn
+     (setq goal-point (point))
+     (goto-char prev-parse-point)
+     (while (progn
+              (setq open-items (js--ensure-cache--pop-if-ended
+                                open-items (car parse)))
+              ;; Make sure parse-partial-sexp doesn't stop because we *entered*
+              ;; the given depth -- i.e., make sure we're deeper than the target
+              ;; depth.
+              (assert (> (nth 0 parse)
+                         (js--pitem-paren-depth (car open-items))))
+              (setq parse (parse-partial-sexp
+                           prev-parse-point goal-point
+                           (js--pitem-paren-depth (car open-items))
+                           nil parse))
+
+;;              (let ((overlay (make-overlay prev-parse-point (point))))
+;;                (overlay-put overlay 'face '(:background "red"))
+;;                (unwind-protect
+;;                     (progn
+;;                       (js--debug "parsed: %S" parse)
+;;                       (sit-for 1))
+;;                  (delete-overlay overlay)))
+
+              (setq prev-parse-point (point))
+              (< (point) goal-point)))
+
+     (setq open-items (js--ensure-cache--pop-if-ended
+                       open-items (car parse)))))
+
+(defun js--show-cache-at-point ()
+  (interactive)
+  (require 'pp)
+  (let ((prop (get-text-property (point) 'js--pstate)))
+    (with-output-to-temp-buffer "*Help*"
+      (pp prop))))
+
+(defun js--split-name (string)
+  "Splits a name into its dot-separated parts. Also removes any
+prototype parts from the split name (unless the name is just
+\"prototype\" to start with, that is)."
+  (let ((name (save-match-data
+                (split-string string "\\." t))))
+    (unless (and (= (length name) 1)
+                 (equal (car name) "prototype"))
+
+      (setq name (remove "prototype" name)))))
+
+(defvar js--guess-function-name-start nil
+  "Secondary out-variable for js--guess-function-name")
+
+(defun js--guess-function-name (position)
+  "Guess the name of the function at POSITION, which should be
+the just after the end of the word 'function'. Return the name of
+the function or nil if the name could not be guessed. Clobbers
+match data. If in guessing the function name we find the preamble
+begins earlier than expected, set `js--guess-function-name-start'
+to that position, otherwise set that variable to nil."
+
+  (setq js--guess-function-name-start nil)
+  (save-excursion
+    (goto-char position)
+    (forward-line 0)
+    (cond
+     ((looking-at js--function-heading-3-re)
+      (and (eq (match-end 0) position)
+           (setq js--guess-function-name-start (match-beginning 1))
+           (match-string-no-properties 1)))
+
+     ((looking-at js--function-heading-2-re)
+      (and (eq (match-end 0) position)
+           (setq js--guess-function-name-start (match-beginning 1))
+           (match-string-no-properties 1))))))
+
+(defun js--clear-stale-cache ()
+  ;; Clear any endings that occur after point
+  (let (end-prop)
+    (save-excursion
+      (while (setq end-prop (js--forward-text-property
+                             'js--pend))
+        (setf (js--pitem-b-end end-prop) nil))))
+
+  ;; Remove any cache properties after this point
+  (remove-text-properties (point) (point-max)
+                          '(js--pstate t js--pend t)))
+
+(defun js--ensure-cache (&optional limit)
+  "Ensures brace cache is valid up to the character before LIMIT.
+LIMIT defaults to point."
+  (setq limit (or limit (point)))
+  (when (< js--cache-end limit)
+
+    (c-save-buffer-state
+        (open-items
+         orig-match-start
+         orig-match-end
+         orig-depth
+         parse
+         prev-parse-point
+         name
+         case-fold-search
+         filtered-class-styles
+         new-item
+         goal-point
+         end-prop)
+
+      ;; Figure out which class styles we need to look for
+      (setq filtered-class-styles
+            (loop for style in js--class-styles
+                  if (memq (plist-get style :framework)
+                           js-enabled-frameworks)
+                  collect style))
+
+      (save-excursion
+        (save-restriction
+          (widen)
+
+          ;; Find last known good position
+          (goto-char js--cache-end)
+          (unless (bobp)
+            (setq open-items (get-text-property
+                              (1- (point)) 'js--pstate))
+
+            (unless open-items
+              (goto-char (previous-single-property-change
+                          (point) 'js--pstate nil (point-min)))
+
+              (unless (bobp)
+                (setq open-items (get-text-property (1- (point))
+                                                    'js--pstate))
+                (assert open-items))))
+
+          (unless open-items
+            ;; Make a placeholder for the top-level definition
+            (setq open-items (list js--initial-pitem)))
+
+          (setq parse (syntax-ppss))
+          (setq prev-parse-point (point))
+
+          (js--clear-stale-cache)
+
+          (narrow-to-region (point-min) limit)
+
+          (loop while (re-search-forward js--quick-match-re-func nil t)
+                for orig-match-start = (goto-char (match-beginning 0))
+                for orig-match-end = (match-end 0)
+                do (js--ensure-cache--update-parse)
+                for orig-depth = (nth 0 parse)
+
+                ;; Each of these conditions should return non-nil if
+                ;; we should add a new item and leave point at the end
+                ;; of the new item's header (h-end in the
+                ;; js--pitem diagram). This point is the one
+                ;; after the last character we need to unambiguously
+                ;; detect this construct. If one of these evaluates to
+                ;; nil, the location of the point is ignored.
+                if (cond
+                    ;; In comment or string
+                    ((nth 8 parse) nil)
+
+                    ;; Regular function declaration
+                    ((and (looking-at "\\_<function\\_>")
+                          (setq name (js--forward-function-decl)))
+
+                     (when (eq name t)
+                       (setq name (js--guess-function-name orig-match-end))
+                       (if name
+                           (when js--guess-function-name-start
+                             (setq orig-match-start
+                                   js--guess-function-name-start))
+
+                         (setq name t)))
+
+                     (assert (eq (char-after) ?{))
+                     (forward-char)
+                     (make-js--pitem
+                      :paren-depth orig-depth
+                      :h-begin orig-match-start
+                      :type 'function
+                      :name (if (eq name t)
+                                name
+                              (js--split-name name))))
+
+                    ;; Macro
+                    ((looking-at js--macro-decl-re)
+
+                     ;; Macros often contain unbalanced parentheses.
+                     ;; Make sure that h-end is at the textual end of
+                     ;; the macro no matter what the parenthesis say.
+                     (c-end-of-macro)
+                     (js--ensure-cache--update-parse)
+
+                     (make-js--pitem
+                      :paren-depth (nth 0 parse)
+                      :h-begin orig-match-start
+                      :type 'macro
+                      :name (list (match-string-no-properties 1))))
+
+                    ;; "Prototype function" declaration
+                    ((looking-at js--plain-method-re)
+                     (goto-char (match-beginning 3))
+                     (when (save-match-data
+                             (js--forward-function-decl))
+                       (forward-char)
+                       (make-js--pitem
+                        :paren-depth orig-depth
+                        :h-begin orig-match-start
+                        :type 'function
+                        :name (nconc (js--split-name
+                                      (match-string-no-properties 1))
+                                     (list (match-string-no-properties 2))))))
+
+                    ;; Class definition
+                    ((loop with syntactic-context =
+                           (js--syntactic-context-from-pstate open-items)
+                           for class-style in filtered-class-styles
+                           if (and (memq syntactic-context
+                                         (plist-get class-style :contexts))
+                                   (looking-at (plist-get class-style
+                                                          :class-decl)))
+                           do (goto-char (match-end 0))
+                           and return
+                           (make-js--pitem
+                            :paren-depth orig-depth
+                            :h-begin orig-match-start
+                            :type class-style
+                            :name (js--split-name
+                                   (match-string-no-properties 1))))))
+
+                do (js--ensure-cache--update-parse)
+                and do (push it open-items)
+                and do (put-text-property
+                        (1- (point)) (point) 'js--pstate open-items)
+                else do (goto-char orig-match-end))
+
+          (goto-char limit)
+          (js--ensure-cache--update-parse)
+          (setq js--cache-end limit)
+          (setq js--last-parse-pos limit)
+          (setq js--state-at-last-parse-pos open-items)
+          )))))
+
+(defun js--end-of-defun-flat ()
+  "Internal helper for js--end-of-defun"
+  (loop while (js--re-search-forward "}" nil t)
+        do (js--ensure-cache)
+        if (get-text-property (1- (point)) 'js--pend)
+        if (eq 'function (js--pitem-type it))
+        return t
+        finally do (goto-char (point-max))))
+
+(defun js--end-of-defun-nested ()
+  "Internal helper for js--end-of-defun"
+  (message "test")
+  (let* (pitem
+         (this-end (save-excursion
+                     (and (setq pitem (js--beginning-of-defun-nested))
+                          (js--pitem-goto-h-end pitem)
+                          (progn (backward-char)
+                                 (forward-list)
+                                 (point)))))
+         found)
+
+    (if (and this-end (< (point) this-end))
+        ;; We're already inside a function; just go to its end.
+        (goto-char this-end)
+
+      ;; Otherwise, go to the end of the next function...
+      (while (and (js--re-search-forward "\\_<function\\_>" nil t)
+                  (not (setq found (progn
+                                     (goto-char (match-beginning 0))
+                                     (js--forward-function-decl))))))
+
+      (if found (forward-list)
+        ;; ... or eob.
+        (goto-char (point-max))))))
+
+(defun js--end-of-defun (&optional arg)
+  "Used as end-of-defun-function"
+  (setq arg (or arg 1))
+  (while (and (not (bobp)) (< arg 0))
+    (let (orig-pos (point))
+      (incf arg)
+      (js--beginning-of-defun)
+      (js--beginning-of-defun)
+      (unless (bobp)
+        (js--end-of-defun))))
+
+  (while (> arg 0)
+    (decf arg)
+    ;; look for function backward. if we're inside it, go to that
+    ;; function's end. otherwise, search for the next function's end and
+    ;; go there
+    (if js-flat-functions
+        (js--end-of-defun-flat)
+
+      ;; if we're doing nested functions, see whether we're in the
+      ;; prologue. If we are, go to the end of the function; otherwise,
+      ;; call js--end-of-defun-nested to do the real work
+      (let ((prologue-begin (js--function-prologue-beginning)))
+        (cond ((and prologue-begin (<= prologue-begin (point)))
+               (goto-char prologue-begin)
+               (re-search-forward "\\_<function")
+               (goto-char (match-beginning 0))
+               (js--forward-function-decl)
+               (forward-list))
+
+              (t (js--end-of-defun-nested)))))))
+
+(defun js--beginning-of-macro (&optional lim)
+  (let ((here (point)))
+    (save-restriction
+      (if lim (narrow-to-region lim (point-max)))
+      (beginning-of-line)
+      (while (eq (char-before (1- (point))) ?\\)
+        (forward-line -1))
+      (back-to-indentation)
+      (if (and (<= (point) here)
+               (looking-at js--opt-cpp-start))
+          t
+        (goto-char here)
+        nil))))
+
+(defun js--backward-syntactic-ws (&optional lim)
+  "Simple implementation of `c-backward-syntactic-ws'"
+  (save-restriction
+    (when lim (narrow-to-region lim (point-max)))
+
+    (let ((in-macro (save-excursion (js--beginning-of-macro)))
+          (pos (point)))
+
+      (while (progn (unless in-macro (js--beginning-of-macro))
+                    (forward-comment most-negative-fixnum)
+                    (/= (point)
+                        (prog1
+                            pos
+                          (setq pos (point)))))))))
+
+(defun js--forward-syntactic-ws (&optional lim)
+  "Simple implementation of `c-forward-syntactic-ws'"
+  (save-restriction
+    (when lim (narrow-to-region (point-min) lim))
+    (let ((pos (point)))
+      (while (progn
+               (forward-comment most-positive-fixnum)
+               (when (eq (char-after) ?#)
+                 (c-end-of-macro))
+               (/= (point)
+                   (prog1
+                       pos
+                     (setq pos (point)))))))))
+
+(defun js--up-nearby-list ()
+  "Like (up-list -1), but only considers lists that end nearby"
+  (save-restriction
+    ;; Look at a very small region so our compuation time doesn't
+    ;; explode in pathological cases.
+    (narrow-to-region (max (point-min) (- (point) 500)) (point))
+    (up-list -1)))
+
+(defun js--inside-param-list-p ()
+  "Return non-nil iff point is inside a function parameter list."
+  (ignore-errors
+    (save-excursion
+      (js--up-nearby-list)
+      (and (looking-at "(")
+           (progn (forward-symbol -1)
+                  (or (looking-at "function")
+                      (progn (forward-symbol -1)
+                             (looking-at "function"))))))))
+
+(defun js--inside-dojo-class-list-p ()
+  "Return non-nil iff point is inside a Dojo multiple-inheritance
+class block."
+  (ignore-errors
+    (save-excursion
+      (js--up-nearby-list)
+      (let ((list-begin (point)))
+        (forward-line 0)
+        (and (looking-at js--dojo-class-decl-re)
+             (goto-char (match-end 0))
+             (looking-at "\"\\s-*,\\s-*\\[")
+             (eq (match-end 0) (1+ list-begin)))))))
+
+(defun js--syntax-begin-function ()
+  (when (< js--cache-end (point))
+    (goto-char (max (point-min) js--cache-end)))
+
+  (let ((pitem))
+    (while (and (setq pitem (car (js--backward-pstate)))
+                (not (eq 0 (js--pitem-paren-depth pitem)))))
+
+    (when pitem
+      (goto-char (js--pitem-h-begin pitem )))))
+
+;;; Font Lock
+(defun js--make-framework-matcher (framework &rest regexps)
+  "Create a byte-compiled function that only matches the given
+regular expressions (that concatenation of REGEXPS) if FRAMEWORK
+is in js-enabled-frameworks"
+
+  (setq regexps (apply #'concat regexps))
+  (byte-compile
+   `(lambda (limit)
+      (when (memq (quote ,framework) js-enabled-frameworks)
+        (re-search-forward ,regexps limit t)))))
+
+(defvar js--tmp-location nil)
+(make-variable-buffer-local 'js--tmp-location)
+
+(defun js--forward-destructuring-spec (&optional func)
+  "Move forward over a destructuring spec. If FUNC is supplied,
+call it with no arguments before every variable name in the spec.
+Return true iff this was actually a spec. FUNC must preserve the
+match data."
+
+  (case (char-after)
+    (?\[
+     (forward-char)
+     (while
+         (progn
+           (forward-comment most-positive-fixnum)
+           (cond ((memq (char-after) '(?\[ ?\{))
+                  (js--forward-destructuring-spec func))
+
+                 ((eq (char-after) ?,)
+                  (forward-char)
+                  t)
+
+                 ((looking-at js--name-re)
+                  (and func (funcall func))
+                  (goto-char (match-end 0))
+                  t))))
+     (when (eq (char-after) ?\])
+       (forward-char)
+       t))
+
+    (?\{
+     (forward-char)
+     (forward-comment most-positive-fixnum)
+     (while
+         (when (looking-at js--objfield-re)
+           (goto-char (match-end 0))
+           (forward-comment most-positive-fixnum)
+           (and (cond ((memq (char-after) '(?\[ ?\{))
+                       (js--forward-destructuring-spec func))
+                      ((looking-at js--name-re)
+                       (and func (funcall func))
+                       (goto-char (match-end 0))
+                       t))
+                (progn (forward-comment most-positive-fixnum)
+                       (when (eq (char-after) ?\,)
+                         (forward-char)
+                         (forward-comment most-positive-fixnum)
+                         t)))))
+     (when (eq (char-after) ?\})
+       (forward-char)
+       t))))
+
+(defun js--variable-decl-matcher (limit)
+  "Font-lock matcher for variable names in a variable
+declaration. This is a cc-mode-style matcher that *always* fails,
+from the point of view of font-lock. It applies highlighting
+directly with `font-lock-apply-higlight'."
+
+  (condition-case nil
+      (save-restriction
+        (narrow-to-region (point-min) limit)
+
+        (let ((first t))
+          (forward-comment most-positive-fixnum)
+          (while
+              (and (or first
+                       (when (eq (char-after) ?,)
+                         (forward-char)
+                         (forward-comment most-positive-fixnum)
+                         t))
+                   (cond ((looking-at js--name-re)
+                          (font-lock-apply-highlight
+                           '(0 font-lock-variable-name-face))
+                          (goto-char (match-end 0)))
+
+                         ((save-excursion
+                            (js--forward-destructuring-spec))
+
+                          (js--forward-destructuring-spec
+                           (lambda ()
+                             (font-lock-apply-highlight
+                              '(0 font-lock-variable-name-face)))))))
+
+            (forward-comment most-positive-fixnum)
+            (when (eq (char-after) ?=)
+              (forward-char)
+              (js--forward-expression)
+              (forward-comment most-positive-fixnum))
+
+            (setq first nil))))
+
+    ;; Conditions to handle
+    (scan-error nil)
+    (end-of-buffer nil))
+
+  ;; Matcher always "fails"
+  nil)
+
+(defconst js--font-lock-keywords-3
+  `(
+    ;; This goes before keywords-2 so it gets used preferentially
+    ;; instead of the keywords in keywords-2. Don't use override
+    ;; because that will override syntactic fontification too, which
+    ;; will fontify commented-out directives as if they weren't
+    ;; commented out.
+    ,@cpp-font-lock-keywords ; from font-lock.el
+
+    ,@js--font-lock-keywords-2
+
+    ("\\.\\(prototype\\)\\_>"
+     (1 font-lock-constant-face))
+
+    ;; Highlights class being declared, in parts
+    (js--class-decl-matcher
+     ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
+     (goto-char (match-beginning 1))
+     nil
+     (1 font-lock-type-face))
+
+    ;; Highlights parent class, in parts, if available
+    (js--class-decl-matcher
+     ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
+     (if (match-beginning 2)
+         (progn
+           (setq js--tmp-location (match-end 2))
+           (goto-char js--tmp-location)
+           (insert "=")
+           (goto-char (match-beginning 2)))
+       (setq js--tmp-location nil)
+       (goto-char (point-at-eol)))
+     (when js--tmp-location
+       (save-excursion
+         (goto-char js--tmp-location)
+         (delete-char 1)))
+     (1 font-lock-type-face))
+
+    ;; Highlights parent class
+    (js--class-decl-matcher
+     (2 font-lock-type-face nil t))
+
+    ;; Dojo needs its own matcher to override the string highlighting
+    (,(js--make-framework-matcher
+       'dojo
+       "^\\s-*dojo\\.declare\\s-*(\""
+       "\\(" js--dotted-name-re "\\)"
+       "\\(?:\"\\s-*,\\s-*\\(" js--dotted-name-re "\\)\\)?")
+     (1 font-lock-type-face t)
+     (2 font-lock-type-face nil t))
+
+    ;; Match Dojo base classes. Of course Mojo has to be different
+    ;; from everything else under the sun...
+    (,(js--make-framework-matcher
+       'dojo
+       "^\\s-*dojo\\.declare\\s-*(\""
+       "\\(" js--dotted-name-re "\\)\"\\s-*,\\s-*\\[")
+     ,(concat "[[,]\\s-*\\(" js--dotted-name-re "\\)\\s-*"
+              "\\(?:\\].*$\\)?")
+     (backward-char)
+     (end-of-line)
+     (1 font-lock-type-face))
+
+    ;; continued Dojo base-class list
+    (,(js--make-framework-matcher
+       'dojo
+       "^\\s-*" js--dotted-name-re "\\s-*[],]")
+     ,(concat "\\(" js--dotted-name-re "\\)"
+              "\\s-*\\(?:\\].*$\\)?")
+     (if (save-excursion (backward-char)
+                         (js--inside-dojo-class-list-p))
+         (forward-symbol -1)
+       (end-of-line))
+     (end-of-line)
+     (1 font-lock-type-face))
+
+    ;; variable declarations
+    ,(list
+      (concat "\\_<\\(const\\|var\\|let\\)\\_>\\|" js--basic-type-re)
+      (list #'js--variable-decl-matcher nil nil nil))
+
+    ;; class instantiation
+    ,(list
+      (concat "\\_<new\\_>\\s-+\\(" js--dotted-name-re "\\)")
+      (list 1 'font-lock-type-face))
+
+    ;; instanceof
+    ,(list
+      (concat "\\_<instanceof\\_>\\s-+\\(" js--dotted-name-re "\\)")
+      (list 1 'font-lock-type-face))
+
+    ;; formal parameters
+    ,(list
+      (concat
+       "\\_<function\\_>\\(\\s-+" js--name-re "\\)?\\s-*(\\s-*"
+       js--name-start-re)
+      (list (concat "\\(" js--name-re "\\)\\(\\s-*).*\\)?")
+            '(backward-char)
+            '(end-of-line)
+            '(1 font-lock-variable-name-face)))
+
+    ;; continued formal parameter list
+    ,(list
+      (concat
+       "^\\s-*" js--name-re "\\s-*[,)]")
+      (list js--name-re
+            '(if (save-excursion (backward-char)
+                                 (js--inside-param-list-p))
+                 (forward-symbol -1)
+               (end-of-line))
+            '(end-of-line)
+            '(0 font-lock-variable-name-face))))
+
+  "Level three font lock.")
+
+(defun js--inside-pitem-p (pitem)
+  "Return whether point is inside the given pitem's header or body"
+  (js--ensure-cache)
+  (assert (js--pitem-h-begin pitem))
+  (assert (js--pitem-paren-depth pitem))
+
+  (and (> (point) (js--pitem-h-begin pitem))
+       (or (null (js--pitem-b-end pitem))
+           (> (js--pitem-b-end pitem) (point)))))
+
+(defun js--parse-state-at-point ()
+  "Get a list of js--pitem instances that apply to point,
+most specific first. In the worst case, the current toplevel
+instance will be returned."
+
+  (save-excursion
+    (save-restriction
+      (widen)
+      (js--ensure-cache)
+      (let* ((bound (if (eobp) (point) (1+ (point))))
+             (pstate (or (save-excursion
+                           (js--backward-pstate))
+                         (list js--initial-pitem))))
+
+        ;; Loop until we either hit a pitem at BOB or pitem ends after
+        ;; point (or at point if we're at eob)
+        (loop for pitem = (car pstate)
+              until (or (eq (js--pitem-type pitem)
+                            'toplevel)
+                        (js--inside-pitem-p pitem))
+              do (pop pstate))
+
+        pstate))))
+
+(defun js--syntactic-context-from-pstate (pstate)
+  "Return the syntactic context corresponding to PSTATE"
+  (let ((type (js--pitem-type (car pstate))))
+    (cond ((memq type '(function macro))
+           type)
+
+          ((consp type)
+           'class)
+
+          (t 'toplevel))))
+
+(defun js-syntactic-context ()
+  "Get the current syntactic context of point. When called
+interatively, also display a message with that context."
+  (interactive)
+  (let* ((syntactic-context (js--syntactic-context-from-pstate
+                             (js--parse-state-at-point))))
+
+    (when (interactive-p)
+      (message "Syntactic context: %s" syntactic-context))
+
+    syntactic-context))
+
+(defun js--class-decl-matcher (limit)
+  "Fontifies according to js--class-styles"
+  (loop initially (js--ensure-cache limit)
+        while (re-search-forward js--quick-match-re limit t)
+        for orig-end = (match-end 0)
+        do (goto-char (match-beginning 0))
+        if (loop for style in js--class-styles
+                 for decl-re = (plist-get style :class-decl)
+                 if (and (memq (plist-get style :framework)
+                               js-enabled-frameworks)
+                         (memq (js-syntactic-context)
+                               (plist-get style :contexts))
+                         decl-re
+                         (looking-at decl-re))
+                 do (goto-char (match-end 0))
+                 and return t)
+        return t
+        else do (goto-char orig-end)))
+
+(defconst js--font-lock-keywords
+  '(js--font-lock-keywords-3 js--font-lock-keywords-1
+                                   js--font-lock-keywords-2
+                                   js--font-lock-keywords-3)
+  "See `font-lock-keywords'.")
+
+;; XXX: Javascript can continue a regexp literal across lines so long
+;; as the newline is escaped with \. Account for that in the regexp
+;; below.
+(defconst js--regexp-literal
+  "[=(,:]\\(?:\\s-\\|\n\\)*\\(/\\)\\(?:\\\\/\\|[^/*]\\)\\(?:\\\\/\\|[^/]\\)*\\(/\\)"
+  "Match a regular expression literal. Match groups 1 and 2 are
+the characters forming the beginning and end of the literal")
+
+;; we want to match regular expressions only at the beginning of
+;; expressions
+(defconst js--font-lock-syntactic-keywords
+  `((,js--regexp-literal (1 "|") (2 "|")))
+  "Highlighting of regular expressions. See also the variable
+  `font-lock-keywords'.")
+
+;;; Indentation
+
+(defconst js--possibly-braceless-keyword-re
+  (js--regexp-opt-symbol
+   '("catch" "do" "else" "finally" "for" "if" "try" "while" "with"
+     "each"))
+  "Regular expression matching keywords that are optionally
+  followed by an opening brace.")
+
+(defconst js--indent-operator-re
+  (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|"
+          (js--regexp-opt-symbol '("in" "instanceof")))
+  "Regular expression matching operators that affect indentation
+  of continued expressions.")
+
+
+(defun js--looking-at-operator-p ()
+  "Return non-nil if text after point is an operator (that is not
+a comma)."
+  (save-match-data
+    (and (looking-at js--indent-operator-re)
+         (or (not (looking-at ":"))
+             (save-excursion
+               (and (js--re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
+                    (looking-at "?")))))))
+
+
+(defun js--continued-expression-p ()
+  "Returns non-nil if the current line continues an expression."
+  (save-excursion
+    (back-to-indentation)
+    (or (js--looking-at-operator-p)
+        (and (js--re-search-backward "\n" nil t)
+	     (progn
+	       (skip-chars-backward " \t")
+	       (or (bobp) (backward-char))
+	       (and (> (point) (point-min))
+                    (save-excursion (backward-char) (not (looking-at "[/*]/")))
+                    (js--looking-at-operator-p)
+		    (and (progn (backward-char)
+				(not (looking-at "++\\|--\\|/[/*]"))))))))))
+
+
+(defun js--end-of-do-while-loop-p ()
+  "Returns non-nil if word after point is `while' of a do-while
+statement, else returns nil. A braceless do-while statement
+spanning several lines requires that the start of the loop is
+indented to the same column as the current line."
+  (interactive)
+  (save-excursion
+    (save-match-data
+      (when (looking-at "\\s-*\\_<while\\_>")
+	(if (save-excursion
+	      (skip-chars-backward "[ \t\n]*}")
+	      (looking-at "[ \t\n]*}"))
+	    (save-excursion
+	      (backward-list) (forward-symbol -1) (looking-at "\\_<do\\_>"))
+	  (js--re-search-backward "\\_<do\\_>" (point-at-bol) t)
+	  (or (looking-at "\\_<do\\_>")
+	      (let ((saved-indent (current-indentation)))
+		(while (and (js--re-search-backward "^\\s-*\\_<" nil t)
+			    (/= (current-indentation) saved-indent)))
+		(and (looking-at "\\s-*\\_<do\\_>")
+		     (not (js--re-search-forward
+			   "\\_<while\\_>" (point-at-eol) t))
+		     (= (current-indentation) saved-indent)))))))))
+
+
+(defun js--ctrl-statement-indentation ()
+  "Returns the proper indentation of the current line if it
+starts the body of a control statement without braces, else
+returns nil."
+  (save-excursion
+    (back-to-indentation)
+    (when (save-excursion
+            (and (not (eq (point-at-bol) (point-min)))
+                 (not (looking-at "[{]"))
+                 (progn
+                   (js--re-search-backward "[[:graph:]]" nil t)
+                   (or (eobp) (forward-char))
+                   (when (= (char-before) ?\)) (backward-list))
+                   (skip-syntax-backward " ")
+                   (skip-syntax-backward "w_")
+                   (looking-at js--possibly-braceless-keyword-re))
+                 (not (js--end-of-do-while-loop-p))))
+      (save-excursion
+        (goto-char (match-beginning 0))
+        (+ (current-indentation) js-indent-level)))))
+
+(defun js--get-c-offset (symbol anchor)
+  (let ((c-offsets-alist
+         (list (cons 'c js-comment-lineup-func))))
+    (c-get-syntactic-indentation (list (cons symbol anchor)))))
+
+(defun js--proper-indentation (parse-status)
+  "Return the proper indentation for the current line."
+  (save-excursion
+    (back-to-indentation)
+    (cond ((nth 4 parse-status)
+           (js--get-c-offset 'c (nth 8 parse-status)))
+          ((nth 8 parse-status) 0) ; inside string
+          ((js--ctrl-statement-indentation))
+          ((eq (char-after) ?#) 0)
+          ((save-excursion (js--beginning-of-macro)) 4)
+          ((nth 1 parse-status)
+           (let ((same-indent-p (looking-at
+                                 "[]})]\\|\\_<case\\_>\\|\\_<default\\_>"))
+                 (continued-expr-p (js--continued-expression-p)))
+             (goto-char (nth 1 parse-status))
+             (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
+                 (progn
+                   (skip-syntax-backward " ")
+                   (when (= (char-before) ?\)) (backward-list))
+                   (back-to-indentation)
+                   (cond (same-indent-p
+                          (current-column))
+                         (continued-expr-p
+                          (+ (current-column) (* 2 js-indent-level)
+                             js-expr-indent-offset))
+                         (t
+                          (+ (current-column) js-indent-level))))
+               (unless same-indent-p
+                 (forward-char)
+                 (skip-chars-forward " \t"))
+               (current-column))))
+
+          ((js--continued-expression-p)
+           (+ js-indent-level js-expr-indent-offset))
+          (t 0))))
+
+(defun js-indent-line ()
+  "Indent the current line as JavaScript source text."
+  (interactive)
+  (save-restriction
+    (widen)
+    (let* ((parse-status
+            (save-excursion (syntax-ppss (point-at-bol))))
+           (offset (- (current-column) (current-indentation))))
+
+      (indent-line-to (js--proper-indentation parse-status))
+      (when (> offset 0) (forward-char offset)))))
+
+;;; Filling
+
+(defun js-c-fill-paragraph (&optional justify)
+  "Fill the paragraph with c-fill-paragraph"
+  (interactive "*P")
+
+  (flet ((c-forward-sws
+          (&optional limit)
+          (js--forward-syntactic-ws limit))
+
+         (c-backward-sws
+          (&optional limit)
+          (js--backward-syntactic-ws limit))
+
+         (c-beginning-of-macro
+          (&optional limit)
+          (js--beginning-of-macro limit)))
+
+    (let ((fill-paragraph-function 'c-fill-paragraph))
+      (c-fill-paragraph justify))))
+
+;;; Type database and Imenu
+
+;; We maintain a cache of semantic information, i.e., the classes and
+;; functions we've encountered so far. In order to avoid having to
+;; re-parse the buffer on every change, we cache the parse state at
+;; each interesting point in the buffer. Each parse state is a
+;; modified copy of the previous one, or in the case of the first
+;; parse state, the empty state.
+;;
+;; The parse state itself is just a stack of js--pitem
+;; instances. It starts off containing one element that is never
+;; closed, that is initially js--initial-pitem.
+;;
+
+
+(defun js--pitem-format (pitem)
+  (let ((name (js--pitem-name pitem))
+        (type (js--pitem-type pitem)))
+
+    (format "name:%S type:%S"
+            name
+            (if (atom type)
+                type
+              (plist-get type :name)))))
+
+(defun js--make-merged-item (item child name-parts)
+  "Internal helper for js--splice-into-items. Return a new
+item that is the result of merging CHILD into ITEM. NAME-PARTS is
+a list of parts of the name of CHILD that we haven't consumed
+yet."
+
+  (js--debug "js--make-merged-item: {%s} into {%s}"
+                   (js--pitem-format child)
+                   (js--pitem-format item))
+
+  ;; If the item we're merging into isn't a class, make it into one
+  (unless (consp (js--pitem-type item))
+    (js--debug "js--make-merged-item: changing dest into class")
+    (setq item (make-js--pitem
+                :children (list item)
+
+                ;; Use the child's class-style if it's available
+                :type (if (atom (js--pitem-type child))
+                          js--dummy-class-style
+                  (js--pitem-type child))
+
+                :name (js--pitem-strname item))))
+
+  ;; Now we can merge either a function or a class into a class
+  (cons (cond
+         ((cdr name-parts)
+          (js--debug "js--make-merged-item: recursing")
+          ;; if we have more name-parts to go before we get to the
+          ;; bottom of the class hierarchy, call the merger
+          ;; recursively
+          (js--splice-into-items (car item) child
+                                       (cdr name-parts)))
+
+         ((atom (js--pitem-type child))
+          (js--debug "js--make-merged-item: straight merge")
+          ;; Not merging a class, but something else, so just prepend
+          ;; it
+          (cons child (car item)))
+
+         (t
+          ;; Otherwise, merge the new child's items into those
+          ;; of the new class
+          (js--debug "js--make-merged-item: merging class contents")
+          (append (car child) (car item))))
+        (cdr item)))
+
+(defun js--pitem-strname (pitem)
+  "Last part of the name of PITEM as a string or symbol"
+  (let ((name (js--pitem-name pitem)))
+    (if (consp name)
+        (car (last name))
+      name)))
+
+(defun js--splice-into-items (items child name-parts)
+  "Non-destructively inserts CHILD into the item list ITEMS in
+the proper place as given by NAME-PARTS. If a class doesn't
+exist in the tree, create it. Return the new items list.
+NAME-PARTS is a list of strings given the broken-down class
+name of the item to insert."
+
+  (let ((top-name (car name-parts))
+        (item-ptr items)
+        new-items last-new-item new-cons item)
+
+    (js--debug "js--splice-into-items: name-parts: %S items:%S"
+             name-parts
+             (mapcar #'js--pitem-name items))
+
+    (assert (stringp top-name))
+    (assert (> (length top-name) 0))
+
+    ;; If top-name isn't found in items, then we build a copy of items
+    ;; and throw it away. But that's okay, since most of the time, we
+    ;; *will* find an instance.
+
+    (while (and item-ptr
+                (cond ((equal (js--pitem-strname (car item-ptr)) top-name)
+                       ;; Okay, we found an entry with the right name. Splice
+                       ;; the merged item into the list...
+                       (setq new-cons (cons (js--make-merged-item
+                                             (car item-ptr) child
+                                             name-parts)
+                                            (cdr item-ptr)))
+
+                       (if last-new-item
+                           (setcdr last-new-item new-cons)
+                         (setq new-items new-cons))
+
+                       ;; ...and terminate the loop
+                       nil)
+
+                      (t
+                       ;; Otherwise, copy the current cons and move onto the
+                       ;; text. This is tricky; we keep track of the tail of
+                       ;; the list that begins with new-items in
+                       ;; last-new-item.
+                       (setq new-cons (cons (car item-ptr) nil))
+                       (if last-new-item
+                           (setcdr last-new-item new-cons)
+                         (setq new-items new-cons))
+                       (setq last-new-item new-cons)
+
+                       ;; Go to the next cell in items
+                       (setq item-ptr (cdr item-ptr))))))
+
+    (if item-ptr
+        ;; Yay! We stopped because we found something, not because
+        ;; we ran out of items to search. Just return the new
+        ;; list.
+        (progn
+          (js--debug "search succeeded: %S" name-parts)
+          new-items)
+
+      ;; We didn't find anything. If the child is a class and we don't
+      ;; have any classes to drill down into, just push that class;
+      ;; otherwise, make a fake class and carry on.
+      (js--debug "search failed: %S" name-parts)
+      (cons (if (cdr name-parts)
+                ;; We have name-parts left to process. Make a fake
+                ;; class for this particular part...
+                (make-js--pitem
+                 ;; ...and recursively digest the rest of the name
+                 :children (js--splice-into-items
+                            nil child (cdr name-parts))
+                 :type js--dummy-class-style
+                 :name top-name)
+
+              ;; Otherwise, this is the only name we have, so stick
+              ;; the item on the front of the list
+              child)
+            items))))
+
+(defun js--pitem-add-child (pitem child)
+  "Copy js--pitem PITEM and while copying, push CHILD onto
+its list of children."
+
+  (assert (integerp (js--pitem-h-begin child)))
+  (assert (if (consp (js--pitem-name child))
+              (loop for part in (js--pitem-name child)
+                    always (stringp part))
+            t))
+
+  ;; This trick works because we know (based on our defstructs) that
+  ;; the child list is always the first element, and so the second
+  ;; element and beyond can be shared when we make our "copy".
+  (cons
+
+   (let ((name (js--pitem-name child))
+         (type (js--pitem-type child)))
+
+     (cond ((cdr-safe name) ; true if a list of at least two elements
+            ;; Use slow path because we need class lookup
+            (js--splice-into-items (car pitem) child name))
+
+           ((and (consp type)
+                 (plist-get type :prototype))
+
+            ;; Use slow path because we need class merging. We know
+            ;; name is a list here because down in
+            ;; `js--ensure-cache', we made sure to only add
+            ;; class entries with lists for :name
+            (assert (consp name))
+            (js--splice-into-items (car pitem) child name))
+
+           (t
+            ;; Fast path
+            (cons child (car pitem)))))
+
+   (cdr pitem)))
+
+(defun js--maybe-make-marker (location)
+  "Make LOCATION into a marker if imenu-use-markers"
+  (if imenu-use-markers
+      (set-marker (make-marker) location)
+    location))
+
+(defun js--pitems-to-imenu (pitems unknown-ctr)
+  "Convert list of pitems PITEMS to imenu format"
+
+  (let (imenu-items pitem pitem-type pitem-name subitems)
+
+    (while (setq pitem (pop pitems))
+      (setq pitem-type (js--pitem-type pitem))
+      (setq pitem-name (js--pitem-strname pitem))
+      (when (eq pitem-name t)
+        (setq pitem-name (format "[unknown %s]"
+                                 (incf (car unknown-ctr)))))
+
+      (cond
+       ((memq pitem-type '(function macro))
+        (assert (integerp (js--pitem-h-begin pitem)))
+        (push (cons pitem-name
+                    (js--maybe-make-marker
+                     (js--pitem-h-begin pitem)))
+              imenu-items))
+
+       ((consp pitem-type) ; class definition
+        (setq subitems (js--pitems-to-imenu
+                        (js--pitem-children pitem)
+                        unknown-ctr))
+        (cond (subitems
+               (push (cons pitem-name subitems)
+                     imenu-items))
+
+              ((js--pitem-h-begin pitem)
+               (assert (integerp (js--pitem-h-begin pitem)))
+               (setq subitems (list
+                               (cons "[empty]"
+                                     (js--maybe-make-marker
+                                      (js--pitem-h-begin pitem)))))
+               (push (cons pitem-name subitems)
+                     imenu-items))))
+
+       (t (error "Unknown item type: %S" pitem-type))))
+
+    imenu-items))
+
+(defun js--imenu-create-index ()
+  "Creates an imenu index for the current buffer"
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char (point-max))
+      (js--ensure-cache)
+      (assert (or (= (point-min) (point-max))
+                  (eq js--last-parse-pos (point))))
+      (when js--last-parse-pos
+        (let ((state js--state-at-last-parse-pos)
+              (unknown-ctr (cons -1 nil)))
+
+          ;; Make sure everything is closed
+          (while (cdr state)
+            (setq state
+                  (cons (js--pitem-add-child (second state) (car state))
+                        (cddr state))))
+
+          (assert (= (length state) 1))
+
+          ;; Convert the new-finalized state into what imenu expects
+          (js--pitems-to-imenu
+           (car (js--pitem-children state))
+           unknown-ctr))))))
+
+(defun js--which-func-joiner (parts)
+  (mapconcat #'identity parts "."))
+
+(defun js--imenu-to-flat (items prefix symbols)
+  (loop for item in items
+        if (imenu--subalist-p item)
+        do (js--imenu-to-flat
+            (cdr item) (concat prefix (car item) ".")
+            symbols)
+        else
+        do (let* ((name (concat prefix (car item)))
+                  (name2 name)
+                  (ctr 0))
+
+             (while (gethash name2 symbols)
+               (setq name2 (format "%s<%d>" name (incf ctr))))
+
+             (puthash name2 (cdr item) symbols))))
+
+(defun js--get-all-known-symbols ()
+  "Get a hash table of all relevant Javascript symbols across all
+js-mode buffers. Each key is the name of a symbol (possibly
+disambiguated with <N>, where N > 1), and each value is a marker
+giving the location of that symbol."
+  (loop with symbols = (make-hash-table :test 'equal)
+        with imenu-use-markers = t
+        for buffer being the buffers
+        for imenu-index = (with-current-buffer buffer
+                            (when (eq major-mode 'js-mode)
+                              (js--imenu-create-index)))
+        do (js--imenu-to-flat imenu-index "" symbols)
+        finally return symbols))
+
+(defvar js--symbol-history nil
+  "History of entered Javascript symbols")
+
+(defun js--read-symbol (symbols-table prompt &optional initial-input)
+  "Read a symbol from SYMBOLS-TABLE, which is a hash table like
+the one from `js--get-all-known-symbols'. Return value is a
+cons of (SYMBOL-NAME . LOCATION), where SYMBOL-NAME is a string
+and LOCATION is a marker.
+
+Prompt is PROMPT.
+
+If INITIAL-INPUT is not nil, use it as the initial input"
+
+  (unless ido-mode
+    (ido-mode t)
+    (ido-mode nil))
+
+  (let ((choice (ido-completing-read
+                 prompt
+                 (loop for key being the hash-keys of symbols-table
+                       collect key)
+                 nil t initial-input 'js--symbol-history)))
+    (cons choice (gethash choice symbols-table))))
+
+(defun js--guess-symbol-at-point ()
+  (let ((bounds (bounds-of-thing-at-point 'symbol)))
+    (when bounds
+      (save-excursion
+        (goto-char (car bounds))
+        (when (eq (char-before) ?.)
+          (backward-char)
+          (setf (car bounds) (point))))
+      (buffer-substring (car bounds) (cdr bounds)))))
+
+(defun js-find-symbol (&optional arg)
+  "Jump to a Javascript symbol we read in from the user. With
+prefix argument, restrict symbols to those from the current
+buffer. Pushes a mark onto the tag ring just like `find-tag'."
+
+  (interactive "P")
+  (let (symbols marker)
+    (if (not arg)
+        (setq symbols (js--get-all-known-symbols))
+      (setq symbols (make-hash-table :test 'equal))
+      (js--imenu-to-flat (js--imenu-create-index)
+                               "" symbols))
+
+    (setq marker (cdr (js--read-symbol
+                       symbols "Jump to: "
+                       (js--guess-symbol-at-point))))
+
+    (ring-insert find-tag-marker-ring (point-marker))
+    (switch-to-buffer (marker-buffer marker))
+    (push-mark)
+    (goto-char marker)))
+
+;;; MozRepl integration
+
+(put 'js-moz-bad-rpc 'error-conditions '(error timeout))
+(put 'js-moz-bad-rpc 'error-message "Mozilla RPC Error")
+
+(put 'js-js-error 'error-conditions '(error js-error))
+(put 'js-js-error 'error-message "Javascript Error")
+
+(defun js--wait-for-matching-output
+  (process regexp timeout &optional start)
+  "Wait TIMEOUT seconds for PROCESS to output something that
+matches REGEXP. On timeout, return nil. On success, return t with
+match data set. If START is non-nil, look for output starting
+from START. Otherwise, use the current value of `process-mark'."
+  (with-current-buffer (process-buffer process)
+    (loop with start-pos = (or start
+                               (marker-position (process-mark process)))
+          with end-time = (+ (float-time) timeout)
+          for time-left = (- end-time (float-time))
+          do (goto-char (point-max))
+          if (looking-back regexp start-pos) return t
+          while (> time-left 0)
+          do (accept-process-output process time-left nil t)
+          do (goto-char (process-mark process))
+          finally do (signal
+                      'js-moz-bad-rpc
+                      (list (format "Timed out waiting for output matching %S" regexp))))))
+
+(defstruct js--js-handle
+  ;; Integer, mirrors the value we see in JS
+  (id nil :read-only t)
+
+  ;; Process to which this thing belongs
+  (process nil :read-only t))
+
+(defun js--js-handle-expired-p (x)
+  (not (eq (js--js-handle-process x)
+           (inferior-moz-process))))
+
+(defvar js--js-references nil
+  "Maps Elisp Javascript proxy objects to their Javascript IDs.")
+
+(defvar js--js-process nil
+  "The last MozRepl process object we saw")
+
+(defvar js--js-gc-idle-timer nil
+  "Idle timer for cleaning up JS object references")
+
+(defvar js--js-last-gcs-done nil
+  "Last number of gcs we GCed with")
+
+(defconst js--moz-interactor
+  (replace-regexp-in-string
+   "[ \n]+" " "
+   ; */" Make Emacs happy
+"(function(repl) {
+  repl.defineInteractor('js', {
+    onStart: function onStart(repl) {
+      if(!repl._jsObjects) {
+        repl._jsObjects = {};
+        repl._jsLastID = 0;
+        repl._jsGC = this._jsGC;
+      }
+      this._input = '';
+    },
+
+    _jsGC: function _jsGC(ids_in_use) {
+      var objects = this._jsObjects;
+      var keys = [];
+      var num_freed = 0;
+
+      for(var pn in objects) {
+        keys.push(Number(pn));
+      }
+
+      keys.sort(function(x, y) x - y);
+      ids_in_use.sort(function(x, y) x - y);
+      var i = 0;
+      var j = 0;
+
+      while(i < ids_in_use.length && j < keys.length) {
+        var id = ids_in_use[i++];
+        while(j < keys.length && keys[j] !== id) {
+          var k_id = keys[j++];
+          delete objects[k_id];
+          ++num_freed;
+        }
+        ++j;
+      }
+
+      while(j < keys.length) {
+        var k_id = keys[j++];
+        delete objects[k_id];
+        ++num_freed;
+      }
+
+      return num_freed;
+    },
+
+    _mkArray: function _mkArray() {
+      var result = [];
+      for(var i = 0; i < arguments.length; ++i) {
+        result.push(arguments[i]);
+      }
+      return result;
+    },
+
+    _parsePropDescriptor: function _parsePropDescriptor(parts) {
+      if(typeof parts === 'string') {
+        parts = [ parts ];
+      }
+
+      var obj = parts[0];
+      var start = 1;
+
+      if(typeof obj === 'string') {
+        obj = window;
+        start = 0;
+      } else if(parts.length < 2) {
+        throw new Error('expected at least 2 arguments');
+      }
+
+      for(var i = start; i < parts.length - 1; ++i) {
+        obj = obj[parts[i]];
+      }
+
+      return [obj, parts[parts.length - 1]];
+    },
+
+    _getProp: function _getProp(/*...*/) {
+      if(arguments.length === 0) {
+        throw new Error('no arguments supplied to getprop');
+      }
+
+      if(arguments.length === 1 &&
+         (typeof arguments[0]) !== 'string')
+      {
+        return arguments[0];
+      }
+
+      var [obj, propname] = this._parsePropDescriptor(arguments);
+      return obj[propname];
+    },
+
+    _putProp: function _putProp(properties, value) {
+      var [obj, propname] = this._parsePropDescriptor(properties);
+      obj[propname] = value;
+    },
+
+    _delProp: function _delProp(propname) {
+      var [obj, propname] = this._parsePropDescriptor(arguments);
+      delete obj[propname];
+    },
+
+    _typeOf: function _typeOf(thing) {
+      return typeof thing;
+    },
+
+    _callNew: function(constructor) {
+      if(typeof constructor === 'string')
+      {
+        constructor = window[constructor];
+      } else if(constructor.length === 1 &&
+                typeof constructor[0] !== 'string')
+      {
+        constructor = constructor[0];
+      } else {
+        var [obj,propname] = this._parsePropDescriptor(constructor);
+        constructor = obj[propname];
+      }
+
+      /* Hacky, but should be robust */
+      var s = 'new constructor(';
+      for(var i = 1; i < arguments.length; ++i) {
+        if(i != 1) {
+          s += ',';
+        }
+
+        s += 'arguments[' + i + ']';
+      }
+
+      s += ')';
+      return eval(s);
+    },
+
+    _callEval: function(thisobj, js) {
+      return eval.call(thisobj, js);
+    },
+
+    getPrompt: function getPrompt(repl) {
+      return 'EVAL>'
+    },
+
+    _lookupObject: function _lookupObject(repl, id) {
+      if(typeof id === 'string') {
+        switch(id) {
+        case 'global':
+          return window;
+        case 'nil':
+          return null;
+        case 't':
+          return true;
+        case 'false':
+          return false;
+        case 'undefined':
+          return undefined;
+        case 'repl':
+          return repl;
+        case 'interactor':
+          return this;
+        case 'NaN':
+          return NaN;
+        case 'Infinity':
+          return Infinity;
+        case '-Infinity':
+          return -Infinity;
+        default:
+          throw new Error('No object with special id:' + id);
+        }
+      }
+
+      var ret = repl._jsObjects[id];
+      if(ret === undefined) {
+        throw new Error('No object with id:' + id + '(' + typeof id + ')');
+      }
+      return ret;
+    },
+
+    _findOrAllocateObject: function _findOrAllocateObject(repl, value) {
+      if(typeof value !== 'object'  && typeof value !== 'function') {
+        throw new Error('_findOrAllocateObject called on non-object('
+                        + typeof(value) + '): '
+                        + value)
+      }
+
+      for(var id in repl._jsObjects) {
+        id = Number(id);
+        var obj = repl._jsObjects[id];
+        if(obj === value) {
+          return id;
+        }
+      }
+
+      var id = ++repl._jsLastID;
+      repl._jsObjects[id] = value;
+      return id;
+    },
+
+    _fixupList: function _fixupList(repl, list) {
+      for(var i = 0; i < list.length; ++i) {
+        if(list[i] instanceof Array) {
+          this._fixupList(repl, list[i]);
+        } else if(typeof list[i] === 'object') {
+          var obj = list[i];
+          if(obj.funcall) {
+            var parts = obj.funcall;
+            this._fixupList(repl, parts);
+            var [thisobj, func] = this._parseFunc(parts[0]);
+            list[i] = func.apply(thisobj, parts.slice(1));
+          } else if(obj.objid) {
+            list[i] = this._lookupObject(repl, obj.objid);
+          } else {
+            throw new Error('Unknown object type: ' + obj.toSource());
+          }
+        }
+      }
+    },
+
+    _parseFunc: function(func) {
+      var thisobj = null;
+
+      if(typeof func === 'string') {
+        func = window[func];
+      } else if(func instanceof Array) {
+        if(func.length === 1 && typeof func[0] !== 'string') {
+          func = func[0];
+        } else {
+          [thisobj, func] = this._parsePropDescriptor(func);
+          func = thisobj[func];
+        }
+      }
+
+      return [thisobj,func];
+    },
+
+    _encodeReturn: function(value, array_as_mv) {
+      var ret;
+
+      if(value === null) {
+        ret = ['special', 'null'];
+      } else if(value === true) {
+        ret = ['special', 'true'];
+      } else if(value === false) {
+        ret = ['special', 'false'];
+      } else if(value === undefined) {
+        ret = ['special', 'undefined'];
+      } else if(typeof value === 'number') {
+        if(isNaN(value)) {
+          ret = ['special', 'NaN'];
+        } else if(value === Infinity) {
+          ret = ['special', 'Infinity'];
+        } else if(value === -Infinity) {
+          ret = ['special', '-Infinity'];
+        } else {
+          ret = ['atom', value];
+        }
+      } else if(typeof value === 'string') {
+        ret = ['atom', value];
+      } else if(array_as_mv && value instanceof Array) {
+        ret = ['array', value.map(this._encodeReturn, this)];
+      } else {
+        ret = ['objid', this._findOrAllocateObject(repl, value)];
+      }
+
+      return ret;
+    },
+
+    _handleInputLine: function _handleInputLine(repl, line) {
+      var ret;
+      var array_as_mv = false;
+
+      try {
+        if(line[0] === '*') {
+          array_as_mv = true;
+          line = line.substring(1);
+        }
+        var parts = eval(line);
+        this._fixupList(repl, parts);
+        var [thisobj, func] = this._parseFunc(parts[0]);
+        ret = this._encodeReturn(
+          func.apply(thisobj, parts.slice(1)),
+          array_as_mv);
+      } catch(x) {
+        ret = ['error', x.toString() ];
+      }
+
+      var JSON = Components.classes['@mozilla.org/dom/json;1'].createInstance(Components.interfaces.nsIJSON);
+      repl.print(JSON.encode(ret));
+      repl._prompt();
+    },
+
+    handleInput: function handleInput(repl, chunk) {
+      this._input += chunk;
+      var match, line;
+      while(match = this._input.match(/.*\\n/)) {
+        line = match[0];
+
+        if(line === 'EXIT\\n') {
+          repl.popInteractor();
+          repl._prompt();
+          return;
+        }
+
+        this._input = this._input.substring(line.length);
+        this._handleInputLine(repl, line);
+      }
+    }
+  });
+})
+")
+
+  "String to set MozRepl up into a simple-minded evaluation mode")
+
+(defun js--js-encode-value (x)
+  "Marshall the given value for JS. Strings and numbers get
+JSON-encoded. Lists (including nil) are made into Javascript
+array literals and their contents encoded with
+js--js-encode-value."
+
+  (cond ((stringp x) (json-encode-string x))
+        ((numberp x) (json-encode-number x))
+        ((symbolp x) (format "{objid:%S}" (symbol-name x)))
+        ((js--js-handle-p x)
+
+         (when (js--js-handle-expired-p x)
+           (error "Stale JS handle"))
+
+         (format "{objid:%s}" (js--js-handle-id x)))
+
+        ((sequencep x)
+         (if (eq (car-safe x) 'js--funcall)
+             (format "{funcall:[%s]}"
+                     (mapconcat #'js--js-encode-value (cdr x) ","))
+           (concat
+            "[" (mapconcat #'js--js-encode-value x ",") "]")))
+        (t
+         (error "Unrecognized item: %S" x))))
+
+(defconst js--js-prompt-regexp "\\(repl[0-9]*\\)> $")
+(defconst js--js-repl-prompt-regexp "^EVAL>$")
+(defvar js--js-repl-depth 0)
+
+(defun js--js-wait-for-eval-prompt ()
+  (js--wait-for-matching-output
+   (inferior-moz-process)
+   js--js-repl-prompt-regexp js-js-timeout
+
+   ;; start matching against the beginning of the line in
+   ;; order to catch a prompt that's only partially arrived
+   (save-excursion (forward-line 0) (point))))
+
+(defun js--js-enter-repl ()
+  (inferior-moz-process) ; called for side-effect
+  (with-current-buffer inferior-moz-buffer
+    (goto-char (point-max))
+
+    ;; Do some initialization the first time we see a process
+    (unless (eq (inferior-moz-process) js--js-process)
+      (setq js--js-process (inferior-moz-process))
+      (setq js--js-references (make-hash-table :test 'eq :weakness t))
+      (setq js--js-repl-depth 0)
+
+      ;; Send interactor definition
+      (comint-send-string js--js-process js--moz-interactor)
+      (comint-send-string js--js-process
+                          (concat "(" moz-repl-name ")\n"))
+      (js--wait-for-matching-output
+       (inferior-moz-process) js--js-prompt-regexp
+       js-js-timeout))
+
+    ;; Sanity check
+    (when (looking-back js--js-prompt-regexp
+                        (save-excursion (forward-line 0) (point)))
+      (setq js--js-repl-depth 0))
+
+    (if (> js--js-repl-depth 0)
+        ;; If js--js-repl-depth > 0, we *should* be seeing an
+        ;; EVAL> prompt. If we don't, give Mozilla a chance to catch
+        ;; up with us.
+        (js--js-wait-for-eval-prompt)
+
+      ;; Otherwise, tell Mozilla to enter the interactor mode
+      (insert (match-string-no-properties 1)
+              ".pushInteractor('js')")
+      (comint-send-input nil t)
+      (js--wait-for-matching-output
+       (inferior-moz-process) js--js-repl-prompt-regexp
+       js-js-timeout))
+
+    (incf js--js-repl-depth)))
+
+(defun js--js-leave-repl ()
+  (assert (> js--js-repl-depth 0))
+  (when (= 0 (decf js--js-repl-depth))
+    (with-current-buffer inferior-moz-buffer
+      (goto-char (point-max))
+      (js--js-wait-for-eval-prompt)
+      (insert "EXIT")
+      (comint-send-input nil t)
+      (js--wait-for-matching-output
+       (inferior-moz-process) js--js-prompt-regexp
+       js-js-timeout))))
+
+(defsubst js--js-not (value)
+  (memq value '(nil false undefined)))
+
+(defsubst js--js-true (value)
+  (not (js--js-not value)))
+
+(eval-and-compile
+  (defun js--optimize-arglist (arglist)
+    "Convert immediate js< and js! references to deferred ones"
+    (loop for item in arglist
+          if (eq (car-safe item) 'js<)
+          collect (append (list 'list ''js--funcall
+                                '(list 'interactor "_getProp"))
+                          (js--optimize-arglist (cdr item)))
+          else if (eq (car-safe item) 'js>)
+          collect (append (list 'list ''js--funcall
+                                '(list 'interactor "_putProp"))
+
+                          (if (atom (cadr item))
+                              (list (cadr item))
+                            (list
+                             (append
+                              (list 'list ''js--funcall
+                                    '(list 'interactor "_mkArray"))
+                              (js--optimize-arglist (cadr item)))))
+                          (js--optimize-arglist (cddr item)))
+          else if (eq (car-safe item) 'js!)
+          collect (destructuring-bind (ignored function &rest body) item
+                    (append (list 'list ''js--funcall
+                                  (if (consp function)
+                                      (cons 'list
+                                            (js--optimize-arglist function))
+                                    function))
+                            (js--optimize-arglist body)))
+          else
+          collect item)))
+
+(defmacro js--js-get-service (class-name interface-name)
+    `(js! ("Components" "classes" ,class-name "getService")
+        (js< "Components" "interfaces" ,interface-name)))
+
+(defmacro js--js-create-instance (class-name interface-name)
+  `(js! ("Components" "classes" ,class-name "createInstance")
+        (js< "Components" "interfaces" ,interface-name)))
+
+(defmacro js--js-qi (object interface-name)
+  `(js! (,object "QueryInterface")
+        (js< "Components" "interfaces" ,interface-name)))
+
+(defmacro with-js (&rest forms)
+  "Runs FORMS with the Mozilla repl set up for js commands.
+Inside the lexical scope of `with-js', `js?', `js!',
+`js-new', `js-eval', `js-list', `js<', `js>', `js-get-service',
+`js-create-instance', and `js-qi' are defined."
+
+  `(progn
+     (js--js-enter-repl)
+     (unwind-protect
+         (macrolet ((js? (&rest body) `(js--js-true ,@body))
+                    (js! (function &rest body)
+                         `(js--js-funcall
+                           ,(if (consp function)
+                                (cons 'list
+                                      (js--optimize-arglist function))
+                              function)
+                           ,@(js--optimize-arglist body)))
+
+                    (js-new (function &rest body)
+                            `(js--js-new
+                              ,(if (consp function)
+                                   (cons 'list
+                                         (js--optimize-arglist function))
+                                 function)
+                              ,@body))
+
+                    (js-eval (thisobj js)
+                            `(js--js-eval
+                              ,@(js--optimize-arglist
+                                 (list thisobj js))))
+
+                    (js-list (&rest args)
+                             `(js--js-list
+                               ,@(js--optimize-arglist args)))
+
+                    (js-get-service (&rest args)
+                                    `(js--js-get-service
+                                      ,@(js--optimize-arglist args)))
+
+                    (js-create-instance (&rest args)
+                                        `(js--js-create-instance
+                                          ,@(js--optimize-arglist args)))
+
+                    (js-qi (&rest args)
+                           `(js--js-qi
+                             ,@(js--optimize-arglist args)))
+
+                    (js< (&rest body) `(js--js-get
+                                        ,@(js--optimize-arglist body)))
+                    (js> (props value)
+                         `(js--js-funcall
+                           '(interactor "_putProp")
+                           ,(if (consp props)
+                                (cons 'list
+                                      (js--optimize-arglist props))
+                              props)
+                           ,@(js--optimize-arglist (list value))
+                           ))
+                    (js-handle? (arg) `(js--js-handle-p ,arg)))
+           ,@forms)
+       (js--js-leave-repl))))
+
+(defvar js--js-array-as-list nil
+"If bound to t and a function returns an Array, return the
+elements of that Array as a list instead of returning the whole
+Array as a JS symbol" )
+
+(defun js--js-decode-retval (result)
+  (ecase (intern (first result))
+         (atom (second result))
+         (special (intern (second result)))
+         (array
+          (mapcar #'js--js-decode-retval (second result)))
+         (objid
+          (or (gethash (second result)
+                       js--js-references)
+              (puthash (second result)
+                       (make-js--js-handle
+                        :id (second result)
+                        :process (inferior-moz-process))
+                       js--js-references)))
+
+         (error (signal 'js-js-error (list (second result))))))
+
+(defun js--js-funcall (function &rest arguments)
+  "Call the Mozilla function FUNCTION with arguments ARGUMENTS.
+If function is a string, look it up as a property on the global
+object and use the global object for `this'; if FUNCTION is a
+list with one element, use that element as the function with the
+global object for `this', except that if that single element is a
+string, look it up on the global object. If FUNCTION is a list
+with more than one argument, use the list up to the last value as
+a property descriptor and the last argument as a function."
+
+  (with-js
+   (let ((argstr (js--js-encode-value
+                  (cons function arguments))))
+
+     (with-current-buffer inferior-moz-buffer
+       ;; Actual funcall
+       (when js--js-array-as-list
+         (insert "*"))
+       (insert argstr)
+       (comint-send-input nil t)
+       (js--wait-for-matching-output
+        (inferior-moz-process) "EVAL>"
+        js-js-timeout)
+       (goto-char comint-last-input-end)
+
+       ;; Read the result
+       (let* ((json-array-type 'list)
+              (result (prog1 (json-read)
+                        (goto-char (point-max)))))
+         (js--js-decode-retval result))))))
+
+(defun js--js-new (constructor &rest arguments)
+  "Call CONSTRUCTOR as a constructor (as with new), with the
+given arguments ARGUMENTS. CONSTRUCTOR is a JS handle, a string,
+or a list of these things."
+
+  (apply #'js--js-funcall
+         '(interactor "_callNew")
+         constructor arguments))
+
+(defun js--js-eval (thisobj js)
+  (js--js-funcall '(interactor "_callEval") thisobj js))
+
+(defun js--js-list (&rest arguments)
+  "Return a Lisp array that is the result of evaluating each of
+  the elements of ARGUMENTS"
+
+  (let ((js--js-array-as-list t))
+    (apply #'js--js-funcall '(interactor "_mkArray")
+           arguments)))
+
+(defun js--js-get (&rest props)
+  (apply #'js--js-funcall '(interactor "_getProp") props))
+
+(defun js--js-put (props value)
+  (js--js-funcall '(interactor "_putProp") props value))
+
+(defun js-gc (&optional force)
+  "Tell the repl about any objects we don't reference anymore.
+With argument, run even if no intervening GC has happened."
+  (interactive)
+
+  (when force
+    (setq js--js-last-gcs-done nil))
+
+  (let ((this-gcs-done gcs-done) keys num)
+    (when (and js--js-references
+               (boundp 'inferior-moz-buffer)
+               (buffer-live-p inferior-moz-buffer)
+
+               ;; Don't bother running unless we've had an intervening
+               ;; garbage collection; without a gc, nothing is deleted
+               ;; from the weak hash table, so it's pointless telling
+               ;; MozRepl about that references we still hold
+               (not (eq js--js-last-gcs-done this-gcs-done))
+
+               ;; Are we looking at a normal prompt? Make sure not to
+               ;; interrupt the user if he's doing something
+               (with-current-buffer inferior-moz-buffer
+                 (save-excursion
+                   (goto-char (point-max))
+                   (looking-back js--js-prompt-regexp
+                                 (save-excursion (forward-line 0) (point))))))
+
+      (setq keys (loop for x being the hash-keys
+                       of js--js-references
+                       collect x))
+      (setq num (js--js-funcall '(repl "_jsGC") (or keys [])))
+
+      (setq js--js-last-gcs-done this-gcs-done)
+      (when (interactive-p)
+        (message "Cleaned %s entries" num))
+
+      num)))
+
+(run-with-idle-timer 30 t #'js-gc)
+
+(defun js-eval (js)
+  "Evaluate the Javascript contained in JS and return the
+JSON-decoded result. eval must be called using this function
+because it is special to the Javascript interpreter."
+  (interactive "MJavascript to evaluate: ")
+  (with-js
+   (let* ((content-window (js--js-content-window
+                           (js--get-js-context)))
+          (result (js-eval content-window js)))
+
+     (when (interactive-p)
+       (message "%s" (js! "String" result)))
+     result)))
+
+(defun js--get-tabs ()
+  "Enumerate all the contexts available. Each one is a list:
+
+   The list is (TITLE URL BROWSER TAB TABBROWSER) for content documents
+   The list is (TITLE URL WINDOW) for windows
+
+   All tabs of a given window are grouped together. The most
+   recent window is first. Within each window, the tabs are
+   returned left-to-right.
+"
+  (with-js
+   (let (windows)
+
+     (loop with window-mediator = (js! ("Components" "classes"
+                                        "@mozilla.org/appshell/window-mediator;1"
+                                        "getService")
+                                       (js< "Components" "interfaces"
+                                            "nsIWindowMediator"))
+           with enumerator = (js! (window-mediator "getEnumerator") nil)
+
+           while (js? (js! (enumerator "hasMoreElements")))
+           for window = (js! (enumerator "getNext"))
+           for window-info = (js-list window
+                                      (js< window "document" "title")
+                                      (js! (window "location" "toString"))
+                                      (js< window "closed")
+                                      (js< window "windowState"))
+
+           unless (or (js? (fourth window-info))
+                      (eq (fifth window-info) 2))
+           do (push window-info windows))
+
+     (loop for window-info in windows
+           for window = (first window-info)
+           collect (list (second window-info)
+                         (third window-info)
+                         window)
+
+           for gbrowser = (js< window "gBrowser")
+           if (js-handle? gbrowser)
+           nconc (loop
+                  for x below (js< gbrowser "browsers" "length")
+                  collect (js-list (js< gbrowser
+                                        "browsers"
+                                        x
+                                        "contentDocument"
+                                        "title")
+
+                                   (js! (gbrowser
+                                         "browsers"
+                                         x
+                                         "contentWindow"
+                                         "location"
+                                         "toString"))
+                                   (js< gbrowser
+                                        "browsers"
+                                        x)
+
+                                   (js! (gbrowser
+                                         "tabContainer"
+                                         "childNodes"
+                                         "item")
+                                        x)
+
+                                   gbrowser))))))
+
+(defvar js-read-tab-history nil)
+
+(defun js--read-tab (prompt)
+  "Read a Mozilla tab with prompt PROMPT. Return a cons of (TYPE . OBJECT).
+TYPE is either 'window or 'tab, and OBJECT is a Javascript handle
+to a ChromeWindow or a browser, respectively."
+
+  ;; Prime IDO
+  (unless ido-mode
+    (ido-mode t)
+    (ido-mode nil))
+
+  (with-js
+   (lexical-let ((tabs (js--get-tabs)) selected-tab-cname
+                 selected-tab prev-hitab)
+
+     ;; Disambiguate names
+     (setq tabs (loop with tab-names = (make-hash-table :test 'equal)
+                      for tab in tabs
+                      for cname = (format "%s (%s)" (second tab) (first tab))
+                      for num = (incf (gethash cname tab-names -1))
+                      if (> num 0)
+                      do (setq cname (format "%s <%d>" cname num))
+                      collect (cons cname tab)))
+
+     (labels ((find-tab-by-cname
+               (cname)
+               (loop for tab in tabs
+                     if (equal (car tab) cname)
+                     return (cdr tab)))
+
+              (mogrify-highlighting
+               (hitab unhitab)
+
+               ;; Hack to reduce the number of
+               ;; round-trips to mozilla
+               (let (cmds)
+                 (cond
+                  ;; Highlighting tab
+                  ((fourth hitab)
+                   (push '(js! ((fourth hitab) "setAttribute")
+                               "style"
+                               "color: red; font-weight: bold")
+                         cmds)
+
+                   ;; Highlight window proper
+                   (push '(js! ((third hitab)
+                                "setAttribute")
+                               "style"
+                               "border: 8px solid red")
+                         cmds)
+
+                   ;; Select tab, when appropriate
+                   (when js-js-switch-tabs
+                     (push
+                      '(js> ((fifth hitab) "selectedTab") (fourth hitab))
+                      cmds)))
+
+                  ;; Hilighting whole window
+                  ((third hitab)
+                   (push '(js! ((third hitab) "document"
+                                "documentElement" "setAttribute")
+                               "style"
+                               (concat "-moz-appearance: none;"
+                                       "border: 8px solid red;"))
+                         cmds)))
+
+                 (cond
+                  ;; Unhighlighting tab
+                  ((fourth unhitab)
+                   (push '(js! ((fourth unhitab) "setAttribute") "style" "")
+                         cmds)
+                   (push '(js! ((third unhitab) "setAttribute") "style" "")
+                         cmds))
+
+                  ;; Unhighlighting window
+                  ((third unhitab)
+                   (push '(js! ((third unhitab) "document"
+                                "documentElement" "setAttribute")
+                               "style" "")
+                         cmds)))
+
+                 (eval (list 'with-js
+                             (cons 'js-list (nreverse cmds))))))
+
+              (command-hook
+               ()
+               (let* ((tab (find-tab-by-cname (car ido-matches))))
+                 (mogrify-highlighting tab prev-hitab)
+                 (setq prev-hitab tab)))
+
+              (setup-hook
+               ()
+               ;; Fiddle with the match list a bit: if our first match
+               ;; is a tabbrowser window, rotate the match list until
+               ;; the active tab comes up
+               (let ((matched-tab (find-tab-by-cname (car ido-matches))))
+                 (when (and matched-tab
+                            (null (fourth matched-tab))
+                            (equal "navigator:browser"
+                                   (js! ((third matched-tab)
+                                         "document"
+                                         "documentElement"
+                                         "getAttribute")
+                                        "windowtype")))
+
+                   (loop with tab-to-match = (js< (third matched-tab)
+                                                  "gBrowser"
+                                                  "selectedTab")
+
+                         with index = 0
+                         for match in ido-matches
+                         for candidate-tab = (find-tab-by-cname match)
+                         if (eq (fourth candidate-tab) tab-to-match)
+                         do (setq ido-cur-list (ido-chop ido-cur-list match))
+                         and return t)))
+
+               (add-hook 'post-command-hook #'command-hook t t)))
+
+
+       (unwind-protect
+           (setq selected-tab-cname
+                 (let ((ido-minibuffer-setup-hook
+                        (cons #'setup-hook ido-minibuffer-setup-hook)))
+                   (ido-completing-read
+                    prompt
+                    (mapcar #'car tabs)
+                    nil t nil
+                    'js-read-tab-history)))
+
+         (when prev-hitab
+           (mogrify-highlighting nil prev-hitab)
+           (setq prev-hitab nil)))
+
+       (add-to-history 'js-read-tab-history selected-tab-cname)
+
+       (setq selected-tab (loop for tab in tabs
+                                if (equal (car tab) selected-tab-cname)
+                                return (cdr tab)))
+
+       (if (fourth selected-tab)
+           (cons 'browser (third selected-tab))
+         (cons 'window (third selected-tab)))))))
+
+(defun js--guess-eval-defun-info (pstate)
+  "Internal helper for `js-eval-defun'. Returns a list (NAME . CLASSPARTS),
+where CLASSPARTS is a list of strings making up the class name
+and NAME is the name of the function part."
+  (cond ((and (= (length pstate) 3)
+              (eq (js--pitem-type (first pstate)) 'function)
+              (= (length (js--pitem-name (first pstate))) 1)
+              (consp (js--pitem-type (second pstate))))
+
+         (append (js--pitem-name (second pstate))
+                 (list (first (js--pitem-name (first pstate))))))
+
+        ((and (= (length pstate) 2)
+              (eq (js--pitem-type (first pstate)) 'function))
+
+         (append
+          (butlast (js--pitem-name (first pstate)))
+          (list (car (last (js--pitem-name (first pstate)))))))
+
+        (t (error "Function not a toplevel defun or class member"))))
+
+(defvar js--js-context nil
+  "The current JS context. This is a cons like the one returned
+from `js--read-tab'. Change with
+`js-set-js-context'.")
+
+(defconst js--js-inserter
+  "(function(func_info,func) {
+    func_info.unshift('window');
+    var obj = window;
+    for(var i = 1; i < func_info.length - 1; ++i) {
+      var next = obj[func_info[i]];
+      if(typeof next !== 'object' && typeof next !== 'function') {
+        next = obj.prototype && obj.prototype[func_info[i]];
+        if(typeof next !== 'object' && typeof next !== 'function') {
+          alert('Could not find ' + func_info.slice(0, i+1).join('.') +
+                ' or ' + func_info.slice(0, i+1).join('.') + '.prototype');
+          return;
+        }
+
+        func_info.splice(i+1, 0, 'prototype');
+        ++i;
+      }
+    }
+
+    obj[func_info[i]] = func;
+    alert('Successfully updated '+func_info.join('.'));
+  })")
+
+(defun js-set-js-context (context)
+  "Set the Javascript context to CONTEXT, reading from the user
+if interactive"
+  (interactive (list (js--read-tab "Javascript Context: ")))
+  (setq js--js-context context))
+
+(defun js--get-js-context ()
+  "Return a valid JS context. If one hasn't been set, or if it's
+stale, ask the user for a new one."
+
+  (with-js
+   (when (or (null js--js-context)
+             (js--js-handle-expired-p (cdr js--js-context))
+             (ecase (car js--js-context)
+               (window (js? (js< (cdr js--js-context) "closed")))
+               (browser (not (js? (js< (cdr js--js-context)
+                                       "contentDocument"))))))
+     (setq js--js-context (js--read-tab "Javascript Context: ")))
+
+   js--js-context))
+
+(defun js--js-content-window (context)
+  (with-js
+   (ecase (car context)
+     (window (cdr context))
+     (browser (js< (cdr context)
+                   "contentWindow" "wrappedJSObject")))))
+
+(defun js--make-nsilocalfile (path)
+  (with-js
+   (let ((file (js-create-instance "@mozilla.org/file/local;1"
+                                   "nsILocalFile")))
+     (js! (file "initWithPath") path)
+     file)))
+
+(defun js--js-add-resource-alias (alias path)
+  (with-js
+   (let* ((io-service (js-get-service "@mozilla.org/network/io-service;1"
+                                                "nsIIOService"))
+          (res-prot (js! (io-service "getProtocolHandler") "resource"))
+          (res-prot (js-qi res-prot "nsIResProtocolHandler"))
+          (path-file (js--make-nsilocalfile path))
+          (path-uri (js! (io-service "newFileURI") path-file)))
+     (js! (res-prot "setSubstitution") alias path-uri))))
+
+(defun* js-eval-defun ()
+  "Update some Mozilla tab with defun containing point or after
+point"
+  (interactive)
+
+  ;; This function works by generating a temporary file that contains
+  ;; the function we'd like to insert. We then use the elisp-js bridge
+  ;; to command mozilla to load this file by inserting a script tag
+  ;; into the document we set. This way, debuggers and such will have
+  ;; a way to find the source of the just-inserted function.
+  ;;
+  ;; We delete the temporary file if there's an error, but otherwise
+  ;; we add an unload event listener on the Mozilla side to delete the
+  ;; file.
+
+  (save-excursion
+    (let (begin end pstate defun-info temp-name defun-body)
+      (js--end-of-defun)
+      (setq end (point))
+      (js--ensure-cache)
+      (js--beginning-of-defun)
+      (re-search-forward "\\_<function\\_>")
+      (setq begin (match-beginning 0))
+      (setq pstate (js--forward-pstate))
+
+      (when (or (null pstate)
+                (> (point) end))
+        (error "Could not locate function definition"))
+
+      (setq defun-info (js--guess-eval-defun-info pstate))
+
+      (let ((overlay (make-overlay begin end)))
+        (overlay-put overlay 'face 'highlight)
+        (unwind-protect
+            (unless (y-or-n-p (format "Send %s to Mozilla? "
+                                      (mapconcat #'identity defun-info ".")))
+              (message "") ; question message lingers until next command
+              (return-from js-eval-defun))
+          (delete-overlay overlay)))
+
+      (setq defun-body (buffer-substring-no-properties begin end))
+
+      (make-directory js-js-tmpdir t)
+
+      ;; (Re)register a Mozilla resource URL to point to the
+      ;; temporary directory
+      (js--js-add-resource-alias "js" js-js-tmpdir)
+
+      (setq temp-name (make-temp-file (concat js-js-tmpdir
+                                             "/js-")
+                                      nil ".js"))
+      (unwind-protect
+          (with-js
+            (with-temp-buffer
+              (insert js--js-inserter)
+              (insert "(")
+              (insert (json-encode-list defun-info))
+              (insert ",\n")
+              (insert defun-body)
+              (insert "\n)")
+              (write-region (point-min) (point-max) temp-name
+                            nil 1))
+
+            ;; Give Mozilla responsibility for deleting this file
+            (let* ((content-window (js--js-content-window
+                                    (js--get-js-context)))
+                   (content-document (js< content-window "document"))
+                   (head (if (js? (js< content-document "body"))
+                             ;; Regular content
+                             (js< (js! (content-document "getElementsByTagName")
+                                       "head")
+                                  0)
+                           ;; Chrome
+                           (js< content-document "documentElement")))
+                   (elem (js! (content-document "createElementNS")
+                              "http://www.w3.org/1999/xhtml" "script")))
+
+              (js! (elem "setAttribute") "type" "text/javascript")
+              (js! (elem "setAttribute") "src"
+                   (format "resource://js/%s"
+                           (file-name-nondirectory temp-name)))
+
+              (js! (head "appendChild") elem)
+
+              (js! (content-window "addEventListener") "unload"
+                   (js! ((js-new
+                          "Function" "file"
+                          "return function() { file.remove(false) }"))
+                        (js--make-nsilocalfile temp-name))
+                   'false)
+              (setq temp-name nil)
+
+
+
+              ))
+
+        ;; temp-name is set to nil on success
+        (when temp-name
+          (delete-file temp-name))))))
+
+;;; Main Function
+
+;;;###autoload
+(define-derived-mode js-mode nil "js"
+  "Major mode for editing JavaScript source text.
+
+Key bindings:
+
+\\{js-mode-map}"
+
+  :group 'js
+  :syntax-table js-mode-syntax-table
+
+  (set (make-local-variable 'indent-line-function) 'js-indent-line)
+  (set (make-local-variable 'beginning-of-defun-function)
+       'js--beginning-of-defun)
+  (set (make-local-variable 'end-of-defun-function)
+       'js--end-of-defun)
+
+  (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil)
+  (set (make-local-variable 'font-lock-defaults)
+       (list js--font-lock-keywords
+	     nil nil nil nil
+	     '(font-lock-syntactic-keywords
+               . js--font-lock-syntactic-keywords)))
+
+  (set (make-local-variable 'parse-sexp-ignore-comments) t)
+  (set (make-local-variable 'parse-sexp-lookup-properties) t)
+  (set (make-local-variable 'which-func-imenu-joiner-function)
+       #'js--which-func-joiner)
+
+  ;; Comments
+  (setq comment-start "// ")
+  (setq comment-end "")
+  (set (make-local-variable 'fill-paragraph-function)
+       'js-c-fill-paragraph)
+
+  ;; Parse cache
+  (add-hook 'before-change-functions #'js--flush-caches t t)
+
+  ;; Frameworks
+  (js--update-quick-match-re)
+
+  ;; Imenu
+  (setq imenu-case-fold-search nil)
+  (set (make-local-variable 'imenu-create-index-function)
+       #'js--imenu-create-index)
+
+  (setq major-mode 'js-mode)
+  (setq mode-name "Javascript")
+
+  ;; for filling, pretend we're cc-mode
+  (setq c-comment-prefix-regexp "//+\\|\\**"
+        c-paragraph-start "$"
+        c-paragraph-separate "$"
+        c-block-comment-prefix "* "
+        c-line-comment-starter "//"
+        c-comment-start-regexp "/[*/]\\|\\s!"
+        comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
+
+  (let ((c-buffer-is-cc-mode t))
+    (c-setup-paragraph-variables))
+
+  (set (make-local-variable 'syntax-begin-function)
+       #'js--syntax-begin-function)
+
+  ;; Important to fontify the whole buffer syntactically! If we don't,
+  ;; then we might have regular expression literals that aren't marked
+  ;; as strings, which will screw up parse-partial-sexp, scan-lists,
+  ;; etc. and and produce maddening "unbalanced parenthesis" errors.
+  ;; When we attempt to find the error and scroll to the portion of
+  ;; the buffer containing the problem, JIT-lock will apply the
+  ;; correct syntax to the regular expresion literal and the problem
+  ;; will mysteriously disappear.
+  (font-lock-set-defaults)
+
+  (let (font-lock-keywords) ; leaves syntactic keywords intact
+    (font-lock-fontify-buffer)))
+
+(defalias 'javascript-mode 'js-mode)
+
+(eval-after-load 'folding
+  '(when (fboundp 'folding-add-to-marks-list)
+     (folding-add-to-marks-list 'js-mode "// {{{" "// }}}" )))
+
+(provide 'js)
+
+;; js.el ends here