poltergeist 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +0 -0
 - data/LICENSE +20 -0
 - data/README.md +85 -0
 - data/lib/capybara/poltergeist/browser.rb +130 -0
 - data/lib/capybara/poltergeist/client/agent.coffee +175 -0
 - data/lib/capybara/poltergeist/client/browser.coffee +121 -0
 - data/lib/capybara/poltergeist/client/compiled/agent.js +198 -0
 - data/lib/capybara/poltergeist/client/compiled/browser.js +117 -0
 - data/lib/capybara/poltergeist/client/compiled/connection.js +20 -0
 - data/lib/capybara/poltergeist/client/compiled/main.js +38 -0
 - data/lib/capybara/poltergeist/client/compiled/node.js +74 -0
 - data/lib/capybara/poltergeist/client/compiled/web_page.js +136 -0
 - data/lib/capybara/poltergeist/client/connection.coffee +11 -0
 - data/lib/capybara/poltergeist/client/main.coffee +27 -0
 - data/lib/capybara/poltergeist/client/node.coffee +56 -0
 - data/lib/capybara/poltergeist/client/web_page.coffee +102 -0
 - data/lib/capybara/poltergeist/client.rb +57 -0
 - data/lib/capybara/poltergeist/driver.rb +85 -0
 - data/lib/capybara/poltergeist/errors.rb +26 -0
 - data/lib/capybara/poltergeist/node.rb +92 -0
 - data/lib/capybara/poltergeist/server.rb +35 -0
 - data/lib/capybara/poltergeist/server_manager.rb +118 -0
 - data/lib/capybara/poltergeist/version.rb +5 -0
 - data/lib/capybara/poltergeist.rb +20 -0
 - metadata +102 -0
 
    
        data/CHANGELOG.md
    ADDED
    
    | 
         
            File without changes
         
     | 
    
        data/LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2011 Jonathan Leighton
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 4 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 6 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 7 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 8 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 9 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 12 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 15 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 16 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 17 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 18 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 19 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 20 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,85 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Poltergeist - A PhantomJS driver for Capybara #
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Version: 0.1.0
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Poltergeist is a driver for [Capybara](https://github.com/jnicklas/capybara). It allows you to
         
     | 
| 
      
 6 
     | 
    
         
            +
            run your Capybara tests on a headless [WebKit](http://webkit.org) browser,
         
     | 
| 
      
 7 
     | 
    
         
            +
            provided by [PhantomJS](http://www.phantomjs.org/).
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            ## Installation ##
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            Add `poltergeist` to your Gemfile, and add `Capybara.javascript_driver = :poltergeist`
         
     | 
| 
      
 12 
     | 
    
         
            +
            in your test setup.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            You will also need PhantomJS 1.3+ on your system.
         
     | 
| 
      
 15 
     | 
    
         
            +
            [Here's how to do that](http://code.google.com/p/phantomjs/wiki/BuildInstructions).
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            Currently PhantomJS is not 'truly headless', so to run it on a continuous integration
         
     | 
| 
      
 18 
     | 
    
         
            +
            server you will need to use [Xvfb](http://en.wikipedia.org/wiki/Xvfb). You can either use the
         
     | 
| 
      
 19 
     | 
    
         
            +
            [headless gem](https://github.com/leonid-shevtsov/headless) for this,
         
     | 
| 
      
 20 
     | 
    
         
            +
            or make sure that Xvfb is running and the `DISPLAY` environment variable is set.
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            ## What's supported? ##
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            Poltergeist supports basically everything that is supported by the stock Selenium driver,
         
     | 
| 
      
 25 
     | 
    
         
            +
            including Javascript, drag-and-drop, etc.
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            Additionally, you can grab screenshots of the page at any point by calling
         
     | 
| 
      
 28 
     | 
    
         
            +
            `page.driver.render('/path/to/file.png')` (this works the same way as the PhantomJS
         
     | 
| 
      
 29 
     | 
    
         
            +
            render feature, so you can specify other extensions like `.pdf`, `.gif`, etc.)
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            ## Customisation ##
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            You can customise the way that Capybara sets up Poltegeist via the following code in your
         
     | 
| 
      
 34 
     | 
    
         
            +
            test setup:
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                Capybara.register_driver :poltergeist do |app|
         
     | 
| 
      
 37 
     | 
    
         
            +
                  Capybara::Poltergeist::Driver.new(app, options)
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            `options` is a hash of options. The following options are supported:
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              * `:phantomjs` (String) - A custom path to the phantomjs executable
         
     | 
| 
      
 43 
     | 
    
         
            +
              * `:debug` (Boolean) - When true, debug output is logged to `STDERR`
         
     | 
| 
      
 44 
     | 
    
         
            +
              * `:logger` (Object responding to `puts`) - When present, debug output is written to this object
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            ## Bugs ##
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            Please file bug reports on Github and include example code to reproduce the problem wherever
         
     | 
| 
      
 49 
     | 
    
         
            +
            possible. (Tests are even better.)
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            ## Why not use [capybara-webkit](https://github.com/thoughtbot/capybara-webkit)? ##
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            If capybara-webkit works for you, then by all means carry on using it.
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            However, I have had some trouble with it, and Poltergeist basically started
         
     | 
| 
      
 56 
     | 
    
         
            +
            as an experiment to see whether a PhantomJS driver was possible. (It turned out it
         
     | 
| 
      
 57 
     | 
    
         
            +
            was, but only thanks to some new features in the recent 1.3.0 release.)
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            In the long term, I think having a PhantomJS driver makes sense, because that allows
         
     | 
| 
      
 60 
     | 
    
         
            +
            PhantomJS to concentrate on being an awesome headless browser, while the capybara driver
         
     | 
| 
      
 61 
     | 
    
         
            +
            (Poltergeist) is able to be the minimal amount of glue code necessary to drive the
         
     | 
| 
      
 62 
     | 
    
         
            +
            browser.
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
            ## License ##
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            Copyright (c) 2011 Jonathan Leighton
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 69 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 70 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 71 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 72 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 73 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 74 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 77 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 80 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 81 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 82 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 83 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 84 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 85 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
         @@ -0,0 +1,130 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Capybara::Poltergeist
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Browser
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :options, :server, :client
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @options = options
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @server  = Server.new
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @client  = Client.new(server.port, options[:phantomjs])
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def restart
         
     | 
| 
      
 14 
     | 
    
         
            +
                  server.restart
         
     | 
| 
      
 15 
     | 
    
         
            +
                  client.restart
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def visit(url, attributes = {})
         
     | 
| 
      
 19 
     | 
    
         
            +
                  command 'visit', url
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def current_url
         
     | 
| 
      
 23 
     | 
    
         
            +
                  command 'current_url'
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def body
         
     | 
| 
      
 27 
     | 
    
         
            +
                  command 'body'
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def source
         
     | 
| 
      
 31 
     | 
    
         
            +
                  command 'source'
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def find(selector, id = nil)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  command 'find', selector, id
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def text(id)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  command 'text', id
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                def attribute(id, name)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  command 'attribute', id, name
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def value(id)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  command 'value', id
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def set(id, value)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  command 'set', id, value
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def select_file(id, value)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  command 'select_file', id, value
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def tag_name(id)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  command('tag_name', id).downcase
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def visible?(id)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  command 'visible', id
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def evaluate(script)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  command 'evaluate', script
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def execute(script)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  command 'execute', script
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def within_frame(id, &block)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  command 'push_frame', id
         
     | 
| 
      
 76 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 77 
     | 
    
         
            +
                  command 'pop_frame'
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 81 
     | 
    
         
            +
                  visit('about:blank')
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def click(id)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  command 'click', id
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                def drag(id, other_id)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  command 'drag', id, other_id
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def select(id, value)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  command 'select', id, value
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                def trigger(id, event)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  command 'trigger', id, event
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 101 
     | 
    
         
            +
                  command 'reset'
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                def render(path)
         
     | 
| 
      
 105 
     | 
    
         
            +
                  command 'render', path
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                def logger
         
     | 
| 
      
 109 
     | 
    
         
            +
                  options[:logger]
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                def log(message)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  logger.puts message if logger
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def command(name, *args)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  message = { 'name' => name, 'args' => args }
         
     | 
| 
      
 118 
     | 
    
         
            +
                  log message.inspect
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                  json = JSON.parse(server.send(JSON.generate(message)))
         
     | 
| 
      
 121 
     | 
    
         
            +
                  log json.inspect
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                  if json['error']
         
     | 
| 
      
 124 
     | 
    
         
            +
                    raise BrowserError.new(json['error'])
         
     | 
| 
      
 125 
     | 
    
         
            +
                  else
         
     | 
| 
      
 126 
     | 
    
         
            +
                    json['response']
         
     | 
| 
      
 127 
     | 
    
         
            +
                  end
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
              end
         
     | 
| 
      
 130 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,175 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # This is injected into each page that is loaded
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class PoltergeistAgent
         
     | 
| 
      
 4 
     | 
    
         
            +
              constructor: ->
         
     | 
| 
      
 5 
     | 
    
         
            +
                @elements = []
         
     | 
| 
      
 6 
     | 
    
         
            +
                @nodes    = {}
         
     | 
| 
      
 7 
     | 
    
         
            +
                @windows  = []
         
     | 
| 
      
 8 
     | 
    
         
            +
                this.pushWindow(window)
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              pushWindow: (new_window) ->
         
     | 
| 
      
 11 
     | 
    
         
            +
                @windows.push(new_window)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                @window   = new_window
         
     | 
| 
      
 14 
     | 
    
         
            +
                @document = @window.document
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              popWindow: ->
         
     | 
| 
      
 17 
     | 
    
         
            +
                @windows.pop()
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                @window   = @windows[@windows.length - 1]
         
     | 
| 
      
 20 
     | 
    
         
            +
                @document = @window.document
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              pushFrame: (id) ->
         
     | 
| 
      
 23 
     | 
    
         
            +
                this.pushWindow @document.getElementById(id).contentWindow
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              popFrame: ->
         
     | 
| 
      
 26 
     | 
    
         
            +
                this.popWindow()
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              currentUrl: ->
         
     | 
| 
      
 29 
     | 
    
         
            +
                window.location.toString()
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              find: (selector, id) ->
         
     | 
| 
      
 32 
     | 
    
         
            +
                context = if id? then @elements[id] else @document
         
     | 
| 
      
 33 
     | 
    
         
            +
                results = @document.evaluate(selector, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
         
     | 
| 
      
 34 
     | 
    
         
            +
                ids     = []
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                for i in [0...results.snapshotLength]
         
     | 
| 
      
 37 
     | 
    
         
            +
                  ids.push(this.register(results.snapshotItem(i)))
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                ids
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              register: (element) ->
         
     | 
| 
      
 42 
     | 
    
         
            +
                @elements.push(element)
         
     | 
| 
      
 43 
     | 
    
         
            +
                @elements.length - 1
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
              documentSize: ->
         
     | 
| 
      
 46 
     | 
    
         
            +
                height: @document.documentElement.scrollHeight,
         
     | 
| 
      
 47 
     | 
    
         
            +
                width:  @document.documentElement.scrollWidth
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
              get: (id) ->
         
     | 
| 
      
 50 
     | 
    
         
            +
                @nodes[id] or= new PoltergeistAgent.Node(this, @elements[id])
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              nodeCall: (id, name, arguments) ->
         
     | 
| 
      
 53 
     | 
    
         
            +
                node = this.get(id)
         
     | 
| 
      
 54 
     | 
    
         
            +
                node[name].apply(node, arguments)
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            class PoltergeistAgent.Node
         
     | 
| 
      
 57 
     | 
    
         
            +
              @EVENTS = {
         
     | 
| 
      
 58 
     | 
    
         
            +
                FOCUS: ['blur', 'focus', 'focusin', 'focusout'],
         
     | 
| 
      
 59 
     | 
    
         
            +
                MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove',
         
     | 
| 
      
 60 
     | 
    
         
            +
                        'mouseover', 'mouseout', 'mouseup']
         
     | 
| 
      
 61 
     | 
    
         
            +
              }
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
              constructor: (@agent, @element) ->
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
              parentId: ->
         
     | 
| 
      
 66 
     | 
    
         
            +
                @agent.register(@element.parentNode)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
              isObsolete: ->
         
     | 
| 
      
 69 
     | 
    
         
            +
                obsolete = (element) =>
         
     | 
| 
      
 70 
     | 
    
         
            +
                  if element.parentNode?
         
     | 
| 
      
 71 
     | 
    
         
            +
                    if element.parentNode == @agent.document
         
     | 
| 
      
 72 
     | 
    
         
            +
                      false
         
     | 
| 
      
 73 
     | 
    
         
            +
                    else
         
     | 
| 
      
 74 
     | 
    
         
            +
                      obsolete element.parentNode
         
     | 
| 
      
 75 
     | 
    
         
            +
                  else
         
     | 
| 
      
 76 
     | 
    
         
            +
                    true
         
     | 
| 
      
 77 
     | 
    
         
            +
                obsolete @element
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              changed: ->
         
     | 
| 
      
 80 
     | 
    
         
            +
                event = document.createEvent('HTMLEvents')
         
     | 
| 
      
 81 
     | 
    
         
            +
                event.initEvent("change", true, false)
         
     | 
| 
      
 82 
     | 
    
         
            +
                @element.dispatchEvent(event)
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
              text: ->
         
     | 
| 
      
 85 
     | 
    
         
            +
                @element.textContent
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              getAttribute: (name) ->
         
     | 
| 
      
 88 
     | 
    
         
            +
                if name == 'checked' || name == 'selected'
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @element[name]
         
     | 
| 
      
 90 
     | 
    
         
            +
                else
         
     | 
| 
      
 91 
     | 
    
         
            +
                  @element.getAttribute(name)
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
              value: ->
         
     | 
| 
      
 94 
     | 
    
         
            +
                if @element.tagName == 'SELECT' && @element.multiple
         
     | 
| 
      
 95 
     | 
    
         
            +
                  option.value for option in @element.children when option.selected
         
     | 
| 
      
 96 
     | 
    
         
            +
                else
         
     | 
| 
      
 97 
     | 
    
         
            +
                  @element.value
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
              set: (value) ->
         
     | 
| 
      
 100 
     | 
    
         
            +
                if (@element.maxLength >= 0)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  value = value.substr(0, @element.maxLength)
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                @element.value = value
         
     | 
| 
      
 104 
     | 
    
         
            +
                this.changed()
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
              isMultiple: ->
         
     | 
| 
      
 107 
     | 
    
         
            +
                @element.multiple
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
              setAttribute: (name, value) ->
         
     | 
| 
      
 110 
     | 
    
         
            +
                @element.setAttribute(name, value)
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
              removeAttribute: (name) ->
         
     | 
| 
      
 113 
     | 
    
         
            +
                @element.removeAttribute(name)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
              select: (value) ->
         
     | 
| 
      
 116 
     | 
    
         
            +
                if value == false && !@element.parentNode.multiple
         
     | 
| 
      
 117 
     | 
    
         
            +
                  false
         
     | 
| 
      
 118 
     | 
    
         
            +
                else
         
     | 
| 
      
 119 
     | 
    
         
            +
                  @element.selected = value
         
     | 
| 
      
 120 
     | 
    
         
            +
                  this.changed()
         
     | 
| 
      
 121 
     | 
    
         
            +
                  true
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
              tagName: ->
         
     | 
| 
      
 124 
     | 
    
         
            +
                @element.tagName
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
              elementVisible: (element) ->
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
              isVisible: (id) ->
         
     | 
| 
      
 129 
     | 
    
         
            +
                visible = (element) ->
         
     | 
| 
      
 130 
     | 
    
         
            +
                  if @window.getComputedStyle(element).display == 'none'
         
     | 
| 
      
 131 
     | 
    
         
            +
                    false
         
     | 
| 
      
 132 
     | 
    
         
            +
                  else if element.parentElement
         
     | 
| 
      
 133 
     | 
    
         
            +
                    visible element.parentElement
         
     | 
| 
      
 134 
     | 
    
         
            +
                  else
         
     | 
| 
      
 135 
     | 
    
         
            +
                    true
         
     | 
| 
      
 136 
     | 
    
         
            +
                visible @element
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
              position: (id) ->
         
     | 
| 
      
 139 
     | 
    
         
            +
                pos = (element) ->
         
     | 
| 
      
 140 
     | 
    
         
            +
                  x = element.offsetLeft
         
     | 
| 
      
 141 
     | 
    
         
            +
                  y = element.offsetTop
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                  if element.offsetParent
         
     | 
| 
      
 144 
     | 
    
         
            +
                    parentPos = pos(element.offsetParent)
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                    x += parentPos.x
         
     | 
| 
      
 147 
     | 
    
         
            +
                    y += parentPos.y
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  { x: x, y: y }
         
     | 
| 
      
 150 
     | 
    
         
            +
                pos @element
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
              trigger: (name) ->
         
     | 
| 
      
 153 
     | 
    
         
            +
                if Node.EVENTS.MOUSE.indexOf(name) != -1
         
     | 
| 
      
 154 
     | 
    
         
            +
                  event = document.createEvent('MouseEvent')
         
     | 
| 
      
 155 
     | 
    
         
            +
                  event.initMouseEvent(
         
     | 
| 
      
 156 
     | 
    
         
            +
                    name, true, true, @agent.window, 0, 0, 0, 0, 0,
         
     | 
| 
      
 157 
     | 
    
         
            +
                    false, false, false, false, 0, null
         
     | 
| 
      
 158 
     | 
    
         
            +
                  )
         
     | 
| 
      
 159 
     | 
    
         
            +
                else if Node.EVENTS.FOCUS.indexOf(name) != -1
         
     | 
| 
      
 160 
     | 
    
         
            +
                  event = document.createEvent('HTMLEvents')
         
     | 
| 
      
 161 
     | 
    
         
            +
                  event.initEvent(name, true, true)
         
     | 
| 
      
 162 
     | 
    
         
            +
                else
         
     | 
| 
      
 163 
     | 
    
         
            +
                  throw "Unknown event"
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                @element.dispatchEvent(event)
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
            window.__poltergeist = new PoltergeistAgent
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
            document.addEventListener(
         
     | 
| 
      
 170 
     | 
    
         
            +
              'DOMContentLoaded',
         
     | 
| 
      
 171 
     | 
    
         
            +
              -> console.log('__DOMContentLoaded')
         
     | 
| 
      
 172 
     | 
    
         
            +
            )
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
            # Important to return true here - Phantom seems to choke otherwise
         
     | 
| 
      
 175 
     | 
    
         
            +
            true
         
     | 
| 
         @@ -0,0 +1,121 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Poltergeist.Browser
         
     | 
| 
      
 2 
     | 
    
         
            +
              constructor: (@owner) ->
         
     | 
| 
      
 3 
     | 
    
         
            +
                @awaiting_response = false
         
     | 
| 
      
 4 
     | 
    
         
            +
                this.resetPage()
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              resetPage: ->
         
     | 
| 
      
 7 
     | 
    
         
            +
                @page.release() if @page?
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                @page = new Poltergeist.WebPage
         
     | 
| 
      
 10 
     | 
    
         
            +
                @page.onLoadFinished = (status) =>
         
     | 
| 
      
 11 
     | 
    
         
            +
                  if @awaiting_response
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @owner.sendResponse(status)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @awaiting_response = false
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              visit: (url) ->
         
     | 
| 
      
 16 
     | 
    
         
            +
                @awaiting_response = true
         
     | 
| 
      
 17 
     | 
    
         
            +
                @page.open(url)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              current_url: ->
         
     | 
| 
      
 20 
     | 
    
         
            +
                @owner.sendResponse @page.currentUrl()
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              body: ->
         
     | 
| 
      
 23 
     | 
    
         
            +
                @owner.sendResponse @page.content()
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              source: ->
         
     | 
| 
      
 26 
     | 
    
         
            +
                @owner.sendResponse @page.source()
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              find: (selector, id) ->
         
     | 
| 
      
 29 
     | 
    
         
            +
                @owner.sendResponse @page.find(selector, id)
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              text: (id) ->
         
     | 
| 
      
 32 
     | 
    
         
            +
                @owner.sendResponse @page.get(id).text()
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              attribute: (id, name) ->
         
     | 
| 
      
 35 
     | 
    
         
            +
                @owner.sendResponse @page.get(id).getAttribute(name)
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              value: (id) ->
         
     | 
| 
      
 38 
     | 
    
         
            +
                @owner.sendResponse @page.get(id).value()
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              set: (id, value) ->
         
     | 
| 
      
 41 
     | 
    
         
            +
                @page.get(id).set(value)
         
     | 
| 
      
 42 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              # PhantomJS only allows us to reference the element by CSS selector, not XPath,
         
     | 
| 
      
 45 
     | 
    
         
            +
              # so we have to add an attribute to the element to identify it, then remove it
         
     | 
| 
      
 46 
     | 
    
         
            +
              # afterwards.
         
     | 
| 
      
 47 
     | 
    
         
            +
              #
         
     | 
| 
      
 48 
     | 
    
         
            +
              # PhantomJS does not support multiple-file inputs, so we have to blatently cheat
         
     | 
| 
      
 49 
     | 
    
         
            +
              # by temporarily changing it to a single-file input. This obviously could break
         
     | 
| 
      
 50 
     | 
    
         
            +
              # things in various ways, which is not ideal, but it works in the simplest case.
         
     | 
| 
      
 51 
     | 
    
         
            +
              select_file: (id, value) ->
         
     | 
| 
      
 52 
     | 
    
         
            +
                element = @page.get(id)
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                multiple = element.isMultiple()
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                element.removeAttribute('multiple') if multiple
         
     | 
| 
      
 57 
     | 
    
         
            +
                element.setAttribute('_poltergeist_selected', '')
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                @page.uploadFile('[_poltergeist_selected]', value)
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                element.removeAttribute('_poltergeist_selected')
         
     | 
| 
      
 62 
     | 
    
         
            +
                element.setAttribute('multiple', 'multiple') if multiple
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
              select: (id, value) ->
         
     | 
| 
      
 67 
     | 
    
         
            +
                @owner.sendResponse @page.get(id).select(value)
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              tag_name: (id) ->
         
     | 
| 
      
 70 
     | 
    
         
            +
                @owner.sendResponse @page.get(id).tagName()
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              visible: (id) ->
         
     | 
| 
      
 73 
     | 
    
         
            +
                @owner.sendResponse @page.get(id).isVisible()
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
              evaluate: (script) ->
         
     | 
| 
      
 76 
     | 
    
         
            +
                @owner.sendResponse @page.evaluate("function() { return #{script} }")
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              execute: (script) ->
         
     | 
| 
      
 79 
     | 
    
         
            +
                @page.execute("function() { return #{script} }")
         
     | 
| 
      
 80 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
              push_frame: (id) ->
         
     | 
| 
      
 83 
     | 
    
         
            +
                @page.pushFrame(id)
         
     | 
| 
      
 84 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              pop_frame: ->
         
     | 
| 
      
 87 
     | 
    
         
            +
                @page.popFrame()
         
     | 
| 
      
 88 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
              click: (id) ->
         
     | 
| 
      
 91 
     | 
    
         
            +
                # Detect if the click event triggers a page load. If it does, don't send
         
     | 
| 
      
 92 
     | 
    
         
            +
                # a response here, because the response will be sent once the page has loaded.
         
     | 
| 
      
 93 
     | 
    
         
            +
                @page.onLoadStarted = => @awaiting_response = true
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                @page.get(id).click()
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                # Use a timeout in order to let the stack clear, so that the @page.onLoadStarted
         
     | 
| 
      
 98 
     | 
    
         
            +
                # callback can (possibly) fire, before we decide whether to send a response.
         
     | 
| 
      
 99 
     | 
    
         
            +
                setTimeout(
         
     | 
| 
      
 100 
     | 
    
         
            +
                  =>
         
     | 
| 
      
 101 
     | 
    
         
            +
                    @page.onLoadStarted = null
         
     | 
| 
      
 102 
     | 
    
         
            +
                    @owner.sendResponse(true) unless @awaiting_response
         
     | 
| 
      
 103 
     | 
    
         
            +
                  ,
         
     | 
| 
      
 104 
     | 
    
         
            +
                  0
         
     | 
| 
      
 105 
     | 
    
         
            +
                )
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
              drag: (id, other_id) ->
         
     | 
| 
      
 108 
     | 
    
         
            +
                @page.get(id).dragTo(@page.get(other_id))
         
     | 
| 
      
 109 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
              trigger: (id, event) ->
         
     | 
| 
      
 112 
     | 
    
         
            +
                @page.get(id).trigger(event)
         
     | 
| 
      
 113 
     | 
    
         
            +
                @owner.sendResponse(event)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
              reset: ->
         
     | 
| 
      
 116 
     | 
    
         
            +
                this.resetPage()
         
     | 
| 
      
 117 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
              render: (path) ->
         
     | 
| 
      
 120 
     | 
    
         
            +
                @page.render(path)
         
     | 
| 
      
 121 
     | 
    
         
            +
                @owner.sendResponse(true)
         
     |