   blur:      'UIEvents'
   focusout:  'UIEvents'
 
+# Internal: Base error class for errors raised in Page.
+class PageError
+  constructor: (@page, @message) ->
+
+  raise: ->
+    error = new Error @message
+    error.name = @constructor.name
+    error.page = @page
+    throw error
+
+  @raise: (page, message) ->
+    (new @ page, message).raise()
+
+# Internal: Specialized error for when an attempt to
+# interact with elements from another page happens.
+class DocumentMismatchError extends PageError
+  # Internal: Asserts that element belongs to document.
+  #
+  # node - Any Node whose ownership we'd like to verify.
+  # document - Any Document we'd like to verify as the owner.
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError if element's owner isn't document.
+  @assert: (page, node) ->
+    if node.ownerDocument isnt page.document
+      @raise page, node
+
+  constructor: (page, @node) ->
+    super page, "you're attempting to use a node (#{inspect @node}) that doesn't belong to this page"
+
+# Internal: Specialized error for when we were unable to find a node.
+class NodeNotFoundError extends PageError
+  constructor: (page, @xpath) ->
+    super page, "expected to find a node matching #{inspect @xpath}, but none were found"
+
+# Internal: Specialized error for when we're looking for an element's containing form.
+class MissingFormError extends PageError
+  constructor: (page, @xpath, @document) ->
+    super page, "expected node matching #{inspect @xpath} to be contained by a <form>"
+
+
+# Public: Provides a wrapper around an +HTMLDocument+ to simplify interaction with it.
+#
+# Examples
+#
+#   # click the first link on a page
+#   page.click '//a'
+#
+#   # fill in a search field
+#   page.fill '//input[@name="q"]', "doctor who"
 class Page
   constructor: (@document) ->
 
-  #
-  # Returns the title of this page.
-  #
+  # Public: Gets the String page title, tainted.
   @::__defineGetter__ 'title', ->
     privacy.taint @document.title
 
+  # Public: Sets the String page title.
   @::__defineSetter__ 'title', (title) ->
     @document.title = privacy.untaint title
 
-  #
-  # Returns the window name for this page.
-  #
+  # Public: Gets the tainted String page name, tainted.
   @::__defineGetter__ 'name', ->
     privacy.taint @defaultView.name
 
-  #
-  # Returns the +Window+ associated with this page.
-  #
+  # Public: Gets this page's Window, tainted.
   @::__defineGetter__ 'defaultView', ->
     privacy.taint @document.defaultView
 
-  #
-  # Returns the URL of this page.
-  #
+  # Public: Gets this page's String URL, tainted.
   @::__defineGetter__ 'url', ->
     privacy.taint @document.URL
 
-  #
-  # Returns true if this page is in a frame, false otherwise.
-  #
+  # Public: Gets this page's HTMLBodyElement, tainted.
+  @::__defineGetter__ 'body', ->
+    privacy.taint @document.body
+
+  # Public: Determines whether this page is framed or not.
+  #
+  # Returns a Boolean that's true if this page is framed.
   @::__defineGetter__ 'framed', ->
     if frameElement = @defaultView.frameElement
       return frameElement.tagName?.toLowerCase() isnt 'browser'
     else
       return false
 
-  #
-  # Returns the top-most +Page+, the one at the root of the frame hierarchy.
-  #
+  # Public: Gets the Page at the root of the frame hierarchy.
+  #
+  # Returns a Page that is not contained by any other pages.
   @::__defineGetter__ 'topPage', ->
     @constructor.wrap @defaultView.top.document
 
-  #
-  # Returns an array of +Page+ objects wrapping all the frames contained in this +Page+.
-  #
+  # Public: Gets all Pages contained in this Page's frames.
+  #
+  # Return an Array containing Pages.
   @::__defineGetter__ 'framePages', ->
     @constructor.wrap frame.document for frame in @defaultView.frames
 
-  #
-  # Finds the first node matching +xpathOrNode+ with optional
-  # +scope+ when it is an xpath, returns it when it's a node.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to look for.
-  # scope<HTMLElement>:: The element to scope the search to.
-  #
-  # ==== Returns
-  # HTMLElement, null:: The found element, if one was found.
-  #
-  # @public
-  #
+  # Public: Finds the first matching node. This is like select(), but
+  # returns the first matching node rather than all matching nodes.
+  #
+  # xpathOrNode - If it's a Node, just returns it. Otherwise it should
+  #               be a String xpath.
+  # scope - An Element to restrict the xpath search to, ignored if xpathOrNode
+  #         is a Node.
+  #
+  # Examples
+  #
+  #   # finds the first link on the page
+  #   page.find '//a'
+  #
+  #   # nodes passed in will simply be returned.
+  #   link = page.find '//a'
+  #   (page.find link) is link
+  #   # => true
+  #
+  # Returns a tainted Node.
+  # Raises DocumentMismatchError given a node not belonging to this page.
   find: (xpathOrNode, scope) ->
-    return xpathOrNode if xpathOrNode?.nodeType
+    if xpathOrNode?.nodeType
+      DocumentMismatchError.assert @, xpathOrNode
+      return xpathOrNode
+
     xpath = Pathway.from(xpathOrNode)
     return xpath.first(@document, scope)
 
-  #
-  # Shorthand for finding an element by id, returns null if not found.
-  #
+  # Public: Finds an Element by id.
+  #
+  # id - A String matching a page element's id attribute.
+  #
+  # Examples
+  #
+  #   page.click page.byId('next-button')
+  #
+  # Returns a tainted Element or null if no matching Element was found.
   byId: (id) ->
-    privacy.taint @document.getElementById(id)
-
-  #
-  # Find a link either by id, href, content, or using a matcher function.
+    privacy.taint @document.getElementById(privacy.untaint id)
+
+  # Public: Finds a matching anchor on this page.
+  #
+  # matcher - If a String, matches links whose id or href matches exactly
+  #           or whose text content contains matcher.
+  #           If a Function, it will be called with each link on the page
+  #           until it returns true, which will cause that link to match.
+  # scope - An Element to restrict the link search to.
+  #
+  # Examples
   #
   #   # matches <a id="formSubmitButton">
   #   page.link 'formSubmitButton'

      else
        null

  #
  # Finds the next sibling matching +siblingMatcher+, if given.
  #
  # @param xpathOrNode [HTMLElement, Xpath]
  #   The thing whose next sibling is wanted.
  # @param siblingMatcher [String, null]
  #   An Xpath expression to use to match the following sibling (defaults to "*").
  #
  # @return [tainted([HTMLElement]), null]
  #
  next: (xpathOrNode, siblingMatcher='*') ->
    @find "following-sibling::#{siblingMatcher}", @findStrict(xpathOrNode)

  #
  # Returns the text content of +xpathOrNode+.
  #
  # @param xpathOrNode [HTMLElement, Xpath]
  #   The thing whose text is wanted.
  #
  # @return [tainted([String])]
  #
  text: (xpathOrNode) ->
    node = @findStrict xpathOrNode
    if node.nodeType is 3


      log.radioactive 'value=', value

      maxlength = element.getAttribute "maxlength"
      if value and maxlength
        maxlength = number.parse maxlength
        if maxlength
          log.warn "Truncating value to ", maxlength, " characters"
          value = value[0...maxlength]
