and HTML attributes.
+ * See the @link forms_api_reference.html Form API reference @endlink for more
+ * information on the #theme_wrappers render array property.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
diff --git a/core/themes/seven/css/base/elements.css b/core/themes/seven/css/base/elements.css
index f3167ed33..76fcc4c71 100644
--- a/core/themes/seven/css/base/elements.css
+++ b/core/themes/seven/css/base/elements.css
@@ -137,6 +137,10 @@ ul {
margin-left: 0;
margin-right: 1.5em;
}
+/* This is required to win over specificity of [dir="rtl"] ul */
+[dir="rtl"] .messages__list {
+ margin-right: 0;
+}
ol {
list-style-type: decimal;
margin: 0.25em 0 0.25em 2em; /* LTR */
diff --git a/core/themes/seven/css/components/dialog.css b/core/themes/seven/css/components/dialog.css
index d104c82f0..99aaa5393 100644
--- a/core/themes/seven/css/components/dialog.css
+++ b/core/themes/seven/css/components/dialog.css
@@ -19,7 +19,11 @@
background: #6b6b6b;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
- padding: 15px 49px 15px 15px;
+ padding: 15px 49px 15px 15px; /* LTR */
+}
+[dir="rtl"] .ui-dialog .ui-dialog-titlebar {
+ padding-left: 49px;
+ padding-right: 15px;
}
.ui-dialog .ui-dialog-title {
font-size: 1.231em;
@@ -103,4 +107,3 @@
.ui-dialog .ajax-progress-throbber .message {
display: none;
}
-
diff --git a/core/themes/seven/css/components/dropbutton.component.css b/core/themes/seven/css/components/dropbutton.component.css
index 6f085020f..229f1bdd0 100644
--- a/core/themes/seven/css/components/dropbutton.component.css
+++ b/core/themes/seven/css/components/dropbutton.component.css
@@ -18,10 +18,11 @@
-webkit-font-smoothing: antialiased;
text-align: left; /* LTR */
}
-[dir="rtl"] .js .dropbutton .dropbutton-action > input,
-[dir="rtl"] .js .dropbutton .dropbutton-action > a,
-[dir="rtl"] .js .dropbutton .dropbutton-action > button {
+[dir="rtl"].js .dropbutton .dropbutton-action > input,
+[dir="rtl"].js .dropbutton .dropbutton-action > a,
+[dir="rtl"].js .dropbutton .dropbutton-action > button {
text-align: right;
+ margin-left: 0; /* This is required to win over specificity of [dir="rtl"] .dropbutton-multiple .dropbutton .dropbutton-action > * */
}
.js .dropbutton-action.last {
border-radius: 0 0 0 1em; /* LTR */
diff --git a/core/themes/seven/css/components/menus-and-lists.css b/core/themes/seven/css/components/menus-and-lists.css
index d96fe5516..3507e09b0 100644
--- a/core/themes/seven/css/components/menus-and-lists.css
+++ b/core/themes/seven/css/components/menus-and-lists.css
@@ -34,6 +34,7 @@ ul.inline li {
[dir="rtl"] ul.links li,
[dir="rtl"] ul.inline li {
padding-left: 1em;
+ padding-right: 0;
}
ul.inline li {
display: inline;
diff --git a/core/themes/seven/css/components/tables.css b/core/themes/seven/css/components/tables.css
index afc9301bd..3bdf7cc63 100644
--- a/core/themes/seven/css/components/tables.css
+++ b/core/themes/seven/css/components/tables.css
@@ -108,6 +108,10 @@ th.is-active > a:focus:after {
td .item-list ul {
margin: 0;
}
+/* This is required to win over specificity of [dir="rtl"] .item-list ul */
+[dir="rtl"] td .item-list ul {
+ margin: 0;
+}
td.is-active {
background: none;
}
diff --git a/core/themes/seven/css/components/tabs.css b/core/themes/seven/css/components/tabs.css
index 23c4a48de..28401b425 100644
--- a/core/themes/seven/css/components/tabs.css
+++ b/core/themes/seven/css/components/tabs.css
@@ -257,6 +257,12 @@ li.tabs__tab a {
margin-left: 0;
margin-right: -1px;
}
+/* This is required to win over specificity of [dir="rtl"] .tabs.secondary .tabs__tab */
+[dir="rtl"] .views-displays .tabs.secondary li,
+[dir="rtl"] .views-displays .tabs.secondary li.is-active {
+ padding-left: 0;
+ padding-right: 0;
+}
.tabs.secondary .tabs__tab + .tabs__tab {
border-top: 1px solid #d9d8d4;
}
@@ -270,6 +276,11 @@ li.tabs__tab a {
border-right: 2px solid #004f80;
padding-right: 15px;
}
+/* This is required to win over specificity of [dir="rtl"] .tabs.secondary .tabs__tab.is-active */
+[dir="rtl"] .views-displays .tabs.secondary li.is-active {
+ border: 0 none;
+ padding-right: 0;
+}
.tabs.secondary .tabs__tab:hover,
.tabs.secondary .tabs__tab:focus {
color: #008ee6;
@@ -282,11 +293,26 @@ li.tabs__tab a {
border-right: 2px solid #008ee6;
padding-right: 15px;
}
+/* This is required to win over specificity of [dir="rtl"] .tabs.secondary .tabs__tab:hover */
+[dir="rtl"] .views-displays .tabs li.tabs__tab:hover {
+ border: 0 none;
+ padding-right: 0;
+}
.tabs.secondary a {
background-color: transparent;
padding: 7px 13px 5px;
text-decoration: none;
}
+/* This is required to win over specificity of [dir="rtl"] li.tabs__tab a */
+[dir="rtl"] .tabs.secondary a {
+ padding-left: 13px;
+ padding-right: 13px;
+}
+/* This is required to win over specificity of [dir="rtl"] .tabs.secondary a */
+[dir="rtl"] .views-displays .tabs.secondary a {
+ padding-left: 7px;
+ padding-right: 7px;
+}
.tabs.secondary .is-active a {
color: #004f80;
}
diff --git a/core/themes/seven/css/components/views-ui.css b/core/themes/seven/css/components/views-ui.css
index 8c4afda11..bfd4575c4 100644
--- a/core/themes/seven/css/components/views-ui.css
+++ b/core/themes/seven/css/components/views-ui.css
@@ -43,6 +43,13 @@ details.fieldset-no-legend {
[dir="rtl"] .views-admin a.button,
[dir="rtl"] .views-ui-dialog a.button {
margin-left: 0;
+ margin-right: 1em;
+}
+[dir="rtl"] .views-admin input.form-submit:first-child,
+[dir="rtl"] .views-ui-dialog input.form-submit:first-child,
+[dir="rtl"] .views-admin a.button:first-child,
+[dir="rtl"] .views-ui-dialog a.button:first-child {
+ margin-right: 0;
}
.form-radios > .form-item {
diff --git a/core/themes/seven/css/theme/maintenance-page.css b/core/themes/seven/css/theme/maintenance-page.css
index 678434365..3725ac64d 100644
--- a/core/themes/seven/css/theme/maintenance-page.css
+++ b/core/themes/seven/css/theme/maintenance-page.css
@@ -165,6 +165,9 @@
padding: 15px;
margin: 0.25em 0;
}
+ [dir="rtl"] ul {
+ margin-right: 0; /* Overrides default [dir="rtl"] ul margin */
+ }
.layout-sidebar-first {
float: left; /* LTR */
width: 35%;
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 7f180ab97..0d573e496 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -223,22 +223,24 @@ $databases = array();
* Location of the site configuration files.
*
* The $config_directories array specifies the location of file system
- * directories used for configuration data. On install, "active" and "sync"
- * directories are created for configuration. The sync directory is used for
- * configuration imports; the active directory is not used by default, since the
- * default storage for active configuration is the database rather than the file
- * system (this can be changed; see "Active configuration settings" below).
+ * directories used for configuration data. On install, the "sync" directory is
+ * created. This is used for configuration imports. The "active" directory is
+ * not created by default since the default storage for active configuration is
+ * the database rather than the file system. (This can be changed. See "Active
+ * configuration settings" below).
*
- * The default location for the active and sync directories is inside a
- * randomly-named directory in the public files path; this setting allows you to
- * override these locations. If you use files for the active configuration, you
- * can enhance security by putting the active configuration outside your
- * document root.
+ * The default location for the "sync" directory is inside a randomly-named
+ * directory in the public files path. The setting below allows you to override
+ * the "sync" location.
+ *
+ * If you use files for the "active" configuration, you can tell the
+ * Configuration system where this directory is located by adding an entry with
+ * array key CONFIG_ACTIVE_DIRECTORY.
*
* Example:
* @code
* $config_directories = array(
- * CONFIG_SYNC_DIRECTORY => '/another/directory/outside/webroot',
+ * CONFIG_SYNC_DIRECTORY => '/directory/outside/webroot',
* );
* @endcode
*/
@@ -580,6 +582,10 @@ if ($settings['hash_salt']) {
* By default, the active configuration is stored in the database in the
* {config} table. To use a different storage mechanism for the active
* configuration, do the following prior to installing:
+ * - Create an "active" directory and declare its path in $config_directories
+ * as explained under the 'Location of the site configuration files' section
+ * above in this file. To enhance security, you can declare a path that is
+ * outside your document root.
* - Override the 'bootstrap_config_storage' setting here. It must be set to a
* callable that returns an object that implements
* \Drupal\Core\Config\StorageInterface.
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index 8d5b5ef27..030a0de76 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -6,6 +6,8 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
+ 'Zumba\\Mink\\Driver\\' => array($vendorDir . '/jcalderonzumba/mink-phantomjs-driver/src'),
+ 'Zumba\\GastonJS\\' => array($vendorDir . '/jcalderonzumba/gastonjs/src'),
'Zend\\Stdlib\\' => array($vendorDir . '/zendframework/zend-stdlib/src'),
'Zend\\Hydrator\\' => array($vendorDir . '/zendframework/zend-hydrator/src'),
'Zend\\Feed\\' => array($vendorDir . '/zendframework/zend-feed/src'),
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 4c4df5b1c..af7346c27 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -107,7 +107,7 @@
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/47bb3388cfeae41a38087ac8465a7d08fa92ea2e",
+ "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/6196fdb001faf681f92db2ae10abafb5815affde",
"reference": "47bb3388cfeae41a38087ac8465a7d08fa92ea2e",
"shasum": ""
},
@@ -3757,5 +3757,127 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com"
+ },
+ {
+ "name": "jcalderonzumba/gastonjs",
+ "version": "dev-master",
+ "version_normalized": "9999999-dev",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jcalderonzumba/gastonjs.git",
+ "reference": "5e231b4df98275c404e1371fc5fadd34f6a121ad"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jcalderonzumba/gastonjs/zipball/5e231b4df98275c404e1371fc5fadd34f6a121ad",
+ "reference": "5e231b4df98275c404e1371fc5fadd34f6a121ad",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "~5.0|~6.0",
+ "php": ">=5.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.6",
+ "silex/silex": "~1.2",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/process": "~2.1"
+ },
+ "time": "2015-10-07 11:40:41",
+ "type": "phantomjs-api",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "source",
+ "autoload": {
+ "psr-4": {
+ "Zumba\\GastonJS\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Juan Francisco Calderón Zumba",
+ "email": "juanfcz@gmail.com",
+ "homepage": "http://github.com/jcalderonzumba"
+ }
+ ],
+ "description": "PhantomJS API based server for webpage automation",
+ "homepage": "https://github.com/jcalderonzumba/gastonjs",
+ "keywords": [
+ "api",
+ "automation",
+ "browser",
+ "headless",
+ "phantomjs"
+ ]
+ },
+ {
+ "name": "jcalderonzumba/mink-phantomjs-driver",
+ "version": "dev-master",
+ "version_normalized": "9999999-dev",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jcalderonzumba/MinkPhantomJSDriver.git",
+ "reference": "10d7c48c9a4129463052321b52450d98983c4332"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jcalderonzumba/MinkPhantomJSDriver/zipball/10d7c48c9a4129463052321b52450d98983c4332",
+ "reference": "10d7c48c9a4129463052321b52450d98983c4332",
+ "shasum": ""
+ },
+ "require": {
+ "behat/mink": "~1.6",
+ "jcalderonzumba/gastonjs": "~1.0",
+ "php": ">=5.4",
+ "twig/twig": "~1.8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.6",
+ "silex/silex": "~1.2",
+ "symfony/css-selector": "~2.1",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/process": "~2.3"
+ },
+ "time": "2015-10-05 18:24:44",
+ "type": "mink-driver",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.4.x-dev"
+ }
+ },
+ "installation-source": "source",
+ "autoload": {
+ "psr-4": {
+ "Zumba\\Mink\\Driver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Juan Francisco Calderón Zumba",
+ "email": "juanfcz@gmail.com",
+ "homepage": "http://github.com/jcalderonzumba"
+ }
+ ],
+ "description": "PhantomJS driver for Mink framework",
+ "homepage": "http://mink.behat.org/",
+ "keywords": [
+ "ajax",
+ "browser",
+ "headless",
+ "javascript",
+ "phantomjs",
+ "testing"
+ ]
}
]
diff --git a/vendor/jcalderonzumba/gastonjs/.travis.yml b/vendor/jcalderonzumba/gastonjs/.travis.yml
new file mode 100644
index 000000000..c64d75510
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/.travis.yml
@@ -0,0 +1,37 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - hhvm
+
+matrix:
+ fast_finish: true
+ include:
+ - php: 5.4
+ env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' SYMFONY_DEPRECATIONS_HELPER=weak
+ - php: 5.6
+ env: DEPENDENCIES=dev
+ allow_failures:
+ - php: 7.0
+ - php: hhvm
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+before_install:
+ - composer self-update
+ - if [ "$DEPENDENCIES" = "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi;
+
+install:
+ - composer update $COMPOSER_FLAGS
+
+script:
+ - bin/run-tests.sh
+
+after_script:
+ - ps axo pid,command | grep phantomjs | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
+ - ps axo pid,command | grep php | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
diff --git a/vendor/jcalderonzumba/gastonjs/LICENSE b/vendor/jcalderonzumba/gastonjs/LICENSE
new file mode 100644
index 000000000..7ba018acd
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Juan Francisco Calderón Zumba
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/jcalderonzumba/gastonjs/README.md b/vendor/jcalderonzumba/gastonjs/README.md
new file mode 100644
index 000000000..4f8e83d57
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/README.md
@@ -0,0 +1,8 @@
+GastonJS for Webpage automation
+================================
+[![Build Status](https://travis-ci.org/jcalderonzumba/gastonjs.svg?branch=travis_ci)](https://travis-ci.org/jcalderonzumba/gastonjs)
+[![Latest Stable Version](https://poser.pugx.org/jcalderonzumba/gastonjs/v/stable)](https://packagist.org/packages/jcalderonzumba/gastonjs)
+[![Total Downloads](https://poser.pugx.org/jcalderonzumba/gastonjs/downloads)](https://packagist.org/packages/jcalderonzumba/gastonjs)
+
+
+For full documentation go to [GastonJS doc](http://gastonjs.readthedocs.org/en/latest/)
diff --git a/vendor/jcalderonzumba/gastonjs/bin/run-tests.sh b/vendor/jcalderonzumba/gastonjs/bin/run-tests.sh
new file mode 100755
index 000000000..426ec94f1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/bin/run-tests.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+set -e
+
+start_browser_api(){
+ CURRENT_DIR=$(pwd)
+ LOCAL_PHANTOMJS="${CURRENT_DIR}/bin/phantomjs"
+ if [ -f ${LOCAL_PHANTOMJS} ]; then
+ ${LOCAL_PHANTOMJS} --ssl-protocol=any --ignore-ssl-errors=true src/Client/main.js 8510 1024 768 2>&1 &
+ else
+ phantomjs --ssl-protocol=any --ignore-ssl-errors=true src/Client/main.js 8510 1024 768 2>&1 >> /dev/null &
+ fi
+ sleep 2
+}
+
+stop_services(){
+ ps axo pid,command | grep phantomjs | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
+ ps axo pid,command | grep php | grep -v grep | grep -v phpstorm | awk '{print $1}' | xargs -I {} kill {}
+ sleep 2
+}
+
+mkdir -p /tmp/jcalderonzumba/phantomjs
+stop_services
+start_browser_api
+CURRENT_DIR=$(pwd)
+${CURRENT_DIR}/bin/phpunit --configuration unit_tests.xml
+
diff --git a/vendor/jcalderonzumba/gastonjs/composer.json b/vendor/jcalderonzumba/gastonjs/composer.json
new file mode 100644
index 000000000..7e5d96e6a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/composer.json
@@ -0,0 +1,49 @@
+{
+ "name": "jcalderonzumba/gastonjs",
+ "description": "PhantomJS API based server for webpage automation",
+ "keywords": [
+ "phantomjs",
+ "headless",
+ "api",
+ "automation",
+ "browser"
+ ],
+ "homepage": "https://github.com/jcalderonzumba/gastonjs",
+ "type": "phantomjs-api",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Juan Francisco Calderón Zumba",
+ "email": "juanfcz@gmail.com",
+ "homepage": "http://github.com/jcalderonzumba"
+ }
+ ],
+ "require": {
+ "php": ">=5.4",
+ "guzzlehttp/guzzle": "~5.0|~6.0"
+ },
+ "require-dev": {
+ "symfony/process": "~2.1",
+ "symfony/phpunit-bridge": "~2.7",
+ "phpunit/phpunit": "~4.6",
+ "silex/silex": "~1.2"
+ },
+ "config": {
+ "bin-dir": "bin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Zumba\\GastonJS\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Zumba\\GastonJS\\Tests\\": "tests/unit"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/mkdocs.yml b/vendor/jcalderonzumba/gastonjs/mkdocs.yml
new file mode 100644
index 000000000..51d881f57
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/mkdocs.yml
@@ -0,0 +1,40 @@
+site_name: GastonJS Documentation
+pages:
+ - GastonJS introduction: index.md
+ - GastonJS API :
+ - 'Introduction': api/index.md
+ - 'Available commands': api/command-list.md
+ - 'Navigation commands':
+ - 'visit': api/commands/navigation/visit.md
+ - 'current_url': api/commands/navigation/current_url.md
+ - 'reload': api/commands/navigation/reload.md
+ - 'go_back': api/commands/navigation/go_back.md
+ - 'go_forward': api/commands/navigation/go_forward.md
+ - 'Header commands' :
+ - 'get_headers': api/commands/headers/get_headers.md
+ - 'response_headers': api/commands/headers/response_headers.md
+ - 'set_headers': api/commands/headers/set_headers.md
+ - 'add_headers': api/commands/headers/add_headers.md
+ - 'add_header': api/commands/headers/add_header.md
+ - 'Javascript commands' :
+ - 'add_extension': api/commands/javascript/add_extension.md
+ - 'execute': api/commands/javascript/execute.md
+ - 'evaluate': api/commands/javascript/evaluate.md
+ - 'set_js_errors': api/commands/javascript/set_js_errors.md
+ - 'Cookies commands' :
+ - 'cookies': api/commands/cookies/cookies.md
+ - 'clear_cookies': api/commands/cookies/clear_cookies.md
+ - 'cookies_enabled': api/commands/cookies/cookies_enabled.md
+ - 'remove_cookie': api/commands/cookies/remove_cookie.md
+ - 'set_cookie': api/commands/cookies/set_cookie.md
+ - 'Mouse commands':
+ - 'click': api/commands/mouse/click.md
+ - 'right_click': api/commands/mouse/right_click.md
+ - 'hover': api/commands/mouse/hover.md
+ - 'double_click': api/commands/mouse/double_click.md
+ - 'click_coordinates': api/commands/mouse/click_coordinates.md
+ - 'mouse_event': api/commands/mouse/mouse_event.md
+ - 'Render commands':
+ - 'render': api/commands/render/render.md
+ - 'render_base64': api/commands/render/render_base64.md
+ - GastonJS PHP client: clients/php/index.md
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/Browser.php b/vendor/jcalderonzumba/gastonjs/src/Browser/Browser.php
new file mode 100644
index 000000000..5c2a3374e
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/Browser.php
@@ -0,0 +1,120 @@
+phantomJSHost = $phantomJSHost;
+ $this->logger = $logger;
+ $this->debug = false;
+ $this->createApiClient();
+ }
+
+ /**
+ * Returns the value of a given element in a page
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function value($pageId, $elementId) {
+ return $this->command('value', $pageId, $elementId);
+ }
+
+ /**
+ * Sets a value to a given element in a given page
+ * @param $pageId
+ * @param $elementId
+ * @param $value
+ * @return mixed
+ */
+ public function set($pageId, $elementId, $value) {
+ return $this->command('set', $pageId, $elementId, $value);
+ }
+
+ /**
+ * Tells whether an element on a page is visible or not
+ * @param $pageId
+ * @param $elementId
+ * @return bool
+ */
+ public function isVisible($pageId, $elementId) {
+ return $this->command('visible', $pageId, $elementId);
+ }
+
+ /**
+ * @param $pageId
+ * @param $elementId
+ * @return bool
+ */
+ public function isDisabled($pageId, $elementId) {
+ return $this->command('disabled', $pageId, $elementId);
+ }
+
+ /**
+ * Drag an element to a another in a given page
+ * @param $pageId
+ * @param $fromId
+ * @param $toId
+ * @return mixed
+ */
+ public function drag($pageId, $fromId, $toId) {
+ return $this->command('drag', $pageId, $fromId, $toId);
+ }
+
+ /**
+ * Selects a value in the given element and page
+ * @param $pageId
+ * @param $elementId
+ * @param $value
+ * @return mixed
+ */
+ public function select($pageId, $elementId, $value) {
+ return $this->command('select', $pageId, $elementId, $value);
+ }
+
+ /**
+ * Triggers an event to a given element on the given page
+ * @param $pageId
+ * @param $elementId
+ * @param $event
+ * @return mixed
+ */
+ public function trigger($pageId, $elementId, $event) {
+ return $this->command('trigger', $pageId, $elementId, $event);
+ }
+
+ /**
+ * TODO: not sure what this does, needs to do normalizeKeys
+ * @param int $pageId
+ * @param int $elementId
+ * @param array $keys
+ * @return mixed
+ */
+ public function sendKeys($pageId, $elementId, $keys) {
+ return $this->command('send_keys', $pageId, $elementId, $this->normalizeKeys($keys));
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserAuthenticationTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserAuthenticationTrait.php
new file mode 100644
index 000000000..7416a76e5
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserAuthenticationTrait.php
@@ -0,0 +1,19 @@
+command('set_http_auth', $user, $password);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserBase.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserBase.php
new file mode 100644
index 000000000..3ef14d0d7
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserBase.php
@@ -0,0 +1,124 @@
+apiClient = new Client(array("base_uri" => $this->getPhantomJSHost()));
+ }
+ else {
+ $this->apiClient = new Client(array("base_url" => $this->getPhantomJSHost()));
+ }
+ }
+
+ /**
+ * TODO: not sure how to do the normalizeKeys stuff fix when needed
+ * @param $keys
+ * @return mixed
+ */
+ protected function normalizeKeys($keys) {
+ return $keys;
+ }
+
+ /**
+ * @return Client
+ */
+ public function getApiClient() {
+ return $this->apiClient;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPhantomJSHost() {
+ return $this->phantomJSHost;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getLogger() {
+ return $this->logger;
+ }
+
+ /**
+ * Restarts the browser
+ */
+ public function restart() {
+ //TODO: Do we really need to do this?, we are just a client
+ }
+
+ /**
+ * Sends a command to the browser
+ * @throws BrowserError
+ * @throws \Exception
+ * @return mixed
+ */
+ public function command() {
+ try {
+ $args = func_get_args();
+ $commandName = $args[0];
+ array_shift($args);
+ $messageToSend = json_encode(array('name' => $commandName, 'args' => $args));
+ /** @var $commandResponse \GuzzleHttp\Psr7\Response|\GuzzleHttp\Message\Response */
+ $commandResponse = $this->getApiClient()->post("/api", array("body" => $messageToSend));
+ $jsonResponse = json_decode($commandResponse->getBody(), TRUE);
+ } catch (ServerException $e) {
+ $jsonResponse = json_decode($e->getResponse()->getBody()->getContents(), true);
+ } catch (ConnectException $e) {
+ throw new DeadClient($e->getMessage(), $e->getCode(), $e);
+ } catch (\Exception $e) {
+ throw $e;
+ }
+
+ if (isset($jsonResponse['error'])) {
+ throw $this->getErrorClass($jsonResponse);
+ }
+
+ return $jsonResponse['response'];
+ }
+
+ /**
+ * @param $error
+ * @return BrowserError
+ */
+ protected function getErrorClass($error) {
+ $errorClassMap = array(
+ 'Poltergeist.JavascriptError' => "Zumba\\GastonJS\\Exception\\JavascriptError",
+ 'Poltergeist.FrameNotFound' => "Zumba\\GastonJS\\Exception\\FrameNotFound",
+ 'Poltergeist.InvalidSelector' => "Zumba\\GastonJS\\Exception\\InvalidSelector",
+ 'Poltergeist.StatusFailError' => "Zumba\\GastonJS\\Exception\\StatusFailError",
+ 'Poltergeist.NoSuchWindowError' => "Zumba\\GastonJS\\Exception\\NoSuchWindowError",
+ 'Poltergeist.ObsoleteNode' => "Zumba\\GastonJS\\Exception\\ObsoleteNode"
+ );
+ if (isset($error['error']['name']) && isset($errorClassMap[$error["error"]["name"]])) {
+ return new $errorClassMap[$error["error"]["name"]]($error);
+ }
+
+ return new BrowserError($error);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserConfigurationTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserConfigurationTrait.php
new file mode 100644
index 000000000..0db7f07ac
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserConfigurationTrait.php
@@ -0,0 +1,39 @@
+command('set_js_errors', $enabled);
+ }
+
+ /**
+ * Set a blacklist of urls that we are not supposed to load
+ * @param array $blackList
+ * @return bool
+ */
+ public function urlBlacklist($blackList) {
+ return $this->command('set_url_blacklist', $blackList);
+ }
+
+ /**
+ * Set the debug mode on the browser
+ * @param bool $enable
+ * @return bool
+ */
+ public function debug($enable = false) {
+ $this->debug = $enable;
+ return $this->command('set_debug', $this->debug);
+ }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserCookieTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserCookieTrait.php
new file mode 100644
index 000000000..e2318a4fa
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserCookieTrait.php
@@ -0,0 +1,65 @@
+command('cookies');
+ $objCookies = array();
+ foreach ($cookies as $cookie) {
+ $objCookies[$cookie["name"]] = new Cookie($cookie);
+ }
+ return $objCookies;
+ }
+
+ /**
+ * Sets a cookie on the browser, expires times is set in seconds
+ * @param $cookie
+ * @return mixed
+ */
+ public function setCookie($cookie) {
+ //TODO: add error control when the cookie array is not valid
+ if (isset($cookie["expires"])) {
+ $cookie["expires"] = intval($cookie["expires"]) * 1000;
+ }
+ $cookie['value'] = urlencode($cookie['value']);
+ return $this->command('set_cookie', $cookie);
+ }
+
+ /**
+ * Deletes a cookie on the browser if exists
+ * @param $cookieName
+ * @return bool
+ */
+ public function removeCookie($cookieName) {
+ return $this->command('remove_cookie', $cookieName);
+ }
+
+ /**
+ * Clear all the cookies
+ * @return bool
+ */
+ public function clearCookies() {
+ return $this->command('clear_cookies');
+ }
+
+ /**
+ * Enables or disables the cookies con phantomjs
+ * @param bool $enabled
+ * @return bool
+ */
+ public function cookiesEnabled($enabled = true) {
+ return $this->command('cookies_enabled', $enabled);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFileTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFileTrait.php
new file mode 100644
index 000000000..51fc745f6
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFileTrait.php
@@ -0,0 +1,20 @@
+command('select_file', $pageId, $elementId, $value);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFrameTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFrameTrait.php
new file mode 100644
index 000000000..edefe5eb1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFrameTrait.php
@@ -0,0 +1,31 @@
+command("pop_frame");
+ }
+
+ /**
+ * Goes into the iframe to do stuff
+ * @param string $name
+ * @param int $timeout
+ * @return mixed
+ * @throws \Zumba\GastonJS\Exception\BrowserError
+ * @throws \Exception
+ */
+ public function pushFrame($name, $timeout = null) {
+ return $this->command("push_frame", $name, $timeout);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserHeadersTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserHeadersTrait.php
new file mode 100644
index 000000000..8300048a8
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserHeadersTrait.php
@@ -0,0 +1,53 @@
+command('get_headers');
+ }
+
+ /**
+ * Given an array of headers, set such headers for the requests, removing all others
+ * @param array $headers
+ * @return mixed
+ */
+ public function setHeaders($headers) {
+ return $this->command('set_headers', $headers);
+ }
+
+ /**
+ * Adds headers to current page overriding the existing ones for the next requests
+ * @param $headers
+ * @return mixed
+ */
+ public function addHeaders($headers) {
+ return $this->command('add_headers', $headers);
+ }
+
+ /**
+ * Adds a header to the page making it permanent if needed
+ * @param $header
+ * @param $permanent
+ * @return mixed
+ */
+ public function addHeader($header, $permanent = false) {
+ return $this->command('add_header', $header, $permanent);
+ }
+
+ /**
+ * Gets the response headers after a request
+ * @return mixed
+ */
+ public function responseHeaders() {
+ return $this->command('response_headers');
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserMouseEventTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserMouseEventTrait.php
new file mode 100644
index 000000000..38ec5a60c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserMouseEventTrait.php
@@ -0,0 +1,69 @@
+command('click', $pageId, $elementId);
+ }
+
+ /**
+ * Triggers a right click on a page an element
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function rightClick($pageId, $elementId) {
+ return $this->command('right_click', $pageId, $elementId);
+ }
+
+ /**
+ * Triggers a double click in a given page and element
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function doubleClick($pageId, $elementId) {
+ return $this->command('double_click', $pageId, $elementId);
+ }
+
+ /**
+ * Hovers over an element in a given page
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function hover($pageId, $elementId) {
+ return $this->command('hover', $pageId, $elementId);
+ }
+
+ /**
+ * Click on given coordinates, THIS DOES NOT depend on the page, it just clicks on where we are right now
+ * @param $coordX
+ * @param $coordY
+ * @return mixed
+ */
+ public function clickCoordinates($coordX, $coordY) {
+ return $this->command('click_coordinates', $coordX, $coordY);
+ }
+
+ /**
+ * Scrolls the page by a given left and top coordinates
+ * @param $left
+ * @param $top
+ * @return mixed
+ */
+ public function scrollTo($left, $top) {
+ return $this->command('scroll_to', $left, $top);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNavigateTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNavigateTrait.php
new file mode 100644
index 000000000..24189af77
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNavigateTrait.php
@@ -0,0 +1,56 @@
+command('visit', $url);
+ }
+
+ /**
+ * Gets the current url we are in
+ * @return mixed
+ */
+ public function currentUrl() {
+ return $this->command('current_url');
+ }
+
+ /**
+ * Goes back on the browser history if possible
+ * @return bool
+ * @throws BrowserError
+ * @throws \Exception
+ */
+ public function goBack() {
+ return $this->command('go_back');
+ }
+
+ /**
+ * Goes forward on the browser history if possible
+ * @return mixed
+ * @throws BrowserError
+ * @throws \Exception
+ */
+ public function goForward() {
+ return $this->command('go_forward');
+ }
+
+ /**
+ * Reloads the current page we are in
+ */
+ public function reload() {
+ return $this->command('reload');
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNetworkTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNetworkTrait.php
new file mode 100644
index 000000000..d79d21ec3
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNetworkTrait.php
@@ -0,0 +1,39 @@
+command('network_traffic');
+ $requestTraffic = array();
+
+ if (count($networkTraffic) === 0) {
+ return null;
+ }
+
+ foreach ($networkTraffic as $traffic) {
+ $requestTraffic[] = new Request($traffic["request"], $traffic["responseParts"]);
+ }
+
+ return $requestTraffic;
+ }
+
+ /**
+ * Clear the network traffic data stored on the phantomjs code
+ * @return mixed
+ */
+ public function clearNetworkTraffic() {
+ return $this->command('clear_network_traffic');
+ }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageElementTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageElementTrait.php
new file mode 100644
index 000000000..3f998fa9d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageElementTrait.php
@@ -0,0 +1,193 @@
+command('find', $method, $selector);
+ $found["page_id"] = $result["page_id"];
+ foreach ($result["ids"] as $id) {
+ $found["ids"][] = $id;
+ }
+ return $found;
+ }
+
+ /**
+ * Find elements within a page, method and selector
+ * @param $pageId
+ * @param $elementId
+ * @param $method
+ * @param $selector
+ * @return mixed
+ */
+ public function findWithin($pageId, $elementId, $method, $selector) {
+ return $this->command('find_within', $pageId, $elementId, $method, $selector);
+ }
+
+ /**
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function getParents($pageId, $elementId) {
+ return $this->command('parents', $pageId, $elementId);
+ }
+
+ /**
+ * Returns the text of a given page and element
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function allText($pageId, $elementId) {
+ return $this->command('all_text', $pageId, $elementId);
+ }
+
+ /**
+ * Returns the inner or outer html of the given page and element
+ * @param $pageId
+ * @param $elementId
+ * @param $type
+ * @return mixed
+ * @throws \Zumba\GastonJS\Exception\BrowserError
+ * @throws \Exception
+ */
+ public function allHtml($pageId, $elementId, $type = "inner") {
+ return $this->command('all_html', $pageId, $elementId, $type);
+ }
+
+ /**
+ * Returns ONLY the visible text of a given page and element
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function visibleText($pageId, $elementId) {
+ return $this->command('visible_text', $pageId, $elementId);
+ }
+
+ /**
+ * Deletes the text of a given page and element
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function deleteText($pageId, $elementId) {
+ return $this->command('delete_text', $pageId, $elementId);
+ }
+
+ /**
+ * Gets the tag name of a given element and page
+ * @param $pageId
+ * @param $elementId
+ * @return string
+ */
+ public function tagName($pageId, $elementId) {
+ return strtolower($this->command('tag_name', $pageId, $elementId));
+ }
+
+ /**
+ * Check if two elements are the same on a give
+ * @param $pageId
+ * @param $firstId
+ * @param $secondId
+ * @return bool
+ */
+ public function equals($pageId, $firstId, $secondId) {
+ return $this->command('equals', $pageId, $firstId, $secondId);
+ }
+
+ /**
+ * Returns the attributes of an element in a given page
+ * @param $pageId
+ * @param $elementId
+ * @return mixed
+ */
+ public function attributes($pageId, $elementId) {
+ return $this->command('attributes', $pageId, $elementId);
+ }
+
+ /**
+ * Returns the attribute of an element by name in a given page
+ * @param $pageId
+ * @param $elementId
+ * @param $name
+ * @return mixed
+ */
+ public function attribute($pageId, $elementId, $name) {
+ return $this->command('attribute', $pageId, $elementId, $name);
+ }
+
+ /**
+ * Set an attribute to the given element in the given page
+ * @param $pageId
+ * @param $elementId
+ * @param $name
+ * @param $value
+ * @return mixed
+ * @throws \Zumba\GastonJS\Exception\BrowserError
+ * @throws \Exception
+ */
+ public function setAttribute($pageId, $elementId, $name, $value) {
+ return $this->command('set_attribute', $pageId, $elementId, $name, $value);
+ }
+
+ /**
+ * Remove an attribute for a given page and element
+ * @param $pageId
+ * @param $elementId
+ * @param $name
+ * @return mixed
+ * @throws \Zumba\GastonJS\Exception\BrowserError
+ * @throws \Exception
+ */
+ public function removeAttribute($pageId, $elementId, $name) {
+ return $this->command('remove_attribute', $pageId, $elementId, $name);
+ }
+
+ /**
+ * Checks if an element is visible or not
+ * @param $pageId
+ * @param $elementId
+ * @return boolean
+ */
+ public function isVisible($pageId, $elementId) {
+ return $this->command("visible", $pageId, $elementId);
+ }
+
+ /**
+ * Sends the order to execute a key event on a given element
+ * @param $pageId
+ * @param $elementId
+ * @param $keyEvent
+ * @param $key
+ * @param $modifier
+ * @return mixed
+ */
+ public function keyEvent($pageId, $elementId, $keyEvent, $key, $modifier) {
+ return $this->command("key_event", $pageId, $elementId, $keyEvent, $key, $modifier);
+ }
+
+ /**
+ * Sends the command to select and option given a value
+ * @param $pageId
+ * @param $elementId
+ * @param $value
+ * @param bool $multiple
+ * @return mixed
+ */
+ public function selectOption($pageId, $elementId, $value, $multiple = false) {
+ return $this->command("select_option", $pageId, $elementId, $value, $multiple);
+ }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageTrait.php
new file mode 100644
index 000000000..3d5f9f121
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageTrait.php
@@ -0,0 +1,59 @@
+command('status_code');
+ }
+
+ /**
+ * Returns the body of the response to a given browser request
+ * @return mixed
+ */
+ public function getBody() {
+ return $this->command('body');
+ }
+
+ /**
+ * Returns the source of the current page
+ * @return mixed
+ */
+ public function getSource() {
+ return $this->command('source');
+ }
+
+ /**
+ * Gets the current page title
+ * @return mixed
+ */
+ public function getTitle() {
+ return $this->command('title');
+ }
+
+ /**
+ * Resize the current page
+ * @param $width
+ * @param $height
+ * @return mixed
+ */
+ public function resize($width, $height) {
+ return $this->command('resize', $width, $height);
+ }
+
+ /**
+ * Resets the page we are in to a clean slate
+ * @return mixed
+ */
+ public function reset() {
+ return $this->command('reset');
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserRenderTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserRenderTrait.php
new file mode 100644
index 000000000..3aa10aafb
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserRenderTrait.php
@@ -0,0 +1,65 @@
+checkRenderOptions($options);
+ return $this->command('render', $path, $fixedOptions["full"], $fixedOptions["selector"]);
+ }
+
+ /**
+ * Renders base64 a page or selection to a file given by path
+ * @param string $imageFormat (PNG, GIF, JPEG)
+ * @param array $options
+ * @return mixed
+ */
+ public function renderBase64($imageFormat, $options = array()) {
+ $fixedOptions = $this->checkRenderOptions($options);
+ return $this->command('render_base64', $imageFormat, $fixedOptions["full"], $fixedOptions["selector"]);
+ }
+
+ /**
+ * Sets the paper size, useful when saving to PDF
+ * @param $paperSize
+ * @return mixed
+ */
+ public function setPaperSize($paperSize) {
+ return $this->command('set_paper_size', $paperSize);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserScriptTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserScriptTrait.php
new file mode 100644
index 000000000..769b86fc5
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserScriptTrait.php
@@ -0,0 +1,41 @@
+command('evaluate', $script);
+ }
+
+ /**
+ * Executes a script on the browser
+ * @param $script
+ * @return mixed
+ */
+ public function execute($script) {
+ return $this->command('execute', $script);
+ }
+
+ /**
+ * Add desired extensions to phantomjs
+ * @param $extensions
+ * @return bool
+ */
+ public function extensions($extensions) {
+ //TODO: add error control for when extensions do not exist physically
+ foreach ($extensions as $extensionName) {
+ $this->command('add_extension', $extensionName);
+ }
+ return true;
+ }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserWindowTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserWindowTrait.php
new file mode 100644
index 000000000..8647ffcbb
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserWindowTrait.php
@@ -0,0 +1,81 @@
+command('window_handle', $name);
+ }
+
+ /**
+ * Returns all the window handles present in the browser
+ * @return array
+ */
+ public function windowHandles() {
+ return $this->command('window_handles');
+ }
+
+ /**
+ * Change the browser focus to another window
+ * @param $windowHandleName
+ * @return mixed
+ */
+ public function switchToWindow($windowHandleName) {
+ return $this->command('switch_to_window', $windowHandleName);
+ }
+
+ /**
+ * Opens a new window on the browser
+ * @return mixed
+ */
+ public function openNewWindow() {
+ return $this->command('open_new_window');
+ }
+
+ /**
+ * Closes a window on the browser by a given handler name
+ * @param $windowHandleName
+ * @return mixed
+ */
+ public function closeWindow($windowHandleName) {
+ return $this->command('close_window', $windowHandleName);
+ }
+
+ /**
+ * Gets the current request window name
+ * @return string
+ * @throws \Zumba\GastonJS\Exception\BrowserError
+ * @throws \Exception
+ */
+ public function windowName() {
+ return $this->command('window_name');
+ }
+
+ /**
+ * Zoom factor for a web page
+ * @param $zoomFactor
+ * @return mixed
+ */
+ public function setZoomFactor($zoomFactor) {
+ return $this->command('set_zoom_factor', $zoomFactor);
+ }
+
+ /**
+ * Gets the window size
+ * @param $windowHandleName
+ * @return mixed
+ */
+ public function windowSize($windowHandleName) {
+ return $this->command('window_size', $windowHandleName);
+ }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/browser_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/browser_error.js
new file mode 100644
index 000000000..892333caa
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/browser_error.js
@@ -0,0 +1,17 @@
+Poltergeist.BrowserError = (function (_super) {
+ __extends(BrowserError, _super);
+
+ function BrowserError(message, stack) {
+ this.message = message;
+ this.stack = stack;
+ }
+
+ BrowserError.prototype.name = "Poltergeist.BrowserError";
+
+ BrowserError.prototype.args = function () {
+ return [this.message, this.stack];
+ };
+
+ return BrowserError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/error.js
new file mode 100644
index 000000000..5a6f1f699
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/error.js
@@ -0,0 +1,10 @@
+/**
+ * Poltergeist base error class
+ */
+Poltergeist.Error = (function () {
+ function Error() {
+ }
+
+ return Error;
+
+})();
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/frame_not_found.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/frame_not_found.js
new file mode 100644
index 000000000..d42e87256
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/frame_not_found.js
@@ -0,0 +1,16 @@
+Poltergeist.FrameNotFound = (function (_super) {
+ __extends(FrameNotFound, _super);
+
+ function FrameNotFound(frameName) {
+ this.frameName = frameName;
+ }
+
+ FrameNotFound.prototype.name = "Poltergeist.FrameNotFound";
+
+ FrameNotFound.prototype.args = function () {
+ return [this.frameName];
+ };
+
+ return FrameNotFound;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/invalid_selector.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/invalid_selector.js
new file mode 100644
index 000000000..2ef4ae94d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/invalid_selector.js
@@ -0,0 +1,17 @@
+Poltergeist.InvalidSelector = (function (_super) {
+ __extends(InvalidSelector, _super);
+
+ function InvalidSelector(method, selector) {
+ this.method = method;
+ this.selector = selector;
+ }
+
+ InvalidSelector.prototype.name = "Poltergeist.InvalidSelector";
+
+ InvalidSelector.prototype.args = function () {
+ return [this.method, this.selector];
+ };
+
+ return InvalidSelector;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/javascript_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/javascript_error.js
new file mode 100644
index 000000000..b8679e4f6
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/javascript_error.js
@@ -0,0 +1,16 @@
+Poltergeist.JavascriptError = (function (_super) {
+ __extends(JavascriptError, _super);
+
+ function JavascriptError(errors) {
+ this.errors = errors;
+ }
+
+ JavascriptError.prototype.name = "Poltergeist.JavascriptError";
+
+ JavascriptError.prototype.args = function () {
+ return [this.errors];
+ };
+
+ return JavascriptError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/mouse_event_failed.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/mouse_event_failed.js
new file mode 100644
index 000000000..f3d4e854d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/mouse_event_failed.js
@@ -0,0 +1,18 @@
+Poltergeist.MouseEventFailed = (function (_super) {
+ __extends(MouseEventFailed, _super);
+
+ function MouseEventFailed(eventName, selector, position) {
+ this.eventName = eventName;
+ this.selector = selector;
+ this.position = position;
+ }
+
+ MouseEventFailed.prototype.name = "Poltergeist.MouseEventFailed";
+
+ MouseEventFailed.prototype.args = function () {
+ return [this.eventName, this.selector, this.position];
+ };
+
+ return MouseEventFailed;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/no_such_window_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/no_such_window_error.js
new file mode 100644
index 000000000..ee1d5ad7a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/no_such_window_error.js
@@ -0,0 +1,17 @@
+Poltergeist.NoSuchWindowError = (function (_super) {
+ __extends(NoSuchWindowError, _super);
+
+ function NoSuchWindowError() {
+ _ref2 = NoSuchWindowError.__super__.constructor.apply(this, arguments);
+ return _ref2;
+ }
+
+ NoSuchWindowError.prototype.name = "Poltergeist.NoSuchWindowError";
+
+ NoSuchWindowError.prototype.args = function () {
+ return [];
+ };
+
+ return NoSuchWindowError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/obsolete_node.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/obsolete_node.js
new file mode 100644
index 000000000..758cfd6b2
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/obsolete_node.js
@@ -0,0 +1,21 @@
+Poltergeist.ObsoleteNode = (function (_super) {
+ __extends(ObsoleteNode, _super);
+
+ function ObsoleteNode() {
+ _ref = ObsoleteNode.__super__.constructor.apply(this, arguments);
+ return _ref;
+ }
+
+ ObsoleteNode.prototype.name = "Poltergeist.ObsoleteNode";
+
+ ObsoleteNode.prototype.args = function () {
+ return [];
+ };
+
+ ObsoleteNode.prototype.toString = function () {
+ return this.name;
+ };
+
+ return ObsoleteNode;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/status_fail_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/status_fail_error.js
new file mode 100644
index 000000000..55f1871b1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/status_fail_error.js
@@ -0,0 +1,17 @@
+Poltergeist.StatusFailError = (function (_super) {
+ __extends(StatusFailError, _super);
+
+ function StatusFailError() {
+ _ref1 = StatusFailError.__super__.constructor.apply(this, arguments);
+ return _ref1;
+ }
+
+ StatusFailError.prototype.name = "Poltergeist.StatusFailError";
+
+ StatusFailError.prototype.args = function () {
+ return [];
+ };
+
+ return StatusFailError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Server/server.js b/vendor/jcalderonzumba/gastonjs/src/Client/Server/server.js
new file mode 100644
index 000000000..120d1fd68
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Server/server.js
@@ -0,0 +1,80 @@
+Poltergeist.Server = (function () {
+
+ /**
+ * Server constructor
+ * @param owner
+ * @param port
+ * @constructor
+ */
+ function Server(owner, port) {
+ this.server = require('webserver').create();
+ this.port = port;
+ this.owner = owner;
+ this.webServer = null;
+ }
+
+ /**
+ * Starts the web server
+ */
+ Server.prototype.start = function () {
+ var self = this;
+ this.webServer = this.server.listen(this.port, function (request, response) {
+ self.handleRequest(request, response);
+ });
+ };
+
+ /**
+ * Send error back with code and message
+ * @param response
+ * @param code
+ * @param message
+ * @return {boolean}
+ */
+ Server.prototype.sendError = function (response, code, message) {
+ response.statusCode = code;
+ response.setHeader('Content-Type', 'application/json');
+ response.write(JSON.stringify(message, null, 4));
+ response.close();
+ return true;
+ };
+
+
+ /**
+ * Send response back to the client
+ * @param response
+ * @param data
+ * @return {boolean}
+ */
+ Server.prototype.send = function (response, data) {
+ console.log("RESPONSE: " + JSON.stringify(data, null, 4).substr(0, 200));
+
+ response.statusCode = 200;
+ response.setHeader('Content-Type', 'application/json');
+ response.write(JSON.stringify(data, null, 4));
+ response.close();
+ return true;
+ };
+
+ /**
+ * Handles a request to the server
+ * @param request
+ * @param response
+ * @return {boolean}
+ */
+ Server.prototype.handleRequest = function (request, response) {
+ var commandData;
+ if (request.method !== "POST") {
+ return this.sendError(response, 405, "Only POST method is allowed in the service");
+ }
+ console.log("REQUEST: " + request.post + "\n");
+ try {
+ commandData = JSON.parse(request.post);
+ } catch (parseError) {
+ return this.sendError(response, 400, "JSON data invalid error: " + parseError.message);
+ }
+
+ return this.owner.serverRunCommand(commandData, response);
+ };
+
+ return Server;
+})();
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Tools/inherit.js b/vendor/jcalderonzumba/gastonjs/src/Client/Tools/inherit.js
new file mode 100644
index 000000000..a67a75cc6
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Tools/inherit.js
@@ -0,0 +1,28 @@
+var __extends;
+/**
+ * Helper function so objects can inherit from another
+ * @param child
+ * @param parent
+ * @return {Object}
+ * @private
+ */
+__extends = function (child, parent) {
+ var __hasProp;
+ __hasProp = {}.hasOwnProperty;
+ for (var key in parent) {
+ if (parent.hasOwnProperty(key)) {
+ if (__hasProp.call(parent, key)) {
+ child[key] = parent[key];
+ }
+ }
+ }
+
+ function ClassConstructor() {
+ this.constructor = child;
+ }
+
+ ClassConstructor.prototype = parent.prototype;
+ child.prototype = new ClassConstructor();
+ child.__super__ = parent.prototype;
+ return child;
+};
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/agent.js b/vendor/jcalderonzumba/gastonjs/src/Client/agent.js
new file mode 100644
index 000000000..606a6c166
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/agent.js
@@ -0,0 +1,896 @@
+var PoltergeistAgent;
+
+PoltergeistAgent = (function () {
+ function PoltergeistAgent() {
+ this.elements = [];
+ this.nodes = {};
+ }
+
+ /**
+ * Executes an external call done from the web page class
+ * @param name
+ * @param args
+ * @return {*}
+ */
+ PoltergeistAgent.prototype.externalCall = function (name, args) {
+ var error;
+ try {
+ return {
+ value: this[name].apply(this, args)
+ };
+ } catch (_error) {
+ error = _error;
+ return {
+ error: {
+ message: error.toString(),
+ stack: error.stack
+ }
+ };
+ }
+ };
+
+ /**
+ * Object stringifycation
+ * @param object
+ * @return {*}
+ */
+ PoltergeistAgent.stringify = function (object) {
+ var error;
+ try {
+ return JSON.stringify(object, function (key, value) {
+ if (Array.isArray(this[key])) {
+ return this[key];
+ } else {
+ return value;
+ }
+ });
+ } catch (_error) {
+ error = _error;
+ if (error instanceof TypeError) {
+ return '"(cyclic structure)"';
+ } else {
+ throw error;
+ }
+ }
+ };
+
+ /**
+ * Name speaks for itself
+ * @return {string}
+ */
+ PoltergeistAgent.prototype.currentUrl = function () {
+ return encodeURI(decodeURI(window.location.href));
+ };
+
+ /**
+ * Given a method of selection (xpath or css), a selector and a possible element to search
+ * tries to find the elements that matches such selection
+ * @param method
+ * @param selector
+ * @param within
+ * @return {Array}
+ */
+ PoltergeistAgent.prototype.find = function (method, selector, within) {
+ var elementForXpath, error, i, results, xpath, _i, _len, _results;
+ if (within == null) {
+ within = document;
+ }
+ try {
+ if (method === "xpath") {
+ xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ results = (function () {
+ var _i, _ref, _results;
+ _results = [];
+ for (i = _i = 0, _ref = xpath.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
+ _results.push(xpath.snapshotItem(i));
+ }
+ return _results;
+ })();
+ } else {
+ results = within.querySelectorAll(selector);
+ }
+ _results = [];
+ for (_i = 0, _len = results.length; _i < _len; _i++) {
+ elementForXpath = results[_i];
+ _results.push(this.register(elementForXpath));
+ }
+ return _results;
+ } catch (_error) {
+ error = _error;
+ if (error.code === DOMException.SYNTAX_ERR || error.code === 51) {
+ throw new PoltergeistAgent.InvalidSelector;
+ } else {
+ throw error;
+ }
+ }
+ };
+
+ /**
+ * Register the element in the agent
+ * @param element
+ * @return {number}
+ */
+ PoltergeistAgent.prototype.register = function (element) {
+ this.elements.push(element);
+ return this.elements.length - 1;
+ };
+
+ /**
+ * Gets the size of the document
+ * @return {{height: number, width: number}}
+ */
+ PoltergeistAgent.prototype.documentSize = function () {
+ return {
+ height: document.documentElement.scrollHeight || document.documentElement.clientHeight,
+ width: document.documentElement.scrollWidth || document.documentElement.clientWidth
+ };
+ };
+
+ /**
+ * Gets a Node by a given id
+ * @param id
+ * @return {PoltergeistAgent.Node}
+ */
+ PoltergeistAgent.prototype.get = function (id) {
+ if (typeof this.nodes[id] == "undefined" || this.nodes[id] === null) {
+ //Let's try now the elements approach
+ if (typeof this.elements[id] == "undefined" || this.elements[id] === null) {
+ throw new PoltergeistAgent.ObsoleteNode;
+ }
+ return new PoltergeistAgent.Node(this, this.elements[id]);
+ }
+
+ return this.nodes[id];
+ };
+
+ /**
+ * Calls a Node agent function from the Node caller via delegates
+ * @param id
+ * @param name
+ * @param args
+ * @return {*}
+ */
+ PoltergeistAgent.prototype.nodeCall = function (id, name, args) {
+ var node;
+
+ node = this.get(id);
+ if (node.isObsolete()) {
+ throw new PoltergeistAgent.ObsoleteNode;
+ }
+ //TODO: add some error control here, we might not be able to call name function
+ return node[name].apply(node, args);
+ };
+
+ PoltergeistAgent.prototype.beforeUpload = function (id) {
+ return this.get(id).setAttribute('_poltergeist_selected', '');
+ };
+
+ PoltergeistAgent.prototype.afterUpload = function (id) {
+ return this.get(id).removeAttribute('_poltergeist_selected');
+ };
+
+ PoltergeistAgent.prototype.clearLocalStorage = function () {
+ //TODO: WTF where is variable...
+ return localStorage.clear();
+ };
+
+ return PoltergeistAgent;
+
+})();
+
+PoltergeistAgent.ObsoleteNode = (function () {
+ function ObsoleteNode() {
+ }
+
+ ObsoleteNode.prototype.toString = function () {
+ return "PoltergeistAgent.ObsoleteNode";
+ };
+
+ return ObsoleteNode;
+
+})();
+
+PoltergeistAgent.InvalidSelector = (function () {
+ function InvalidSelector() {
+ }
+
+ InvalidSelector.prototype.toString = function () {
+ return "PoltergeistAgent.InvalidSelector";
+ };
+
+ return InvalidSelector;
+
+})();
+
+PoltergeistAgent.Node = (function () {
+
+ Node.EVENTS = {
+ FOCUS: ['blur', 'focus', 'focusin', 'focusout'],
+ MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'contextmenu'],
+ FORM: ['submit']
+ };
+
+ function Node(agent, element) {
+ this.agent = agent;
+ this.element = element;
+ }
+
+ /**
+ * Give me the node id of the parent of this node
+ * @return {number}
+ */
+ Node.prototype.parentId = function () {
+ return this.agent.register(this.element.parentNode);
+ };
+
+ /**
+ * Returns all the node parents ids up to first child of the dom
+ * @return {Array}
+ */
+ Node.prototype.parentIds = function () {
+ var ids, parent;
+ ids = [];
+ parent = this.element.parentNode;
+ while (parent !== document) {
+ ids.push(this.agent.register(parent));
+ parent = parent.parentNode;
+ }
+ return ids;
+ };
+
+ /**
+ * Finds and returns the node ids that matches the selector within this node
+ * @param method
+ * @param selector
+ * @return {Array}
+ */
+ Node.prototype.find = function (method, selector) {
+ return this.agent.find(method, selector, this.element);
+ };
+
+ /**
+ * Checks whether the node is obsolete or not
+ * @return boolean
+ */
+ Node.prototype.isObsolete = function () {
+ var obsolete;
+
+ obsolete = function (element) {
+ if (element.parentNode != null) {
+ if (element.parentNode === document) {
+ return false;
+ } else {
+ return obsolete(element.parentNode);
+ }
+ } else {
+ return true;
+ }
+ };
+
+ return obsolete(this.element);
+ };
+
+ Node.prototype.changed = function () {
+ var event;
+ event = document.createEvent('HTMLEvents');
+ event.initEvent('change', true, false);
+ return this.element.dispatchEvent(event);
+ };
+
+ Node.prototype.input = function () {
+ var event;
+ event = document.createEvent('HTMLEvents');
+ event.initEvent('input', true, false);
+ return this.element.dispatchEvent(event);
+ };
+
+ Node.prototype.keyupdowned = function (eventName, keyCode) {
+ var event;
+ event = document.createEvent('UIEvents');
+ event.initEvent(eventName, true, true);
+ event.keyCode = keyCode;
+ event.which = keyCode;
+ event.charCode = 0;
+ return this.element.dispatchEvent(event);
+ };
+
+ Node.prototype.keypressed = function (altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
+ var event;
+ event = document.createEvent('UIEvents');
+ event.initEvent('keypress', true, true);
+ event.window = this.agent.window;
+ event.altKey = altKey;
+ event.ctrlKey = ctrlKey;
+ event.shiftKey = shiftKey;
+ event.metaKey = metaKey;
+ event.keyCode = keyCode;
+ event.charCode = charCode;
+ event.which = keyCode;
+ return this.element.dispatchEvent(event);
+ };
+
+ /**
+ * Tells if the node is inside the body of the document and not somewhere else
+ * @return {boolean}
+ */
+ Node.prototype.insideBody = function () {
+ return this.element === document.body || document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
+ };
+
+ /**
+ * Returns all text visible or not of the node
+ * @return {string}
+ */
+ Node.prototype.allText = function () {
+ return this.element.textContent;
+ };
+
+ /**
+ * Returns the inner html our outer
+ * @returns {string}
+ */
+ Node.prototype.allHTML = function (type) {
+ var returnType = type || 'inner';
+
+ if (returnType === "inner") {
+ return this.element.innerHTML;
+ }
+
+ if (returnType === "outer") {
+ if (this.element.outerHTML) {
+ return this.element.outerHTML;
+ }
+ // polyfill:
+ var wrapper = document.createElement('div');
+ wrapper.appendChild(this.element.cloneNode(true));
+ return wrapper.innerHTML;
+ }
+
+ return '';
+ };
+
+ /**
+ * If the element is visible then we return the text
+ * @return {string}
+ */
+ Node.prototype.visibleText = function () {
+ if (!this.isVisible(null)) {
+ return null;
+ }
+
+ if (this.element.nodeName === "TEXTAREA") {
+ return this.element.textContent;
+ }
+
+ return this.element.innerText;
+ };
+
+ /**
+ * Deletes the actual text being represented by a selection object from the node's element DOM.
+ * @return {*}
+ */
+ Node.prototype.deleteText = function () {
+ var range;
+ range = document.createRange();
+ range.selectNodeContents(this.element);
+ window.getSelection().removeAllRanges();
+ window.getSelection().addRange(range);
+ return window.getSelection().deleteFromDocument();
+ };
+
+ /**
+ * Returns all the attributes {name:value} in the element
+ * @return {{}}
+ */
+ Node.prototype.getAttributes = function () {
+ var attributes, i, elementAttributes;
+
+ elementAttributes = this.element.attributes;
+ attributes = {};
+ for (i = 0; i < elementAttributes.length; i++) {
+ attributes[elementAttributes[i].name] = elementAttributes[i].value.replace("\n", "\\n");
+ }
+
+ return attributes;
+ };
+
+ /**
+ * Name speaks for it self, returns the value of a given attribute by name
+ * @param name
+ * @return {string}
+ */
+ Node.prototype.getAttribute = function (name) {
+ if (name === 'checked' || name === 'selected' || name === 'multiple') {
+ return this.element[name];
+ }
+ return this.element.getAttribute(name);
+ };
+
+ /**
+ * Scrolls the current element into the visible area of the browser window
+ * @return {*}
+ */
+ Node.prototype.scrollIntoView = function () {
+ return this.element.scrollIntoViewIfNeeded();
+ };
+
+ /**
+ * Returns the element.value property with special treatment if the element is a select
+ * @return {*}
+ */
+ Node.prototype.value = function () {
+ var options, i, values;
+
+ if (this.element.tagName.toLowerCase() === 'select' && this.element.multiple) {
+ values = [];
+ options = this.element.children;
+ for (i = 0; i < options.length; i++) {
+ if (options[i].selected) {
+ values.push(options[i].value);
+ }
+ }
+ return values;
+ }
+
+ return this.element.value;
+ };
+
+ /**
+ * Sets a given value in the element value property by simulation key interaction
+ * @param value
+ * @return {*}
+ */
+ Node.prototype.set = function (value) {
+ var char, keyCode, i, len;
+
+ if (this.element.readOnly) {
+ return null;
+ }
+
+ //respect the maxLength property if present
+ if (this.element.maxLength >= 0) {
+ value = value.substr(0, this.element.maxLength);
+ }
+
+ this.element.value = '';
+ this.trigger('focus');
+
+ if (this.element.type === 'number') {
+ this.element.value = value;
+ } else {
+ for (i = 0, len = value.length; i < len; i++) {
+ char = value[i];
+ keyCode = this.characterToKeyCode(char);
+ this.keyupdowned('keydown', keyCode);
+ this.element.value += char;
+ this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
+ this.keyupdowned('keyup', keyCode);
+ }
+ }
+
+ this.changed();
+ this.input();
+
+ return this.trigger('blur');
+ };
+
+ /**
+ * Is the node multiple
+ * @return {boolean}
+ */
+ Node.prototype.isMultiple = function () {
+ return this.element.multiple;
+ };
+
+ /**
+ * Sets the value of an attribute given by name
+ * @param name
+ * @param value
+ * @return {boolean}
+ */
+ Node.prototype.setAttribute = function (name, value) {
+ if (value === null) {
+ return this.removeAttribute(name);
+ }
+
+ this.element.setAttribute(name, value);
+ return true;
+ };
+
+ /**
+ * Removes and attribute by name
+ * @param name
+ * @return {boolean}
+ */
+ Node.prototype.removeAttribute = function (name) {
+ this.element.removeAttribute(name);
+ return true;
+ };
+
+ /**
+ * Selects the current node
+ * @param value
+ * @return {boolean}
+ */
+ Node.prototype.select = function (value) {
+ if (value === false && !this.element.parentNode.multiple) {
+ return false;
+ }
+
+ this.element.selected = value;
+ this.changed();
+ return true;
+ };
+
+ /**
+ * Selects the radio button that has the defined value
+ * @param value
+ * @return {boolean}
+ */
+ Node.prototype.selectRadioValue = function (value) {
+ if (this.element.value == value) {
+ this.element.checked = true;
+ this.trigger('focus');
+ this.trigger('click');
+ this.changed();
+ return true;
+ }
+
+ var formElements = this.element.form.elements;
+ var name = this.element.getAttribute('name');
+ var element, i;
+
+ var deselectAllRadios = function (elements, radioName) {
+ var inputRadioElement;
+
+ for (i = 0; i < elements.length; i++) {
+ inputRadioElement = elements[i];
+ if (inputRadioElement.tagName.toLowerCase() == 'input' && inputRadioElement.type.toLowerCase() == 'radio' && inputRadioElement.name == radioName) {
+ inputRadioElement.checked = false;
+ }
+ }
+ };
+
+ var radioChange = function (radioElement) {
+ var radioEvent;
+ radioEvent = document.createEvent('HTMLEvents');
+ radioEvent.initEvent('change', true, false);
+ return radioElement.dispatchEvent(radioEvent);
+ };
+
+ var radioClickEvent = function (radioElement, name) {
+ var radioEvent;
+ radioEvent = document.createEvent('MouseEvent');
+ radioEvent.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ return radioElement.dispatchEvent(radioEvent);
+ };
+
+ if (!name) {
+ throw new Poltergeist.BrowserError('The radio button does not have the value "' + value + '"');
+ }
+
+ for (i = 0; i < formElements.length; i++) {
+ element = formElements[i];
+ if (element.tagName.toLowerCase() == 'input' && element.type.toLowerCase() == 'radio' && element.name === name) {
+ if (value === element.value) {
+ deselectAllRadios(formElements, name);
+ element.checked = true;
+ radioClickEvent(element, 'click');
+ radioChange(element);
+ return true;
+ }
+ }
+ }
+
+ throw new Poltergeist.BrowserError('The radio group "' + name + '" does not have an option "' + value + '"');
+ };
+
+ /**
+ * Checks or uncheck a radio option
+ * @param value
+ * @return {boolean}
+ */
+ Node.prototype.checked = function (value) {
+ //TODO: add error control for the checked stuff
+ this.element.checked = value;
+ return true;
+ };
+
+ /**
+ * Returns the element tag name as is, no transformations done
+ * @return {string}
+ */
+ Node.prototype.tagName = function () {
+ return this.element.tagName;
+ };
+
+ /**
+ * Checks if the element is visible either by itself of because the parents are visible
+ * @param element
+ * @return {boolean}
+ */
+ Node.prototype.isVisible = function (element) {
+ var nodeElement = element || this.element;
+
+ if (window.getComputedStyle(nodeElement).display === 'none') {
+ return false;
+ } else if (nodeElement.parentElement) {
+ return this.isVisible(nodeElement.parentElement);
+ } else {
+ return true;
+ }
+ };
+
+ /**
+ * Is the node disabled for operations with it?
+ * @return {boolean}
+ */
+ Node.prototype.isDisabled = function () {
+ return this.element.disabled || this.element.tagName === 'OPTION' && this.element.parentNode.disabled;
+ };
+
+ /**
+ * Does the node contains the selections
+ * @return {boolean}
+ */
+ Node.prototype.containsSelection = function () {
+ var selectedNode;
+
+ selectedNode = document.getSelection().focusNode;
+ if (!selectedNode) {
+ return false;
+ }
+ //this magic number is NODE.TEXT_NODE
+ if (selectedNode.nodeType === 3) {
+ selectedNode = selectedNode.parentNode;
+ }
+
+ return this.element.contains(selectedNode);
+ };
+
+ /**
+ * Returns the offset of the node in relation to the current frame
+ * @return {{top: number, left: number}}
+ */
+ Node.prototype.frameOffset = function () {
+ var offset, rect, style, win;
+ win = window;
+ offset = {
+ top: 0,
+ left: 0
+ };
+ while (win.frameElement) {
+ rect = win.frameElement.getClientRects()[0];
+ style = win.getComputedStyle(win.frameElement);
+ win = win.parent;
+ offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10);
+ offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10);
+ }
+ return offset;
+ };
+
+ /**
+ * Returns the object position in relation to the window
+ * @return {{top: *, right: *, left: *, bottom: *, width: *, height: *}}
+ */
+ Node.prototype.position = function () {
+ var frameOffset, pos, rect;
+
+ rect = this.element.getClientRects()[0];
+ if (!rect) {
+ throw new PoltergeistAgent.ObsoleteNode;
+ }
+
+ frameOffset = this.frameOffset();
+ pos = {
+ top: rect.top + frameOffset.top,
+ right: rect.right + frameOffset.left,
+ left: rect.left + frameOffset.left,
+ bottom: rect.bottom + frameOffset.top,
+ width: rect.width,
+ height: rect.height
+ };
+
+ return pos;
+ };
+
+ /**
+ * Triggers a DOM event related to the node element
+ * @param name
+ * @return {boolean}
+ */
+ Node.prototype.trigger = function (name) {
+ var event;
+ if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
+ event = document.createEvent('MouseEvent');
+ event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ } else if (Node.EVENTS.FOCUS.indexOf(name) !== -1) {
+ event = this.obtainEvent(name);
+ } else if (Node.EVENTS.FORM.indexOf(name) !== -1) {
+ event = this.obtainEvent(name);
+ } else {
+ throw "Unknown event";
+ }
+ return this.element.dispatchEvent(event);
+ };
+
+ /**
+ * Creates a generic HTMLEvent to be use in the node element
+ * @param name
+ * @return {Event}
+ */
+ Node.prototype.obtainEvent = function (name) {
+ var event;
+ event = document.createEvent('HTMLEvents');
+ event.initEvent(name, true, true);
+ return event;
+ };
+
+ /**
+ * Does a check to see if the coordinates given
+ * match the node element or some of the parents chain
+ * @param x
+ * @param y
+ * @return {*}
+ */
+ Node.prototype.mouseEventTest = function (x, y) {
+ var elementForXpath, frameOffset, origEl;
+
+ frameOffset = this.frameOffset();
+ x -= frameOffset.left;
+ y -= frameOffset.top;
+
+ elementForXpath = origEl = document.elementFromPoint(x, y);
+ while (elementForXpath) {
+ if (elementForXpath === this.element) {
+ return {
+ status: 'success'
+ };
+ } else {
+ elementForXpath = elementForXpath.parentNode;
+ }
+ }
+
+ return {
+ status: 'failure',
+ selector: origEl && this.getSelector(origEl)
+ };
+ };
+
+ /**
+ * Returns the node selector in CSS style (NO xpath)
+ * @param elementForXpath
+ * @return {string}
+ */
+ Node.prototype.getSelector = function (elementForXpath) {
+ var className, selector, i, len, classNames;
+
+ selector = elementForXpath.tagName !== 'HTML' ? this.getSelector(elementForXpath.parentNode) + ' ' : '';
+ selector += elementForXpath.tagName.toLowerCase();
+
+ if (elementForXpath.id) {
+ selector += "#" + elementForXpath.id;
+ }
+
+ classNames = elementForXpath.classList;
+ for (i = 0, len = classNames.length; i < len; i++) {
+ className = classNames[i];
+ selector += "." + className;
+ }
+
+ return selector;
+ };
+
+ /**
+ * Returns the key code that represents the character
+ * @param character
+ * @return {number}
+ */
+ Node.prototype.characterToKeyCode = function (character) {
+ var code, specialKeys;
+ code = character.toUpperCase().charCodeAt(0);
+ specialKeys = {
+ 96: 192,
+ 45: 189,
+ 61: 187,
+ 91: 219,
+ 93: 221,
+ 92: 220,
+ 59: 186,
+ 39: 222,
+ 44: 188,
+ 46: 190,
+ 47: 191,
+ 127: 46,
+ 126: 192,
+ 33: 49,
+ 64: 50,
+ 35: 51,
+ 36: 52,
+ 37: 53,
+ 94: 54,
+ 38: 55,
+ 42: 56,
+ 40: 57,
+ 41: 48,
+ 95: 189,
+ 43: 187,
+ 123: 219,
+ 125: 221,
+ 124: 220,
+ 58: 186,
+ 34: 222,
+ 60: 188,
+ 62: 190,
+ 63: 191
+ };
+ return specialKeys[code] || code;
+ };
+
+ /**
+ * Checks if one element is equal to other given by its node id
+ * @param other_id
+ * @return {boolean}
+ */
+ Node.prototype.isDOMEqual = function (other_id) {
+ return this.element === this.agent.get(other_id).element;
+ };
+
+ /**
+ * The following function allows one to pass an element and an XML document to find a unique string XPath expression leading back to that element.
+ * @param element
+ * @return {string}
+ */
+ Node.prototype.getXPathForElement = function (element) {
+ var elementForXpath = element || this.element;
+ var xpath = '';
+ var pos, tempitem2;
+
+ while (elementForXpath !== document.documentElement) {
+ pos = 0;
+ tempitem2 = elementForXpath;
+ while (tempitem2) {
+ if (tempitem2.nodeType === 1 && tempitem2.nodeName === elementForXpath.nodeName) { // If it is ELEMENT_NODE of the same name
+ pos += 1;
+ }
+ tempitem2 = tempitem2.previousSibling;
+ }
+
+ xpath = "*[name()='" + elementForXpath.nodeName + "' and namespace-uri()='" + (elementForXpath.namespaceURI === null ? '' : elementForXpath.namespaceURI) + "'][" + pos + ']' + '/' + xpath;
+
+ elementForXpath = elementForXpath.parentNode;
+ }
+
+ xpath = '/*' + "[name()='" + document.documentElement.nodeName + "' and namespace-uri()='" + (elementForXpath.namespaceURI === null ? '' : elementForXpath.namespaceURI) + "']" + '/' + xpath;
+ xpath = xpath.replace(/\/$/, '');
+ return xpath;
+ };
+
+ /**
+ * Deselect all the options for this element
+ */
+ Node.prototype.deselectAllOptions = function () {
+ //TODO: error control when the node is not a select node
+ var i, l = this.element.options.length;
+ for (i = 0; i < l; i++) {
+ this.element.options[i].selected = false;
+ }
+ };
+
+ return Node;
+
+})();
+
+window.__poltergeist = new PoltergeistAgent;
+
+document.addEventListener('DOMContentLoaded', function () {
+ return console.log('__DOMContentLoaded');
+});
+
+window.confirm = function (message) {
+ return true;
+};
+
+window.prompt = function (message, _default) {
+ return _default || null;
+};
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/browser.js b/vendor/jcalderonzumba/gastonjs/src/Client/browser.js
new file mode 100644
index 000000000..df667fbac
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/browser.js
@@ -0,0 +1,1293 @@
+var __indexOf = [].indexOf || function (item) {
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (i in this && this[i] === item) return i;
+ }
+ return -1;
+ };
+
+var xpathStringLiteral = function (s) {
+ if (s.indexOf('"') === -1)
+ return '"' + s + '"';
+ if (s.indexOf("'") === -1)
+ return "'" + s + "'";
+ return 'concat("' + s.replace(/"/g, '",\'"\',"') + '")';
+};
+
+Poltergeist.Browser = (function () {
+ /**
+ * Creates the "browser" inside phantomjs
+ * @param owner
+ * @param width
+ * @param height
+ * @constructor
+ */
+ function Browser(owner, width, height) {
+ this.owner = owner;
+ this.width = width || 1024;
+ this.height = height || 768;
+ this.pages = [];
+ this.js_errors = true;
+ this._debug = false;
+ this._counter = 0;
+ this.resetPage();
+ }
+
+ /**
+ * Resets the browser to a clean slate
+ * @return {Function}
+ */
+ Browser.prototype.resetPage = function () {
+ var _ref;
+ var self = this;
+
+ _ref = [0, []];
+ this._counter = _ref[0];
+ this.pages = _ref[1];
+
+ if (this.page != null) {
+ if (!this.page.closed) {
+ if (this.page.currentUrl() !== 'about:blank') {
+ this.page.clearLocalStorage();
+ }
+ this.page.release();
+ }
+ phantom.clearCookies();
+ }
+
+ this.page = this.currentPage = new Poltergeist.WebPage;
+ this.page.setViewportSize({
+ width: this.width,
+ height: this.height
+ });
+ this.page.handle = "" + (this._counter++);
+ this.pages.push(this.page);
+
+ return this.page.onPageCreated = function (newPage) {
+ var page;
+ page = new Poltergeist.WebPage(newPage);
+ page.handle = "" + (self._counter++);
+ return self.pages.push(page);
+ };
+ };
+
+ /**
+ * Given a page handle id, tries to get it from the browser page list
+ * @param handle
+ * @return {WebPage}
+ */
+ Browser.prototype.getPageByHandle = function (handle) {
+ var filteredPages;
+
+ //TODO: perhaps we should throw a PageNotFoundByHandle or something like that..
+ if (handle === null || typeof handle == "undefined") {
+ return null;
+ }
+
+ filteredPages = this.pages.filter(function (p) {
+ return !p.closed && p.handle === handle;
+ });
+
+ if (filteredPages.length === 1) {
+ return filteredPages[0];
+ }
+
+ return null;
+ };
+
+ /**
+ * Sends a debug message to the console
+ * @param message
+ * @return {*}
+ */
+ Browser.prototype.debug = function (message) {
+ if (this._debug) {
+ return console.log("poltergeist [" + (new Date().getTime()) + "] " + message);
+ }
+ };
+
+ /**
+ * Given a page_id and id, gets if possible the node in such page
+ * @param page_id
+ * @param id
+ * @return {Poltergeist.Node}
+ */
+ Browser.prototype.node = function (page_id, id) {
+ if (this.currentPage.id === page_id) {
+ return this.currentPage.get(id);
+ } else {
+ throw new Poltergeist.ObsoleteNode;
+ }
+ };
+
+ /**
+ * Returns the frameUrl related to the frame given by name
+ * @param frame_name
+ * @return {*}
+ */
+ Browser.prototype.frameUrl = function (frame_name) {
+ return this.currentPage.frameUrl(frame_name);
+ };
+
+ /**
+ * This method defines the rectangular area of the web page to be rasterized when render is invoked.
+ * If no clipping rectangle is set, render will process the entire web page.
+ * @param full
+ * @param selector
+ * @return {*}
+ */
+ Browser.prototype.set_clip_rect = function (full, selector) {
+ var dimensions, clipDocument, rect, clipViewport;
+
+ dimensions = this.currentPage.validatedDimensions();
+ clipDocument = dimensions.document;
+ clipViewport = dimensions.viewport;
+
+ if (full) {
+ rect = {
+ left: 0,
+ top: 0,
+ width: clipDocument.width,
+ height: clipDocument.height
+ };
+ } else {
+ if (selector != null) {
+ rect = this.currentPage.elementBounds(selector);
+ } else {
+ rect = {
+ left: 0,
+ top: 0,
+ width: clipViewport.width,
+ height: clipViewport.height
+ };
+ }
+ }
+
+ this.currentPage.setClipRect(rect);
+ return dimensions;
+ };
+
+ /**
+ * Kill the browser, i.e kill phantomjs current process
+ * @return {int}
+ */
+ Browser.prototype.exit = function () {
+ return phantom.exit(0);
+ };
+
+ /**
+ * Do nothing
+ */
+ Browser.prototype.noop = function () {
+ };
+
+ /**
+ * Throws a new Object error
+ */
+ Browser.prototype.browser_error = function () {
+ throw new Error('zomg');
+ };
+
+ /**
+ * Visits a page and load its content
+ * @param serverResponse
+ * @param url
+ * @return {*}
+ */
+ Browser.prototype.visit = function (serverResponse, url) {
+ var prevUrl;
+ var self = this;
+ this.currentPage.state = 'loading';
+ prevUrl = this.currentPage.source === null ? 'about:blank' : this.currentPage.currentUrl();
+ this.currentPage.open(url);
+ if (/#/.test(url) && prevUrl.split('#')[0] === url.split('#')[0]) {
+ this.currentPage.state = 'default';
+ return this.serverSendResponse({
+ status: 'success'
+ }, serverResponse);
+ } else {
+ return this.currentPage.waitState('default', function () {
+ if (self.currentPage.statusCode === null && self.currentPage.status === 'fail') {
+ return self.owner.serverSendError(new Poltergeist.StatusFailError, serverResponse);
+ } else {
+ return self.serverSendResponse({
+ status: self.currentPage.status
+ }, serverResponse);
+ }
+ });
+ }
+ };
+
+ /**
+ * Puts the control of the browser inside the IFRAME given by name
+ * @param serverResponse
+ * @param name
+ * @param timeout
+ * @return {*}
+ */
+ Browser.prototype.push_frame = function (serverResponse, name, timeout) {
+ var _ref;
+ var self = this;
+
+ if (timeout == null) {
+ timeout = new Date().getTime() + 2000;
+ }
+
+ //TODO: WTF, else if after a if with return COMMON
+ if (_ref = this.frameUrl(name), __indexOf.call(this.currentPage.blockedUrls(), _ref) >= 0) {
+ return this.serverSendResponse(true, serverResponse);
+ } else if (this.currentPage.pushFrame(name)) {
+ if (this.currentPage.currentUrl() === 'about:blank') {
+ this.currentPage.state = 'awaiting_frame_load';
+ return this.currentPage.waitState('default', function () {
+ return self.serverSendResponse(true, serverResponse);
+ });
+ } else {
+ return this.serverSendResponse(true, serverResponse);
+ }
+ } else {
+ if (new Date().getTime() < timeout) {
+ return setTimeout((function () {
+ return self.push_frame(serverResponse, name, timeout);
+ }), 50);
+ } else {
+ return this.owner.serverSendError(new Poltergeist.FrameNotFound(name), serverResponse);
+ }
+ }
+ };
+
+ /**
+ * Injects a javascript into the current page
+ * @param serverResponse
+ * @param extension
+ * @return {*}
+ */
+ Browser.prototype.add_extension = function (serverResponse, extension) {
+ //TODO: error control when the injection was not possible
+ this.currentPage.injectExtension(extension);
+ return this.serverSendResponse('success', serverResponse);
+ };
+
+ /**
+ * Returns the url we are currently in
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.current_url = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.currentUrl(), serverResponse);
+ };
+
+ /**
+ * Returns the current page window name
+ * @param serverResponse
+ * @returns {*}
+ */
+ Browser.prototype.window_name = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.windowName(), serverResponse);
+ };
+
+ /**
+ * Returns the status code associated to the page
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.status_code = function (serverResponse) {
+ if (this.currentPage.statusCode === undefined || this.currentPage.statusCode === null) {
+ return this.owner.serverSendError(new Poltergeist.StatusFailError("status_code_error"), serverResponse);
+ }
+ return this.serverSendResponse(this.currentPage.statusCode, serverResponse);
+ };
+
+ /**
+ * Returns the source code of the active frame, useful for when inside an IFRAME
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.body = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.content(), serverResponse);
+ };
+
+ /**
+ * Returns the source code of the page all the html
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.source = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.source, serverResponse);
+ };
+
+ /**
+ * Returns the current page title
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.title = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.title(), serverResponse);
+ };
+
+ /**
+ * Finds the elements that match a method of selection and a selector
+ * @param serverResponse
+ * @param method
+ * @param selector
+ * @return {*}
+ */
+ Browser.prototype.find = function (serverResponse, method, selector) {
+ return this.serverSendResponse({
+ page_id: this.currentPage.id,
+ ids: this.currentPage.find(method, selector)
+ }, serverResponse);
+ };
+
+ /**
+ * Find elements within a given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param method
+ * @param selector
+ * @return {*}
+ */
+ Browser.prototype.find_within = function (serverResponse, page_id, id, method, selector) {
+ return this.serverSendResponse(this.node(page_id, id).find(method, selector), serverResponse);
+ };
+
+ /**
+ * Returns ALL the text, visible and not visible from the given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.all_text = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).allText(), serverResponse);
+ };
+
+ /**
+ * Returns the inner or outer html of a given id
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param type
+ * @returns Object
+ */
+ Browser.prototype.all_html = function (serverResponse, page_id, id, type) {
+ return this.serverSendResponse(this.node(page_id, id).allHTML(type), serverResponse);
+ };
+
+ /**
+ * Returns only the visible text in a given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.visible_text = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).visibleText(), serverResponse);
+ };
+
+ /**
+ * Deletes the text in a given element leaving it empty
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.delete_text = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).deleteText(), serverResponse);
+ };
+
+ /**
+ * Gets the value of a given attribute in an element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param name
+ * @return {*}
+ */
+ Browser.prototype.attribute = function (serverResponse, page_id, id, name) {
+ return this.serverSendResponse(this.node(page_id, id).getAttribute(name), serverResponse);
+ };
+
+ /**
+ * Allows the possibility to set an attribute on a given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param name
+ * @param value
+ * @returns {*}
+ */
+ Browser.prototype.set_attribute = function (serverResponse, page_id, id, name, value) {
+ return this.serverSendResponse(this.node(page_id, id).setAttribute(name, value), serverResponse);
+ };
+
+ /**
+ * Allows the possibility to remove an attribute on a given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param name
+ * @returns {*}
+ */
+ Browser.prototype.remove_attribute = function (serverResponse, page_id, id, name) {
+ return this.serverSendResponse(this.node(page_id, id).removeAttribute(name), serverResponse);
+ };
+
+ /**
+ * Returns all the attributes of a given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param name
+ * @return {*}
+ */
+ Browser.prototype.attributes = function (serverResponse, page_id, id, name) {
+ return this.serverSendResponse(this.node(page_id, id).getAttributes(), serverResponse);
+ };
+
+ /**
+ * Returns all the way to the document level the parents of a given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.parents = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).parentIds(), serverResponse);
+ };
+
+ /**
+ * Returns the element.value of an element given by its page and id
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.value = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).value(), serverResponse);
+ };
+
+ /**
+ * Sets the element.value of an element by the given value
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param value
+ * @return {*}
+ */
+ Browser.prototype.set = function (serverResponse, page_id, id, value) {
+ this.node(page_id, id).set(value);
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Uploads a file to an input file element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param file_path
+ * @return {*}
+ */
+ Browser.prototype.select_file = function (serverResponse, page_id, id, file_path) {
+ var node = this.node(page_id, id);
+
+ this.currentPage.beforeUpload(node.id);
+ this.currentPage.uploadFile('[_poltergeist_selected]', file_path);
+ this.currentPage.afterUpload(node.id);
+
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Sets a value to the selected element (to be used in select elements)
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param value
+ * @return {*}
+ */
+ Browser.prototype.select = function (serverResponse, page_id, id, value) {
+ return this.serverSendResponse(this.node(page_id, id).select(value), serverResponse);
+ };
+
+ /**
+ * Selects an option with the given value
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param value
+ * @param multiple
+ * @return {*}
+ */
+ Browser.prototype.select_option = function (serverResponse, page_id, id, value, multiple) {
+ return this.serverSendResponse(this.node(page_id, id).select_option(value, multiple), serverResponse);
+ };
+
+ /**
+ *
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.tag_name = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).tagName(), serverResponse);
+ };
+
+
+ /**
+ * Tells if an element is visible or not
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.visible = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).isVisible(), serverResponse);
+ };
+
+ /**
+ * Tells if an element is disabled
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.disabled = function (serverResponse, page_id, id) {
+ return this.serverSendResponse(this.node(page_id, id).isDisabled(), serverResponse);
+ };
+
+ /**
+ * Evaluates a javascript and returns the outcome to the client
+ * This will be JSON response so your script better be returning objects that can be used
+ * in JSON.stringify
+ * @param serverResponse
+ * @param script
+ * @return {*}
+ */
+ Browser.prototype.evaluate = function (serverResponse, script) {
+ return this.serverSendResponse(this.currentPage.evaluate("function() { return " + script + " }"), serverResponse);
+ };
+
+ /**
+ * Executes a javascript and goes back to the client with true if there were no errors
+ * @param serverResponse
+ * @param script
+ * @return {*}
+ */
+ Browser.prototype.execute = function (serverResponse, script) {
+ this.currentPage.execute("function() { " + script + " }");
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * If inside a frame then we will go back to the parent
+ * Not defined behaviour if you pop and are not inside an iframe
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.pop_frame = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.popFrame(), serverResponse);
+ };
+
+ /**
+ * Gets the window handle id by a given window name
+ * @param serverResponse
+ * @param name
+ * @return {*}
+ */
+ Browser.prototype.window_handle = function (serverResponse, name) {
+ var handle, pageByWindowName;
+
+ if (name === null || typeof name == "undefined" || name.length === 0) {
+ return this.serverSendResponse(this.currentPage.handle, serverResponse);
+ }
+
+ handle = null;
+
+ //Lets search the handle by the given window name
+ var filteredPages = this.pages.filter(function (p) {
+ return !p.closed && p.windowName() === name;
+ });
+
+ //A bit of error control is always good
+ if (Array.isArray(filteredPages) && filteredPages.length >= 1) {
+ pageByWindowName = filteredPages[0];
+ } else {
+ pageByWindowName = null;
+ }
+
+ if (pageByWindowName !== null && typeof pageByWindowName != "undefined") {
+ handle = pageByWindowName.handle;
+ }
+
+ return this.serverSendResponse(handle, serverResponse);
+ };
+
+ /**
+ * Returns all the window handles of opened windows
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.window_handles = function (serverResponse) {
+ var handles, filteredPages;
+
+ filteredPages = this.pages.filter(function (p) {
+ return !p.closed;
+ });
+
+ if (filteredPages.length > 0) {
+ handles = filteredPages.map(function (p) {
+ return p.handle;
+ });
+ if (handles.length === 0) {
+ handles = null;
+ }
+ } else {
+ handles = null;
+ }
+
+ return this.serverSendResponse(handles, serverResponse);
+ };
+
+ /**
+ * Tries to switch to a window given by the handle id
+ * @param serverResponse
+ * @param handle
+ * @return {*}
+ */
+ Browser.prototype.switch_to_window = function (serverResponse, handle) {
+ var page;
+ var self = this;
+
+ page = this.getPageByHandle(handle);
+ if (page === null || typeof page == "undefined") {
+ throw new Poltergeist.NoSuchWindowError;
+ }
+
+ if (page !== this.currentPage) {
+ return page.waitState('default', function () {
+ self.currentPage = page;
+ return self.serverSendResponse(true, serverResponse);
+ });
+ }
+
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Opens a new window where we can do stuff
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.open_new_window = function (serverResponse) {
+ return this.execute(serverResponse, 'window.open()');
+ };
+
+ /**
+ * Closes the window given by handle name if possible
+ * @param serverResponse
+ * @param handle
+ * @return {*}
+ */
+ Browser.prototype.close_window = function (serverResponse, handle) {
+ var page;
+
+ page = this.getPageByHandle(handle);
+ if (page === null || typeof page == "undefined") {
+ //TODO: should we throw error since we actually could not find the window?
+ return this.serverSendResponse(false, serverResponse);
+ }
+
+ //TODO: we have to add some control here to actually asses that the release has been done
+ page.release();
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Generic mouse event on an element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param name
+ * @return {number}
+ */
+ Browser.prototype.mouse_event = function (serverResponse, page_id, id, name) {
+ var node;
+ var self = this;
+ node = this.node(page_id, id);
+ this.currentPage.state = 'mouse_event';
+ this.last_mouse_event = node.mouseEvent(name);
+ return setTimeout(function () {
+ if (self.currentPage.state === 'mouse_event') {
+ self.currentPage.state = 'default';
+ return self.serverSendResponse({
+ position: self.last_mouse_event
+ }, serverResponse);
+ } else {
+ return self.currentPage.waitState('default', function () {
+ return self.serverSendResponse({
+ position: self.last_mouse_event
+ }, serverResponse);
+ });
+ }
+ }, 5);
+ };
+
+ /**
+ * Simple click on the element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.click = function (serverResponse, page_id, id) {
+ return this.mouse_event(serverResponse, page_id, id, 'click');
+ };
+
+ /**
+ * Right click on the element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.right_click = function (serverResponse, page_id, id) {
+ return this.mouse_event(serverResponse, page_id, id, 'rightclick');
+ };
+
+ /**
+ * Double click on the element given by page and id
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.double_click = function (serverResponse, page_id, id) {
+ return this.mouse_event(serverResponse, page_id, id, 'doubleclick');
+ };
+
+ /**
+ * Executes a mousemove event on the page and given element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @return {*}
+ */
+ Browser.prototype.hover = function (serverResponse, page_id, id) {
+ return this.mouse_event(serverResponse, page_id, id, 'mousemove');
+ };
+
+ /**
+ * Triggers a mouse click event on the given coordinates
+ * @param serverResponse
+ * @param x
+ * @param y
+ * @return {*}
+ */
+ Browser.prototype.click_coordinates = function (serverResponse, x, y) {
+ var response;
+
+ this.currentPage.sendEvent('click', x, y);
+ response = {
+ click: {
+ x: x,
+ y: y
+ }
+ };
+
+ return this.serverSendResponse(response, serverResponse);
+ };
+
+ /**
+ * Drags one element into another, useful for nice javascript thingies
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param other_id
+ * @return {*}
+ */
+ Browser.prototype.drag = function (serverResponse, page_id, id, other_id) {
+ this.node(page_id, id).dragTo(this.node(page_id, other_id));
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Triggers an event on the given page and element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param event
+ * @return {*}
+ */
+ Browser.prototype.trigger = function (serverResponse, page_id, id, event) {
+ this.node(page_id, id).trigger(event);
+ return this.serverSendResponse(event, serverResponse);
+ };
+
+ /**
+ * Checks if two elements are equal on a dom level
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param other_id
+ * @return {*}
+ */
+ Browser.prototype.equals = function (serverResponse, page_id, id, other_id) {
+ return this.serverSendResponse(this.node(page_id, id).isEqual(this.node(page_id, other_id)), serverResponse);
+ };
+
+ /**
+ * Resets the current page to a clean slate
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.reset = function (serverResponse) {
+ this.resetPage();
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Scrolls to a position given by the left, top coordinates
+ * @param serverResponse
+ * @param left
+ * @param top
+ * @return {*}
+ */
+ Browser.prototype.scroll_to = function (serverResponse, left, top) {
+ this.currentPage.setScrollPosition({
+ left: left,
+ top: top
+ });
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Sends keys to an element simulating as closest as possible what a user would do
+ * when typing
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param keys
+ * @return {*}
+ */
+ Browser.prototype.send_keys = function (serverResponse, page_id, id, keys) {
+ var key, sequence, target, _i, _len;
+ target = this.node(page_id, id);
+ if (!target.containsSelection()) {
+ target.mouseEvent('click');
+ }
+ for (_i = 0, _len = keys.length; _i < _len; _i++) {
+ sequence = keys[_i];
+ key = sequence.key != null ? this.currentPage.keyCode(sequence.key) : sequence;
+ this.currentPage.sendEvent('keypress', key);
+ }
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Sends a native phantomjs key event to element
+ * @param serverResponse
+ * @param page_id
+ * @param id
+ * @param keyEvent
+ * @param key
+ * @param modifier
+ */
+ Browser.prototype.key_event = function (serverResponse, page_id, id, keyEvent, key, modifier) {
+ var keyEventModifierMap;
+ var keyEventModifier;
+ var target;
+
+ keyEventModifierMap = {
+ 'none': 0x0,
+ 'shift': 0x02000000,
+ 'ctrl': 0x04000000,
+ 'alt': 0x08000000,
+ 'meta': 0x10000000
+ };
+ keyEventModifier = keyEventModifierMap[modifier];
+
+ target = this.node(page_id, id);
+ if (!target.containsSelection()) {
+ target.mouseEvent('click');
+ }
+ target.page.sendEvent(keyEvent, key, null, null, keyEventModifier);
+
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Sends the rendered page in a base64 encoding
+ * @param serverResponse
+ * @param format
+ * @param full
+ * @param selector
+ * @return {*}
+ */
+ Browser.prototype.render_base64 = function (serverResponse, format, full, selector) {
+ var encoded_image;
+ if (selector == null) {
+ selector = null;
+ }
+ this.set_clip_rect(full, selector);
+ encoded_image = this.currentPage.renderBase64(format);
+ return this.serverSendResponse(encoded_image, serverResponse);
+ };
+
+ /**
+ * Renders the current page entirely or a given selection
+ * @param serverResponse
+ * @param path
+ * @param full
+ * @param selector
+ * @return {*}
+ */
+ Browser.prototype.render = function (serverResponse, path, full, selector) {
+ var dimensions;
+ if (selector == null) {
+ selector = null;
+ }
+ dimensions = this.set_clip_rect(full, selector);
+ this.currentPage.setScrollPosition({
+ left: 0,
+ top: 0
+ });
+ this.currentPage.render(path);
+ this.currentPage.setScrollPosition({
+ left: dimensions.left,
+ top: dimensions.top
+ });
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+
+ /**
+ * Sets the paper size, useful when printing to PDF
+ * @param serverResponse
+ * @param size
+ * @return {*}
+ */
+ Browser.prototype.set_paper_size = function (serverResponse, size) {
+ this.currentPage.setPaperSize(size);
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Sets the zoom factor on the current page
+ * @param serverResponse
+ * @param zoom_factor
+ * @return {*}
+ */
+ Browser.prototype.set_zoom_factor = function (serverResponse, zoom_factor) {
+ this.currentPage.setZoomFactor(zoom_factor);
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Resizes the browser viewport, useful when testing mobile stuff
+ * @param serverResponse
+ * @param width
+ * @param height
+ * @return {*}
+ */
+ Browser.prototype.resize = function (serverResponse, width, height) {
+ this.currentPage.setViewportSize({
+ width: width,
+ height: height
+ });
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Gets the browser viewport size
+ * Because PhantomJS is headless (nothing is shown)
+ * viewportSize effectively simulates the size of the window like in a traditional browser.
+ * @param serverResponse
+ * @param handle
+ * @return {*}
+ */
+ Browser.prototype.window_size = function (serverResponse, handle) {
+ //TODO: add support for window handles
+ return this.serverSendResponse(this.currentPage.viewportSize(), serverResponse);
+ };
+
+ /**
+ * Returns the network traffic that the current page has generated
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.network_traffic = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.networkTraffic(), serverResponse);
+ };
+
+ /**
+ * Clears the accumulated network_traffic in the current page
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.clear_network_traffic = function (serverResponse) {
+ this.currentPage.clearNetworkTraffic();
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Gets the headers of the current page
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.get_headers = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.getCustomHeaders(), serverResponse);
+ };
+
+ /**
+ * Set headers in the browser
+ * @param serverResponse
+ * @param headers
+ * @return {*}
+ */
+ Browser.prototype.set_headers = function (serverResponse, headers) {
+ if (headers['User-Agent']) {
+ this.currentPage.setUserAgent(headers['User-Agent']);
+ }
+ this.currentPage.setCustomHeaders(headers);
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Given an array of headers, adds them to the page
+ * @param serverResponse
+ * @param headers
+ * @return {*}
+ */
+ Browser.prototype.add_headers = function (serverResponse, headers) {
+ var allHeaders, name, value;
+ allHeaders = this.currentPage.getCustomHeaders();
+ for (name in headers) {
+ if (headers.hasOwnProperty(name)) {
+ value = headers[name];
+ allHeaders[name] = value;
+ }
+ }
+ return this.set_headers(serverResponse, allHeaders);
+ };
+
+ /**
+ * Adds a header to the page temporary or permanently
+ * @param serverResponse
+ * @param header
+ * @param permanent
+ * @return {*}
+ */
+ Browser.prototype.add_header = function (serverResponse, header, permanent) {
+ if (!permanent) {
+ this.currentPage.addTempHeader(header);
+ }
+ return this.add_headers(serverResponse, header);
+ };
+
+
+ /**
+ * Sends back the client the response headers sent from the browser when making
+ * the page request
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.response_headers = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.responseHeaders(), serverResponse);
+ };
+
+ /**
+ * Returns the cookies of the current page being browsed
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.cookies = function (serverResponse) {
+ return this.serverSendResponse(this.currentPage.cookies(), serverResponse);
+ };
+
+ /**
+ * Sets a cookie in the browser, the format of the cookies has to be the format it says
+ * on phantomjs documentation and as such you can set it in other domains, not on the
+ * current page
+ * @param serverResponse
+ * @param cookie
+ * @return {*}
+ */
+ Browser.prototype.set_cookie = function (serverResponse, cookie) {
+ return this.serverSendResponse(phantom.addCookie(cookie), serverResponse);
+ };
+
+ /**
+ * Remove a cookie set on the current page
+ * @param serverResponse
+ * @param name
+ * @return {*}
+ */
+ Browser.prototype.remove_cookie = function (serverResponse, name) {
+ //TODO: add error control to check if the cookie was properly deleted
+ this.currentPage.deleteCookie(name);
+ phantom.deleteCookie(name);
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Clear the cookies in the browser
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.clear_cookies = function (serverResponse) {
+ phantom.clearCookies();
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Enables / Disables the cookies on the browser
+ * @param serverResponse
+ * @param flag
+ * @return {*}
+ */
+ Browser.prototype.cookies_enabled = function (serverResponse, flag) {
+ phantom.cookiesEnabled = flag;
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * US19: DONE
+ * Sets a basic authentication credential to access a page
+ * THIS SHOULD BE USED BEFORE accessing a page
+ * @param serverResponse
+ * @param user
+ * @param password
+ * @return {*}
+ */
+ Browser.prototype.set_http_auth = function (serverResponse, user, password) {
+ this.currentPage.setHttpAuth(user, password);
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Sets the flag whether to fail on javascript errors or not.
+ * @param serverResponse
+ * @param value
+ * @return {*}
+ */
+ Browser.prototype.set_js_errors = function (serverResponse, value) {
+ this.js_errors = value;
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Sets the debug mode to boolean value
+ * @param serverResponse
+ * @param value
+ * @return {*}
+ */
+ Browser.prototype.set_debug = function (serverResponse, value) {
+ this._debug = value;
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Goes back in the history when possible
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.go_back = function (serverResponse) {
+ var self = this;
+ if (this.currentPage.canGoBack()) {
+ this.currentPage.state = 'loading';
+ this.currentPage.goBack();
+ return this.currentPage.waitState('default', function () {
+ return self.serverSendResponse(true, serverResponse);
+ });
+ } else {
+ return this.serverSendResponse(false, serverResponse);
+ }
+ };
+
+ /**
+ * Reloads the page if possible
+ * @return {*}
+ */
+ Browser.prototype.reload = function (serverResponse) {
+ var self = this;
+ this.currentPage.state = 'loading';
+ this.currentPage.reload();
+ return this.currentPage.waitState('default', function () {
+ return self.serverSendResponse(true, serverResponse);
+ });
+ };
+
+ /**
+ * Goes forward in the browser history if possible
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.go_forward = function (serverResponse) {
+ var self = this;
+ if (this.currentPage.canGoForward()) {
+ this.currentPage.state = 'loading';
+ this.currentPage.goForward();
+ return this.currentPage.waitState('default', function () {
+ return self.serverSendResponse(true, serverResponse);
+ });
+ } else {
+ return this.serverSendResponse(false, serverResponse);
+ }
+ };
+
+ /**
+ * Sets the urlBlacklist for the given urls as parameters
+ * @return {boolean}
+ */
+ Browser.prototype.set_url_blacklist = function (serverResponse, blackList) {
+ this.currentPage.urlBlacklist = Array.prototype.slice.call(blackList);
+ return this.serverSendResponse(true, serverResponse);
+ };
+
+ /**
+ * Runs a browser command and returns the response back to the client
+ * when the command has finished the execution
+ * @param command
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.serverRunCommand = function (command, serverResponse) {
+ var commandData;
+ var commandArgs;
+ var commandName;
+
+ commandName = command.name;
+ commandArgs = command.args;
+ this.currentPage.state = 'default';
+ commandData = [serverResponse].concat(commandArgs);
+
+ if (typeof this[commandName] !== "function") {
+ //We can not run such command
+ throw new Poltergeist.Error();
+ }
+
+ return this[commandName].apply(this, commandData);
+ };
+
+ /**
+ * Sends a response back to the client who made the request
+ * @param response
+ * @param serverResponse
+ * @return {*}
+ */
+ Browser.prototype.serverSendResponse = function (response, serverResponse) {
+ var errors;
+ errors = this.currentPage.errors;
+ this.currentPage.clearErrors();
+ if (errors.length > 0 && this.js_errors) {
+ return this.owner.serverSendError(new Poltergeist.JavascriptError(errors), serverResponse);
+ } else {
+ return this.owner.serverSendResponse(response, serverResponse);
+ }
+ };
+
+ return Browser;
+
+})();
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/main.js b/vendor/jcalderonzumba/gastonjs/src/Client/main.js
new file mode 100644
index 000000000..a8f2ecb6d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/main.js
@@ -0,0 +1,29 @@
+var Poltergeist, system, _ref, _ref1, _ref2;
+
+//Inheritance tool
+phantom.injectJs("" + phantom.libraryPath + "/Tools/inherit.js");
+
+//Poltergeist main object
+phantom.injectJs("" + phantom.libraryPath + "/poltergeist.js");
+
+//Errors that are controller in the poltergeist code
+phantom.injectJs("" + phantom.libraryPath + "/Errors/error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/obsolete_node.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/invalid_selector.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/frame_not_found.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/mouse_event_failed.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/javascript_error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/browser_error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/status_fail_error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/no_such_window_error.js");
+
+//web server to control the commands
+phantom.injectJs("" + phantom.libraryPath + "/Server/server.js");
+
+phantom.injectJs("" + phantom.libraryPath + "/web_page.js");
+phantom.injectJs("" + phantom.libraryPath + "/node.js");
+phantom.injectJs("" + phantom.libraryPath + "/browser.js");
+
+system = require('system');
+
+new Poltergeist(system.args[1], system.args[2], system.args[3]);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/node.js b/vendor/jcalderonzumba/gastonjs/src/Client/node.js
new file mode 100644
index 000000000..bdf5baf23
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/node.js
@@ -0,0 +1,161 @@
+var __slice = [].slice;
+
+Poltergeist.Node = (function () {
+ var name, _fn, _i, _len, _ref;
+ var xpathStringLiteral;
+
+ Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'checked',
+ 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple',
+ 'select', 'tagName', 'find', 'getAttributes', 'isVisible',
+ 'position', 'trigger', 'input', 'parentId', 'parentIds', 'mouseEventTest',
+ 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'selectRadioValue',
+ 'containsSelection', 'allHTML', 'changed', 'getXPathForElement', 'deselectAllOptions'];
+
+ function Node(page, id) {
+ this.page = page;
+ this.id = id;
+ }
+
+ /**
+ * Returns the parent Node of this Node
+ * @return {Poltergeist.Node}
+ */
+ Node.prototype.parent = function () {
+ return new Poltergeist.Node(this.page, this.parentId());
+ };
+
+ _ref = Node.DELEGATES;
+
+ _fn = function (name) {
+ return Node.prototype[name] = function () {
+ var args = [];
+ if (arguments.length >= 1) {
+ args = __slice.call(arguments, 0)
+ }
+ return this.page.nodeCall(this.id, name, args);
+ };
+ };
+
+ //Adding all the delegates from the agent Node to this Node
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ name = _ref[_i];
+ _fn(name);
+ }
+
+ xpathStringLiteral = function (s) {
+ if (s.indexOf('"') === -1)
+ return '"' + s + '"';
+ if (s.indexOf("'") === -1)
+ return "'" + s + "'";
+ return 'concat("' + s.replace(/"/g, '",\'"\',"') + '")';
+ };
+
+ /**
+ * Gets an x,y position tailored for mouse event actions
+ * @return {{x, y}}
+ */
+ Node.prototype.mouseEventPosition = function () {
+ var middle, pos, viewport;
+
+ viewport = this.page.viewportSize();
+ pos = this.position();
+ middle = function (start, end, size) {
+ return start + ((Math.min(end, size) - start) / 2);
+ };
+
+ return {
+ x: middle(pos.left, pos.right, viewport.width),
+ y: middle(pos.top, pos.bottom, viewport.height)
+ };
+ };
+
+ /**
+ * Executes a phantomjs native mouse event
+ * @param name
+ * @return {{x, y}}
+ */
+ Node.prototype.mouseEvent = function (name) {
+ var pos, test;
+
+ this.scrollIntoView();
+ pos = this.mouseEventPosition();
+ test = this.mouseEventTest(pos.x, pos.y);
+
+ if (test.status === 'success') {
+ if (name === 'rightclick') {
+ this.page.mouseEvent('click', pos.x, pos.y, 'right');
+ this.trigger('contextmenu');
+ } else {
+ this.page.mouseEvent(name, pos.x, pos.y);
+ }
+ return pos;
+ } else {
+ throw new Poltergeist.MouseEventFailed(name, test.selector, pos);
+ }
+ };
+
+ /**
+ * Executes a mouse based drag from one node to another
+ * @param other
+ * @return {{x, y}}
+ */
+ Node.prototype.dragTo = function (other) {
+ var otherPosition, position;
+
+ this.scrollIntoView();
+ position = this.mouseEventPosition();
+ otherPosition = other.mouseEventPosition();
+ this.page.mouseEvent('mousedown', position.x, position.y);
+ return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y);
+ };
+
+ /**
+ * Checks if one node is equal to another
+ * @param other
+ * @return {boolean}
+ */
+ Node.prototype.isEqual = function (other) {
+ return this.page === other.page && this.isDOMEqual(other.id);
+ };
+
+
+ /**
+ * The value to select
+ * @param value
+ * @param multiple
+ */
+ Node.prototype.select_option = function (value, multiple) {
+ var tagName = this.tagName().toLowerCase();
+
+ if (tagName === "select") {
+ var escapedOption = xpathStringLiteral(value);
+ // The value of an option is the normalized version of its text when it has no value attribute
+ var optionQuery = ".//option[@value = " + escapedOption + " or (not(@value) and normalize-space(.) = " + escapedOption + ")]";
+ var ids = this.find("xpath", optionQuery);
+ var polterNode = this.page.get(ids[0]);
+
+ if (multiple || !this.getAttribute('multiple')) {
+ if (!polterNode.getAttribute('selected')) {
+ polterNode.select(value);
+ this.trigger('click');
+ this.input();
+ }
+ return true;
+ }
+
+ this.deselectAllOptions();
+ polterNode.select(value);
+ this.trigger('click');
+ this.input();
+ return true;
+ } else if (tagName === "input" && this.getAttribute("type").toLowerCase() === "radio") {
+ return this.selectRadioValue(value);
+ }
+
+ throw new Poltergeist.BrowserError("The element is not a select or radio input");
+
+ };
+
+ return Node;
+
+}).call(this);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/poltergeist.js b/vendor/jcalderonzumba/gastonjs/src/Client/poltergeist.js
new file mode 100644
index 000000000..20f026769
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/poltergeist.js
@@ -0,0 +1,77 @@
+Poltergeist = (function () {
+
+ /**
+ * The MAIN class of the project
+ * @param port
+ * @param width
+ * @param height
+ * @constructor
+ */
+ function Poltergeist(port, width, height) {
+ var self;
+ this.browser = new Poltergeist.Browser(this, width, height);
+
+ this.commandServer = new Poltergeist.Server(this, port);
+ this.commandServer.start();
+
+ self = this;
+
+ phantom.onError = function (message, stack) {
+ return self.onError(message, stack);
+ };
+
+ this.running = false;
+ }
+
+ /**
+ * Tries to execute a command send by a client and returns the command response
+ * or error if something happened
+ * @param command
+ * @param serverResponse
+ * @return {boolean}
+ */
+ Poltergeist.prototype.serverRunCommand = function (command, serverResponse) {
+ var error;
+ this.running = true;
+ try {
+ return this.browser.serverRunCommand(command, serverResponse);
+ } catch (_error) {
+ error = _error;
+ if (error instanceof Poltergeist.Error) {
+ return this.serverSendError(error, serverResponse);
+ }
+ return this.serverSendError(new Poltergeist.BrowserError(error.toString(), error.stack), serverResponse);
+ }
+ };
+
+ /**
+ * Sends error back to the client
+ * @param error
+ * @param serverResponse
+ * @return {boolean}
+ */
+ Poltergeist.prototype.serverSendError = function (error, serverResponse) {
+ var errorObject;
+ errorObject = {
+ error: {
+ name: error.name || 'Generic',
+ args: error.args && error.args() || [error.toString()]
+ }
+ };
+ return this.commandServer.sendError(serverResponse, 500, errorObject);
+ };
+
+ /**
+ * Send the response back to the client
+ * @param response Data to send to the client
+ * @param serverResponse Phantomjs response object associated to the client request
+ * @return {boolean}
+ */
+ Poltergeist.prototype.serverSendResponse = function (response, serverResponse) {
+ return this.commandServer.send(serverResponse, {response: response});
+ };
+
+ return Poltergeist;
+})();
+
+window.Poltergeist = Poltergeist;
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/web_page.js b/vendor/jcalderonzumba/gastonjs/src/Client/web_page.js
new file mode 100644
index 000000000..c275b03b7
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/web_page.js
@@ -0,0 +1,829 @@
+var __slice = [].slice;
+var __indexOf = [].indexOf || function (item) {
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (i in this && this[i] === item) return i;
+ }
+ return -1;
+ };
+
+Poltergeist.WebPage = (function () {
+ var command, delegate, commandFunctionBind, delegateFunctionBind, i, j, commandsLength, delegatesRefLength, commandsRef, delegatesRef,
+ _this = this;
+
+ //Native or not webpage callbacks
+ WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested',
+ 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing'];
+
+ // Delegates the execution to the phantomjs page native functions but directly available in the WebPage object
+ WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward', 'reload'];
+
+ //Commands to execute on behalf of the browser but on the current page
+ WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload', 'clearLocalStorage'];
+
+ WebPage.EXTENSIONS = [];
+
+ function WebPage(nativeWebPage) {
+ var callback, i, callBacksLength, callBacksRef;
+
+ //Lets create the native phantomjs webpage
+ if (nativeWebPage === null || typeof nativeWebPage == "undefined") {
+ this._native = require('webpage').create();
+ } else {
+ this._native = nativeWebPage;
+ }
+
+ this.id = 0;
+ this.source = null;
+ this.closed = false;
+ this.state = 'default';
+ this.urlBlacklist = [];
+ this.frames = [];
+ this.errors = [];
+ this._networkTraffic = {};
+ this._tempHeaders = {};
+ this._blockedUrls = [];
+
+ callBacksRef = WebPage.CALLBACKS;
+ for (i = 0, callBacksLength = callBacksRef.length; i < callBacksLength; i++) {
+ callback = callBacksRef[i];
+ this.bindCallback(callback);
+ }
+ }
+
+ //Bind the commands we can run from the browser to the current page
+ commandsRef = WebPage.COMMANDS;
+ commandFunctionBind = function (command) {
+ return WebPage.prototype[command] = function () {
+ var args;
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
+ return this.runCommand(command, args);
+ };
+ };
+ for (i = 0, commandsLength = commandsRef.length; i < commandsLength; i++) {
+ command = commandsRef[i];
+ commandFunctionBind(command);
+ }
+
+ //Delegates bind applications
+ delegatesRef = WebPage.DELEGATES;
+ delegateFunctionBind = function (delegate) {
+ return WebPage.prototype[delegate] = function () {
+ return this._native[delegate].apply(this._native, arguments);
+ };
+ };
+ for (j = 0, delegatesRefLength = delegatesRef.length; j < delegatesRefLength; j++) {
+ delegate = delegatesRef[j];
+ delegateFunctionBind(delegate);
+ }
+
+ /**
+ * This callback is invoked after the web page is created but before a URL is loaded.
+ * The callback may be used to change global objects.
+ * @return {*}
+ */
+ WebPage.prototype.onInitializedNative = function () {
+ this.id += 1;
+ this.source = null;
+ this.injectAgent();
+ this.removeTempHeaders();
+ return this.setScrollPosition({
+ left: 0,
+ top: 0
+ });
+ };
+
+ /**
+ * This callback is invoked when the WebPage object is being closed,
+ * either via page.close in the PhantomJS outer space or via window.close in the page's client-side.
+ * @return {boolean}
+ */
+ WebPage.prototype.onClosingNative = function () {
+ this.handle = null;
+ return this.closed = true;
+ };
+
+ /**
+ * This callback is invoked when there is a JavaScript console message on the web page.
+ * The callback may accept up to three arguments: the string for the message, the line number, and the source identifier.
+ * @param message
+ * @param line
+ * @param sourceId
+ * @return {boolean}
+ */
+ WebPage.prototype.onConsoleMessageNative = function (message, line, sourceId) {
+ if (message === '__DOMContentLoaded') {
+ this.source = this._native.content;
+ return false;
+ }
+ console.log(message);
+ return true;
+ };
+
+ /**
+ * This callback is invoked when the page starts the loading. There is no argument passed to the callback.
+ * @return {number}
+ */
+ WebPage.prototype.onLoadStartedNative = function () {
+ this.state = 'loading';
+ return this.requestId = this.lastRequestId;
+ };
+
+ /**
+ * This callback is invoked when the page finishes the loading.
+ * It may accept a single argument indicating the page's status: 'success' if no network errors occurred, otherwise 'fail'.
+ * @param status
+ * @return {string}
+ */
+ WebPage.prototype.onLoadFinishedNative = function (status) {
+ this.status = status;
+ this.state = 'default';
+
+ if (this.source === null || typeof this.source == "undefined") {
+ this.source = this._native.content;
+ } else {
+ this.source = this._native.content;
+ }
+
+ return this.source;
+ };
+
+ /**
+ * This callback is invoked when there is a JavaScript execution error.
+ * It is a good way to catch problems when evaluating a script in the web page context.
+ * The arguments passed to the callback are the error message and the stack trace [as an Array].
+ * @param message
+ * @param stack
+ * @return {Number}
+ */
+ WebPage.prototype.onErrorNative = function (message, stack) {
+ var stackString;
+
+ stackString = message;
+ stack.forEach(function (frame) {
+ stackString += "\n";
+ stackString += " at " + frame.file + ":" + frame.line;
+ if (frame["function"] && frame["function"] !== '') {
+ return stackString += " in " + frame["function"];
+ }
+ });
+
+ return this.errors.push({
+ message: message,
+ stack: stackString
+ });
+ };
+
+ /**
+ * This callback is invoked when the page requests a resource.
+ * The first argument to the callback is the requestData metadata object.
+ * The second argument is the networkRequest object itself.
+ * @param requestData
+ * @param networkRequest
+ * @return {*}
+ */
+ WebPage.prototype.onResourceRequestedNative = function (requestData, networkRequest) {
+ var abort;
+
+ abort = this.urlBlacklist.some(function (blacklistedUrl) {
+ return requestData.url.indexOf(blacklistedUrl) !== -1;
+ });
+
+ if (abort) {
+ if (this._blockedUrls.indexOf(requestData.url) === -1) {
+ this._blockedUrls.push(requestData.url);
+ }
+ //TODO: check this, as it raises onResourceError
+ return networkRequest.abort();
+ }
+
+ this.lastRequestId = requestData.id;
+ if (requestData.url === this.redirectURL) {
+ this.redirectURL = null;
+ this.requestId = requestData.id;
+ }
+
+ return this._networkTraffic[requestData.id] = {
+ request: requestData,
+ responseParts: []
+ };
+ };
+
+ /**
+ * This callback is invoked when a resource requested by the page is received.
+ * The only argument to the callback is the response metadata object.
+ * @param response
+ * @return {*}
+ */
+ WebPage.prototype.onResourceReceivedNative = function (response) {
+ var networkTrafficElement;
+
+ if ((networkTrafficElement = this._networkTraffic[response.id]) != null) {
+ networkTrafficElement.responseParts.push(response);
+ }
+
+ if (this.requestId === response.id) {
+ if (response.redirectURL) {
+ return this.redirectURL = response.redirectURL;
+ }
+
+ this.statusCode = response.status;
+ return this._responseHeaders = response.headers;
+ }
+ };
+
+ /**
+ * Inject the poltergeist agent into the webpage
+ * @return {Array}
+ */
+ WebPage.prototype.injectAgent = function () {
+ var extension, isAgentInjected, i, extensionsRefLength, extensionsRef, injectionResults;
+
+ isAgentInjected = this["native"]().evaluate(function () {
+ return typeof window.__poltergeist;
+ });
+
+ if (isAgentInjected === "undefined") {
+ this["native"]().injectJs("" + phantom.libraryPath + "/agent.js");
+ extensionsRef = WebPage.EXTENSIONS;
+ injectionResults = [];
+ for (i = 0, extensionsRefLength = extensionsRef.length; i < extensionsRefLength; i++) {
+ extension = extensionsRef[i];
+ injectionResults.push(this["native"]().injectJs(extension));
+ }
+ return injectionResults;
+ }
+ };
+
+ /**
+ * Injects a Javascript file extension into the
+ * @param file
+ * @return {*}
+ */
+ WebPage.prototype.injectExtension = function (file) {
+ //TODO: add error control, for example, check if file already in the extensions array, check if the file exists, etc.
+ WebPage.EXTENSIONS.push(file);
+ return this["native"]().injectJs(file);
+ };
+
+ /**
+ * Returns the native phantomjs webpage object
+ * @return {*}
+ */
+ WebPage.prototype["native"] = function () {
+ if (this.closed) {
+ throw new Poltergeist.NoSuchWindowError;
+ }
+
+ return this._native;
+ };
+
+ /**
+ * Returns the current page window name
+ * @return {*}
+ */
+ WebPage.prototype.windowName = function () {
+ return this["native"]().windowName;
+ };
+
+ /**
+ * Returns the keyCode of a given key as set in the phantomjs values
+ * @param name
+ * @return {number}
+ */
+ WebPage.prototype.keyCode = function (name) {
+ return this["native"]().event.key[name];
+ };
+
+ /**
+ * Waits for the page to reach a certain state
+ * @param state
+ * @param callback
+ * @return {*}
+ */
+ WebPage.prototype.waitState = function (state, callback) {
+ var self = this;
+ if (this.state === state) {
+ return callback.call();
+ } else {
+ return setTimeout((function () {
+ return self.waitState(state, callback);
+ }), 100);
+ }
+ };
+
+ /**
+ * Sets the browser header related to basic authentication protocol
+ * @param user
+ * @param password
+ * @return {boolean}
+ */
+ WebPage.prototype.setHttpAuth = function (user, password) {
+ var allHeaders = this.getCustomHeaders();
+
+ if (user === false || password === false) {
+ if (allHeaders.hasOwnProperty("Authorization")) {
+ delete allHeaders["Authorization"];
+ }
+ this.setCustomHeaders(allHeaders);
+ return true;
+ }
+
+ var userName = user || "";
+ var userPassword = password || "";
+
+ allHeaders["Authorization"] = "Basic " + btoa(userName + ":" + userPassword);
+ this.setCustomHeaders(allHeaders);
+ return true;
+ };
+
+ /**
+ * Returns all the network traffic associated to the rendering of this page
+ * @return {{}}
+ */
+ WebPage.prototype.networkTraffic = function () {
+ return this._networkTraffic;
+ };
+
+ /**
+ * Clears all the recorded network traffic related to the current page
+ * @return {{}}
+ */
+ WebPage.prototype.clearNetworkTraffic = function () {
+ return this._networkTraffic = {};
+ };
+
+ /**
+ * Returns the blocked urls that the page will not load
+ * @return {Array}
+ */
+ WebPage.prototype.blockedUrls = function () {
+ return this._blockedUrls;
+ };
+
+ /**
+ * Clean all the urls that should not be loaded
+ * @return {Array}
+ */
+ WebPage.prototype.clearBlockedUrls = function () {
+ return this._blockedUrls = [];
+ };
+
+ /**
+ * This property stores the content of the web page's currently active frame
+ * (which may or may not be the main frame), enclosed in an HTML/XML element.
+ * @return {string}
+ */
+ WebPage.prototype.content = function () {
+ return this["native"]().frameContent;
+ };
+
+ /**
+ * Returns the current active frame title
+ * @return {string}
+ */
+ WebPage.prototype.title = function () {
+ return this["native"]().frameTitle;
+ };
+
+ /**
+ * Returns if possible the frame url of the frame given by name
+ * @param frameName
+ * @return {string}
+ */
+ WebPage.prototype.frameUrl = function (frameName) {
+ var query;
+
+ query = function (frameName) {
+ var iframeReference;
+ if ((iframeReference = document.querySelector("iframe[name='" + frameName + "']")) != null) {
+ return iframeReference.src;
+ }
+ return void 0;
+ };
+
+ return this.evaluate(query, frameName);
+ };
+
+ /**
+ * Remove the errors caught on the page
+ * @return {Array}
+ */
+ WebPage.prototype.clearErrors = function () {
+ return this.errors = [];
+ };
+
+ /**
+ * Returns the response headers associated to this page
+ * @return {{}}
+ */
+ WebPage.prototype.responseHeaders = function () {
+ var headers;
+ headers = {};
+ this._responseHeaders.forEach(function (item) {
+ return headers[item.name] = item.value;
+ });
+ return headers;
+ };
+
+ /**
+ * Get Cookies visible to the current URL (though, for setting, use of page.addCookie is preferred).
+ * This array will be pre-populated by any existing Cookie data visible to this URL that is stored in the CookieJar, if any.
+ * @return {*}
+ */
+ WebPage.prototype.cookies = function () {
+ return this["native"]().cookies;
+ };
+
+ /**
+ * Delete any Cookies visible to the current URL with a 'name' property matching cookieName.
+ * Returns true if successfully deleted, otherwise false.
+ * @param name
+ * @return {*}
+ */
+ WebPage.prototype.deleteCookie = function (name) {
+ return this["native"]().deleteCookie(name);
+ };
+
+ /**
+ * This property gets the size of the viewport for the layout process.
+ * @return {*}
+ */
+ WebPage.prototype.viewportSize = function () {
+ return this["native"]().viewportSize;
+ };
+
+ /**
+ * This property sets the size of the viewport for the layout process.
+ * @param size
+ * @return {*}
+ */
+ WebPage.prototype.setViewportSize = function (size) {
+ return this["native"]().viewportSize = size;
+ };
+
+ /**
+ * This property specifies the scaling factor for the page.render and page.renderBase64 functions.
+ * @param zoomFactor
+ * @return {*}
+ */
+ WebPage.prototype.setZoomFactor = function (zoomFactor) {
+ return this["native"]().zoomFactor = zoomFactor;
+ };
+
+ /**
+ * This property defines the size of the web page when rendered as a PDF.
+ * See: http://phantomjs.org/api/webpage/property/paper-size.html
+ * @param size
+ * @return {*}
+ */
+ WebPage.prototype.setPaperSize = function (size) {
+ return this["native"]().paperSize = size;
+ };
+
+ /**
+ * This property gets the scroll position of the web page.
+ * @return {*}
+ */
+ WebPage.prototype.scrollPosition = function () {
+ return this["native"]().scrollPosition;
+ };
+
+ /**
+ * This property defines the scroll position of the web page.
+ * @param pos
+ * @return {*}
+ */
+ WebPage.prototype.setScrollPosition = function (pos) {
+ return this["native"]().scrollPosition = pos;
+ };
+
+
+ /**
+ * This property defines the rectangular area of the web page to be rasterized when page.render is invoked.
+ * If no clipping rectangle is set, page.render will process the entire web page.
+ * @return {*}
+ */
+ WebPage.prototype.clipRect = function () {
+ return this["native"]().clipRect;
+ };
+
+ /**
+ * This property defines the rectangular area of the web page to be rasterized when page.render is invoked.
+ * If no clipping rectangle is set, page.render will process the entire web page.
+ * @param rect
+ * @return {*}
+ */
+ WebPage.prototype.setClipRect = function (rect) {
+ return this["native"]().clipRect = rect;
+ };
+
+ /**
+ * Returns the size of an element given by a selector and its position relative to the viewport.
+ * @param selector
+ * @return {Object}
+ */
+ WebPage.prototype.elementBounds = function (selector) {
+ return this["native"]().evaluate(function (selector) {
+ return document.querySelector(selector).getBoundingClientRect();
+ }, selector);
+ };
+
+ /**
+ * Defines the user agent sent to server when the web page requests resources.
+ * @param userAgent
+ * @return {*}
+ */
+ WebPage.prototype.setUserAgent = function (userAgent) {
+ return this["native"]().settings.userAgent = userAgent;
+ };
+
+ /**
+ * Returns the additional HTTP request headers that will be sent to the server for EVERY request.
+ * @return {{}}
+ */
+ WebPage.prototype.getCustomHeaders = function () {
+ return this["native"]().customHeaders;
+ };
+
+ /**
+ * Gets the additional HTTP request headers that will be sent to the server for EVERY request.
+ * @param headers
+ * @return {*}
+ */
+ WebPage.prototype.setCustomHeaders = function (headers) {
+ return this["native"]().customHeaders = headers;
+ };
+
+ /**
+ * Adds a one time only request header, after being used it will be deleted
+ * @param header
+ * @return {Array}
+ */
+ WebPage.prototype.addTempHeader = function (header) {
+ var name, value, tempHeaderResult;
+ tempHeaderResult = [];
+ for (name in header) {
+ if (header.hasOwnProperty(name)) {
+ value = header[name];
+ tempHeaderResult.push(this._tempHeaders[name] = value);
+ }
+ }
+ return tempHeaderResult;
+ };
+
+ /**
+ * Remove the temporary headers we have set via addTempHeader
+ * @return {*}
+ */
+ WebPage.prototype.removeTempHeaders = function () {
+ var allHeaders, name, value, tempHeadersRef;
+ allHeaders = this.getCustomHeaders();
+ tempHeadersRef = this._tempHeaders;
+ for (name in tempHeadersRef) {
+ if (tempHeadersRef.hasOwnProperty(name)) {
+ value = tempHeadersRef[name];
+ delete allHeaders[name];
+ }
+ }
+
+ return this.setCustomHeaders(allHeaders);
+ };
+
+ /**
+ * If possible switch to the frame given by name
+ * @param name
+ * @return {boolean}
+ */
+ WebPage.prototype.pushFrame = function (name) {
+ if (this["native"]().switchToFrame(name)) {
+ this.frames.push(name);
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * Switch to parent frame, use with caution:
+ * popFrame assumes you are in frame, pop frame not being in a frame
+ * leaves unexpected behaviour
+ * @return {*}
+ */
+ WebPage.prototype.popFrame = function () {
+ //TODO: add some error control here, some way to check we are in a frame or not
+ this.frames.pop();
+ return this["native"]().switchToParentFrame();
+ };
+
+ /**
+ * Returns the webpage dimensions
+ * @return {{top: *, bottom: *, left: *, right: *, viewport: *, document: {height: number, width: number}}}
+ */
+ WebPage.prototype.dimensions = function () {
+ var scroll, viewport;
+ scroll = this.scrollPosition();
+ viewport = this.viewportSize();
+ return {
+ top: scroll.top,
+ bottom: scroll.top + viewport.height,
+ left: scroll.left,
+ right: scroll.left + viewport.width,
+ viewport: viewport,
+ document: this.documentSize()
+ };
+ };
+
+ /**
+ * Returns webpage dimensions that are valid
+ * @return {{top: *, bottom: *, left: *, right: *, viewport: *, document: {height: number, width: number}}}
+ */
+ WebPage.prototype.validatedDimensions = function () {
+ var dimensions, documentDimensions;
+
+ dimensions = this.dimensions();
+ documentDimensions = dimensions.document;
+
+ if (dimensions.right > documentDimensions.width) {
+ dimensions.left = Math.max(0, dimensions.left - (dimensions.right - documentDimensions.width));
+ dimensions.right = documentDimensions.width;
+ }
+
+ if (dimensions.bottom > documentDimensions.height) {
+ dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - documentDimensions.height));
+ dimensions.bottom = documentDimensions.height;
+ }
+
+ this.setScrollPosition({
+ left: dimensions.left,
+ top: dimensions.top
+ });
+
+ return dimensions;
+ };
+
+ /**
+ * Returns a Poltergeist.Node given by an id
+ * @param id
+ * @return {Poltergeist.Node}
+ */
+ WebPage.prototype.get = function (id) {
+ return new Poltergeist.Node(this, id);
+ };
+
+ /**
+ * Executes a phantomjs mouse event, for more info check: http://phantomjs.org/api/webpage/method/send-event.html
+ * @param name
+ * @param x
+ * @param y
+ * @param button
+ * @return {*}
+ */
+ WebPage.prototype.mouseEvent = function (name, x, y, button) {
+ if (button == null) {
+ button = 'left';
+ }
+ this.sendEvent('mousemove', x, y);
+ return this.sendEvent(name, x, y, button);
+ };
+
+ /**
+ * Evaluates a javascript and returns the evaluation of such script
+ * @return {*}
+ */
+ WebPage.prototype.evaluate = function () {
+ var args, fn;
+ fn = arguments[0];
+ args = [];
+
+ if (2 <= arguments.length) {
+ args = __slice.call(arguments, 1);
+ }
+
+ this.injectAgent();
+ return JSON.parse(this.sanitize(this["native"]().evaluate("function() { return PoltergeistAgent.stringify(" + (this.stringifyCall(fn, args)) + ") }")));
+ };
+
+ /**
+ * Does some string sanitation prior parsing
+ * @param potentialString
+ * @return {*}
+ */
+ WebPage.prototype.sanitize = function (potentialString) {
+ if (typeof potentialString === "string") {
+ return potentialString.replace("\n", "\\n").replace("\r", "\\r");
+ }
+
+ return potentialString;
+ };
+
+ /**
+ * Executes a script into the current page scope
+ * @param script
+ * @return {*}
+ */
+ WebPage.prototype.executeScript = function (script) {
+ return this["native"]().evaluateJavaScript(script);
+ };
+
+ /**
+ * Executes a script via phantomjs evaluation
+ * @return {*}
+ */
+ WebPage.prototype.execute = function () {
+ var args, fn;
+
+ fn = arguments[0];
+ args = [];
+
+ if (2 <= arguments.length) {
+ args = __slice.call(arguments, 1);
+ }
+
+ return this["native"]().evaluate("function() { " + (this.stringifyCall(fn, args)) + " }");
+ };
+
+ /**
+ * Helper methods to do script evaluation and execution
+ * @param fn
+ * @param args
+ * @return {string}
+ */
+ WebPage.prototype.stringifyCall = function (fn, args) {
+ if (args.length === 0) {
+ return "(" + (fn.toString()) + ")()";
+ }
+
+ return "(" + (fn.toString()) + ").apply(this, JSON.parse(" + (JSON.stringify(JSON.stringify(args))) + "))";
+ };
+
+ /**
+ * Binds callbacks to their respective Native implementations
+ * @param name
+ * @return {Function}
+ */
+ WebPage.prototype.bindCallback = function (name) {
+ var self;
+ self = this;
+
+ return this["native"]()[name] = function () {
+ var result;
+ if (self[name + 'Native'] != null) {
+ result = self[name + 'Native'].apply(self, arguments);
+ }
+ if (result !== false && (self[name] != null)) {
+ return self[name].apply(self, arguments);
+ }
+ };
+ };
+
+ /**
+ * Runs a command delegating to the PoltergeistAgent
+ * @param name
+ * @param args
+ * @return {*}
+ */
+ WebPage.prototype.runCommand = function (name, args) {
+ var method, result, selector;
+
+ result = this.evaluate(function (name, args) {
+ return window.__poltergeist.externalCall(name, args);
+ }, name, args);
+
+ if (result !== null) {
+ if (result.error != null) {
+ switch (result.error.message) {
+ case 'PoltergeistAgent.ObsoleteNode':
+ throw new Poltergeist.ObsoleteNode;
+ break;
+ case 'PoltergeistAgent.InvalidSelector':
+ method = args[0];
+ selector = args[1];
+ throw new Poltergeist.InvalidSelector(method, selector);
+ break;
+ default:
+ throw new Poltergeist.BrowserError(result.error.message, result.error.stack);
+ }
+ } else {
+ return result.value;
+ }
+ }
+ };
+
+ /**
+ * Tells if we can go back or not
+ * @return {boolean}
+ */
+ WebPage.prototype.canGoBack = function () {
+ return this["native"]().canGoBack;
+ };
+
+ /**
+ * Tells if we can go forward or not in the browser history
+ * @return {boolean}
+ */
+ WebPage.prototype.canGoForward = function () {
+ return this["native"]().canGoForward;
+ };
+
+ return WebPage;
+
+}).call(this);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Cookie.php b/vendor/jcalderonzumba/gastonjs/src/Cookie.php
new file mode 100644
index 000000000..d59c0f674
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Cookie.php
@@ -0,0 +1,79 @@
+attributes = $attributes;
+ }
+
+ /**
+ * Returns the cookie name
+ * @return string
+ */
+ public function getName() {
+ return $this->attributes['name'];
+ }
+
+ /**
+ * Returns the cookie value
+ * @return string
+ */
+ public function getValue() {
+ return urldecode($this->attributes['value']);
+ }
+
+ /**
+ * Returns the cookie domain
+ * @return string
+ */
+ public function getDomain() {
+ return $this->attributes['domain'];
+ }
+
+ /**
+ * Returns the path were the cookie is valid
+ * @return string
+ */
+ public function getPath() {
+ return $this->attributes['path'];
+ }
+
+ /**
+ * Is a secure cookie?
+ * @return bool
+ */
+ public function isSecure() {
+ return isset($this->attributes['secure']);
+ }
+
+ /**
+ * Is http only cookie?
+ * @return bool
+ */
+ public function isHttpOnly() {
+ return isset($this->attributes['httponly']);
+ }
+
+ /**
+ * Returns cookie expiration time
+ * @return mixed
+ */
+ public function getExpirationTime() {
+ //TODO: return a \DateTime object
+ if (isset($this->attributes['expiry'])) {
+ return $this->attributes['expiry'];
+ }
+ return null;
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/BrowserError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/BrowserError.php
new file mode 100644
index 000000000..a1c4f4b12
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/BrowserError.php
@@ -0,0 +1,44 @@
+message = $this->message();
+ }
+
+ /**
+ * Gets the name of the browser error
+ * @return string
+ */
+ public function getName() {
+ return $this->response["error"]["name"];
+ }
+
+ /**
+ * @return JSErrorItem
+ */
+ public function javascriptError() {
+ //TODO: this need to be check, i don't know yet what comes in response
+ return new JSErrorItem($this->response["error"]["args"][0], $this->response["error"]["args"][1]);
+ }
+
+ /**
+ * Returns error message
+ * TODO: check how to proper implement if we have exceptions
+ * @return string
+ */
+ public function message() {
+ return "There was an error inside the PhantomJS portion of GastonJS.\nThis is probably a bug, so please report it:\n" . $this->javascriptError();
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/ClientError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/ClientError.php
new file mode 100644
index 000000000..06be87bc2
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/ClientError.php
@@ -0,0 +1,36 @@
+response = $response;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getResponse() {
+ return $this->response;
+ }
+
+ /**
+ * @param mixed $response
+ */
+ public function setResponse($response) {
+ $this->response = $response;
+ }
+
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/DeadClient.php b/vendor/jcalderonzumba/gastonjs/src/Exception/DeadClient.php
new file mode 100644
index 000000000..f4af19306
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/DeadClient.php
@@ -0,0 +1,21 @@
+response["args"]));
+ }
+
+ /**
+ * @return string
+ */
+ public function message() {
+ //TODO: check the exception message stuff
+ return "The frame " . $this->getName() . " was not not found";
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/InvalidSelector.php b/vendor/jcalderonzumba/gastonjs/src/Exception/InvalidSelector.php
new file mode 100644
index 000000000..44fad4bec
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/InvalidSelector.php
@@ -0,0 +1,32 @@
+response["error"]["args"][0];
+ }
+
+ /**
+ * Gets the selector related to the method
+ * @return string
+ */
+ public function getSelector() {
+ return $this->response["error"]["args"][1];
+ }
+
+ /**
+ * @return string
+ */
+ public function message() {
+ return "The browser raised a syntax error while trying to evaluate" . $this->getMethod() . " selector " . $this->getSelector();
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/JSErrorItem.php b/vendor/jcalderonzumba/gastonjs/src/Exception/JSErrorItem.php
new file mode 100644
index 000000000..2fa205afd
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/JSErrorItem.php
@@ -0,0 +1,31 @@
+message = $message;
+ $this->stack = $stack;
+ }
+
+ /**
+ * String representation of the class
+ * @return string
+ */
+ public function __toString() {
+ return sprintf("%s\n%s", $this->message, $this->stack);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/JavascriptError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/JavascriptError.php
new file mode 100644
index 000000000..309adfb97
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/JavascriptError.php
@@ -0,0 +1,48 @@
+message = $this->message();
+ }
+
+ /**
+ * Get the javascript errors found during the use of the phantomjs
+ * @return array
+ */
+ public function javascriptErrors() {
+ $jsErrors = array();
+ $errors = $this->response["error"]["args"][0];
+ foreach ($errors as $error) {
+ $jsErrors[] = new JSErrorItem($error["message"], $error["stack"]);
+ }
+ return $jsErrors;
+ }
+
+ /**
+ * Returns the javascript errors found
+ * @return string
+ */
+ public function message() {
+ $error = "One or more errors were raised in the Javascript code on the page.
+ If you don't care about these errors, you can ignore them by
+ setting js_errors: false in your Poltergeist configuration (see documentation for details).";
+ //TODO: add javascript errors
+ $jsErrors = $this->javascriptErrors();
+ foreach($jsErrors as $jsError){
+ $error = "$error\n$jsError";
+ }
+ return $error;
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/MouseEventFailed.php b/vendor/jcalderonzumba/gastonjs/src/Exception/MouseEventFailed.php
new file mode 100644
index 000000000..abd72d3a4
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/MouseEventFailed.php
@@ -0,0 +1,49 @@
+response["args"][0];
+ }
+
+ /**
+ * Selector of the element to act with the mouse
+ * @return string
+ */
+ public function getSelector() {
+ return $this->response["args"][1];
+ }
+
+ /**
+ * Returns the position where the click was done
+ * @return array
+ */
+ public function getPosition() {
+ $position = array();
+ $position[0] = $this->response["args"][1]['x'];
+ $position[1] = $this->response["args"][2]['y'];
+ return $position;
+ }
+
+ /**
+ * @return string
+ */
+ public function message() {
+ $name = $this->getName();
+ $position = implode(",", $this->getPosition());
+ return "Firing a $name at co-ordinates [$position] failed. Poltergeist detected
+ another element with CSS selector '#{selector}' at this position.
+ It may be overlapping the element you are trying to interact with.
+ If you don't care about overlapping elements, try using node.trigger('$name').";
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/NoSuchWindowError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/NoSuchWindowError.php
new file mode 100644
index 000000000..45388d16c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/NoSuchWindowError.php
@@ -0,0 +1,10 @@
+node = $node;
+ parent::__construct($response);
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/ObsoleteNode.php b/vendor/jcalderonzumba/gastonjs/src/Exception/ObsoleteNode.php
new file mode 100644
index 000000000..a0cdb1795
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/ObsoleteNode.php
@@ -0,0 +1,29 @@
+message = $this->message();
+ }
+
+ /**
+ * @return string
+ */
+ public function message() {
+ return "The element you are trying to interact with is either not part of the DOM, or is
+ not currently visible on the page (perhaps display: none is set).
+ It's possible the element has been replaced by another element and you meant to interact with
+ the new element. If so you need to do a new 'find' in order to get a reference to the
+ new element.";
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/StatusFailError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/StatusFailError.php
new file mode 100644
index 000000000..fd90eefec
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/StatusFailError.php
@@ -0,0 +1,17 @@
+data = $data;
+ $this->responseParts = $this->createResponseParts($responseParts);
+ }
+
+ /**
+ * Creates an array of Response objects from a given response array
+ * @param $responseParts
+ * @return array
+ */
+ protected function createResponseParts($responseParts) {
+ if ($responseParts === null) {
+ return array();
+ }
+ $responses = array();
+ foreach ($responseParts as $responsePart) {
+ $responses[] = new Response($responsePart);
+ }
+ return $responses;
+ }
+
+ /**
+ * @return array
+ */
+ public function getResponseParts() {
+ return $this->responseParts;
+ }
+
+ /**
+ * @param array $responseParts
+ */
+ public function setResponseParts($responseParts) {
+ $this->responseParts = $responseParts;
+ }
+
+ /**
+ * Returns the url where the request is going to be made
+ * @return string
+ */
+ public function getUrl() {
+ //TODO: add isset maybe?
+ return $this->data['url'];
+ }
+
+ /**
+ * Returns the request method
+ * @return string
+ */
+ public function getMethod() {
+ return $this->data['method'];
+ }
+
+ /**
+ * Gets the request headers
+ * @return array
+ */
+ public function getHeaders() {
+ //TODO: Check if the data is actually an array, else make it array and see implications
+ return $this->data['headers'];
+ }
+
+ /**
+ * Returns if exists the request time
+ * @return \DateTime
+ */
+ public function getTime() {
+ if (isset($this->data['time'])) {
+ $requestTime = new \DateTime();
+ //TODO: fix the microseconds to miliseconds
+ $requestTime->createFromFormat("Y-m-dTH:i:s.uZ", $this->data["time"]);
+ return $requestTime;
+ }
+ return null;
+ }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Response.php b/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Response.php
new file mode 100644
index 000000000..37edc4250
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Response.php
@@ -0,0 +1,97 @@
+data = $data;
+ }
+
+ /**
+ * Gets Response url
+ * @return string
+ */
+ public function getUrl() {
+ return $this->data['url'];
+ }
+
+ /**
+ * Gets the response status code
+ * @return int
+ */
+ public function getStatus() {
+ return intval($this->data['status']);
+ }
+
+ /**
+ * Gets the status text of the response
+ * @return string
+ */
+ public function getStatusText() {
+ return $this->data['statusText'];
+ }
+
+ /**
+ * Gets the response headers
+ * @return array
+ */
+ public function getHeaders() {
+ return $this->data['headers'];
+ }
+
+ /**
+ * Get redirect url if response is a redirect
+ * @return string
+ */
+ public function getRedirectUrl() {
+ if (isset($this->data['redirectUrl']) && !empty($this->data['redirectUrl'])) {
+ return $this->data['redirectUrl'];
+ }
+ return null;
+ }
+
+ /**
+ * Returns the size of the response body
+ * @return int
+ */
+ public function getBodySize() {
+ if (isset($this->data['bodySize'])) {
+ return intval($this->data['bodySize']);
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the content type of the response
+ * @return string
+ */
+ public function getContentType() {
+ if (isset($this->data['contentType'])) {
+ return $this->data['contentType'];
+ }
+ return null;
+ }
+
+ /**
+ * Returns if exists the response time
+ * @return \DateTime
+ */
+ public function getTime() {
+ if (isset($this->data['time'])) {
+ $requestTime = new \DateTime();
+ //TODO: fix the microseconds to miliseconds
+ $requestTime->createFromFormat("Y-m-dTH:i:s.uZ", $this->data["time"]);
+ return $requestTime;
+ }
+ return null;
+ }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/unit_tests.xml b/vendor/jcalderonzumba/gastonjs/unit_tests.xml
new file mode 100644
index 000000000..5473787bf
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/unit_tests.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ tests/unit
+
+
+
+
+ src
+
+ src/
+
+
+
+
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/.travis.yml b/vendor/jcalderonzumba/mink-phantomjs-driver/.travis.yml
new file mode 100644
index 000000000..0dd8e587b
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/.travis.yml
@@ -0,0 +1,40 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - hhvm
+
+matrix:
+ fast_finish: true
+ include:
+ - php: 5.4
+ env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' SYMFONY_DEPRECATIONS_HELPER=weak
+ - php: 5.6
+ env: DEPENDENCIES=dev
+ allow_failures:
+ - php: 7.0
+ - php: hhvm
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+before_install:
+ - composer self-update
+ - if [ "$DEPENDENCIES" = "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi;
+
+install:
+ - composer update $COMPOSER_FLAGS
+
+before_script:
+ - mkdir -p /tmp/jcalderonzumba/phantomjs
+
+script:
+ - bin/run-tests.sh
+
+after_script:
+ - ps axo pid,command | grep phantomjs | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
+ - ps axo pid,command | grep php | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/CHANGELOG-0.2.md b/vendor/jcalderonzumba/mink-phantomjs-driver/CHANGELOG-0.2.md
new file mode 100644
index 000000000..e7482b297
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/CHANGELOG-0.2.md
@@ -0,0 +1,7 @@
+CHANGELOG for 0.2.x
+===================
+This changelog references the relevant changes (bug and security fixes) done in 0.2 minor versions.
+
+* 0.2.3
+
+ * bug #1 set_url_blacklist was not working properly (thanks to reporter)
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/LICENSE b/vendor/jcalderonzumba/mink-phantomjs-driver/LICENSE
new file mode 100644
index 000000000..7ba018acd
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Juan Francisco Calderón Zumba
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/README.md b/vendor/jcalderonzumba/mink-phantomjs-driver/README.md
new file mode 100644
index 000000000..54a433b11
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/README.md
@@ -0,0 +1,61 @@
+Mink PhantomJS Driver
+===========================
+[![Build Status](https://travis-ci.org/jcalderonzumba/MinkPhantomJSDriver.svg?branch=master)](https://travis-ci.org/jcalderonzumba/MinkPhantomJSDriver)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jcalderonzumba/MinkPhantomJSDriver/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jcalderonzumba/MinkPhantomJSDriver/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/jcalderonzumba/mink-phantomjs-driver/v/stable)](https://packagist.org/packages/jcalderonzumba/mink-phantomjs-driver)
+[![Total Downloads](https://poser.pugx.org/jcalderonzumba/mink-phantomjs-driver/downloads)](https://packagist.org/packages/jcalderonzumba/mink-phantomjs-driver)
+
+Installation & Compatibility
+----------------------------
+You need a working installation of [PhantomJS](http://phantomjs.org/download.html)
+
+This driver is tested using PhantomJS 1.9.8 but it should work with 1.9.X or latest 2.0.X versions
+
+This driver supports **PHP 5.4 or greater**, there is NO support for PHP 5.3
+
+Use [Composer](https://getcomposer.org/) to install all required PHP dependencies:
+
+```bash
+$ composer require --dev behat/mink jcalderonzumba/mink-phantomjs-driver
+```
+
+How to use
+-------------
+Extension configuration (for the moment NONE).
+```yml
+default:
+ extensions:
+ Zumba\PhantomJSExtension:
+```
+Driver specific configuration:
+```yml
+Behat\MinkExtension:
+phantomjs:
+ phantom_server: "http://localhost:8510/api"
+ template_cache: "/tmp/pjsdrivercache/phantomjs"
+```
+PhantomJS browser start:
+```bash
+phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /tmp/gastonjs.log &
+```
+
+FAQ
+---------
+
+1. Is this a selenium based driver?:
+
+ **NO**, it has nothing to do with Selenium it's inspired on [Poltergeist](https://github.com/teampoltergeist/poltergeist)
+
+2. What features does this driver implements?
+
+ **ALL** of the features defined in Mink DriverInterface. maximizeWindow is the only one not implemented since is a headless browser it does not make sense to implement it.
+
+3. Do i need to modify my selenium based tests?
+
+ If you only use the standard behat driver defined methods then NO, you just have to change your default javascript driver.
+
+
+Copyright
+---------
+
+Copyright (c) 2015 Juan Francisco Calderon Zumba
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/bin/run-tests.sh b/vendor/jcalderonzumba/mink-phantomjs-driver/bin/run-tests.sh
new file mode 100644
index 000000000..60bd21941
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/bin/run-tests.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+set -e
+
+start_browser_api(){
+ CURRENT_DIR=$(pwd)
+ LOCAL_PHANTOMJS="${CURRENT_DIR}/bin/phantomjs"
+ if [ -f ${LOCAL_PHANTOMJS} ]; then
+ ${LOCAL_PHANTOMJS} --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 &
+ else
+ phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /dev/null &
+ fi
+ sleep 2
+}
+
+stop_services(){
+ ps axo pid,command | grep phantomjs | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
+ ps axo pid,command | grep php | grep -v grep | grep -v phpstorm | awk '{print $1}' | xargs -I {} kill {}
+ sleep 2
+}
+
+star_local_browser(){
+ CURRENT_DIR=$(pwd)
+ cd ${CURRENT_DIR}/vendor/behat/mink/driver-testsuite/web-fixtures
+ if [ "$TRAVIS" = true ]; then
+ echo "Starting webserver fox fixtures...."
+ ~/.phpenv/versions/5.6/bin/php -S 127.0.0.1:6789 > /dev/null 2>&1 &
+ else
+ php -S 127.0.0.1:6789 2>&1 >> /dev/null &
+ fi
+ sleep 2
+}
+
+mkdir -p /tmp/jcalderonzumba/phantomjs
+stop_services
+start_browser_api
+star_local_browser
+cd ${CURRENT_DIR}
+${CURRENT_DIR}/bin/phpunit --configuration integration_tests.xml
+stop_services
+start_browser_api
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/composer.json b/vendor/jcalderonzumba/mink-phantomjs-driver/composer.json
new file mode 100644
index 000000000..31b4f57a4
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/composer.json
@@ -0,0 +1,53 @@
+{
+ "name": "jcalderonzumba/mink-phantomjs-driver",
+ "description": "PhantomJS driver for Mink framework",
+ "keywords": [
+ "phantomjs",
+ "headless",
+ "javascript",
+ "ajax",
+ "testing",
+ "browser"
+ ],
+ "homepage": "http://mink.behat.org/",
+ "type": "mink-driver",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Juan Francisco Calderón Zumba",
+ "email": "juanfcz@gmail.com",
+ "homepage": "http://github.com/jcalderonzumba"
+ }
+ ],
+ "require": {
+ "php": ">=5.4",
+ "behat/mink": "~1.6",
+ "twig/twig": "~1.8",
+ "jcalderonzumba/gastonjs": "~1.0"
+ },
+ "require-dev": {
+ "symfony/process": "~2.3",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/css-selector": "~2.1",
+ "phpunit/phpunit": "~4.6",
+ "silex/silex": "~1.2"
+ },
+ "config": {
+ "bin-dir": "bin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Zumba\\Mink\\Driver\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Behat\\Mink\\Tests\\Driver\\": "tests/integration"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.4.x-dev"
+ }
+ }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/integration_tests.xml b/vendor/jcalderonzumba/mink-phantomjs-driver/integration_tests.xml
new file mode 100644
index 000000000..739fc365a
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/integration_tests.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+ tests/integration
+ vendor/behat/mink/driver-testsuite/tests/Basic/BasicAuthTest.php
+ vendor/behat/mink/driver-testsuite/tests/Basic/ContentTest.php
+ vendor/behat/mink/driver-testsuite/tests/Basic/CookieTest.php
+ vendor/behat/mink/driver-testsuite/tests/Basic/ErrorHandlingTest.php
+ vendor/behat/mink/driver-testsuite/tests/Basic/IFrameTest.php
+ vendor/behat/mink/driver-testsuite/tests/Basic/ScreenshotTest.php
+ vendor/behat/mink/driver-testsuite/tests/Basic/TraversingTest.php
+ vendor/behat/mink/driver-testsuite/tests/Basic/VisibilityTest.php
+ vendor/behat/mink/driver-testsuite/tests/Form
+ vendor/behat/mink/driver-testsuite/tests/Js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ./src/Behat/Mink/Driver
+
+
+
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/BasePhantomJSDriver.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/BasePhantomJSDriver.php
new file mode 100644
index 000000000..d962ff526
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/BasePhantomJSDriver.php
@@ -0,0 +1,109 @@
+phantomHost = $phantomHost;
+ $this->browser = new Browser($phantomHost);
+ $this->templateLoader = new \Twig_Loader_Filesystem(realpath(__DIR__ . '/Resources/Script'));
+ $this->templateEnv = new \Twig_Environment($this->templateLoader, array('cache' => $this->templateCacheSetup($templateCache), 'strict_variables' => true));
+ }
+
+ /**
+ * Sets up the cache template location for the scripts we are going to create with the driver
+ * @param $templateCache
+ * @return string
+ * @throws DriverException
+ */
+ protected function templateCacheSetup($templateCache) {
+ $cacheDir = $templateCache;
+ if ($templateCache === null) {
+ $cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "jcalderonzumba" . DIRECTORY_SEPARATOR . "phantomjs";
+ if (!file_exists($cacheDir)) {
+ mkdir($cacheDir, 0777, true);
+ }
+ }
+
+ if (!file_exists($cacheDir)) {
+ throw new DriverException("Template cache $cacheDir directory does not exist");
+ }
+ return $cacheDir;
+ }
+
+ /**
+ * Helper to find a node element given an xpath
+ * @param string $xpath
+ * @param int $max
+ * @returns int
+ * @throws DriverException
+ */
+ protected function findElement($xpath, $max = 1) {
+ $elements = $this->browser->find("xpath", $xpath);
+ if (!isset($elements["page_id"]) || !isset($elements["ids"]) || count($elements["ids"]) !== $max) {
+ throw new DriverException("Failed to get elements with given $xpath");
+ }
+ return $elements;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param Session $session
+ */
+ public function setSession(Session $session) {
+ $this->session = $session;
+ }
+
+ /**
+ * @return Browser
+ */
+ public function getBrowser() {
+ return $this->browser;
+ }
+
+ /**
+ * @return \Twig_Environment
+ */
+ public function getTemplateEnv() {
+ return $this->templateEnv;
+ }
+
+ /**
+ * Returns a javascript script via twig template engine
+ * @param $templateName
+ * @param $viewData
+ * @return string
+ */
+ public function javascriptTemplateRender($templateName, $viewData) {
+ /** @var $templateEngine \Twig_Environment */
+ $templateEngine = $this->getTemplateEnv();
+ return $templateEngine->render($templateName, $viewData);
+ }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/CookieTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/CookieTrait.php
new file mode 100644
index 000000000..327b94885
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/CookieTrait.php
@@ -0,0 +1,45 @@
+browser->removeCookie($name);
+ }
+ //TODO: set the cookie with domain, not with url, meaning www.aaa.com or .aaa.com
+ if ($value !== null) {
+ $urlData = parse_url($this->getCurrentUrl());
+ $cookie = array("name" => $name, "value" => $value, "domain" => $urlData["host"]);
+ $this->browser->setCookie($cookie);
+ }
+ }
+
+ /**
+ * Gets a cookie by its name if exists, else it will return null
+ * @param string $name
+ * @return string
+ */
+ public function getCookie($name) {
+ $cookies = $this->browser->cookies();
+ foreach ($cookies as $cookie) {
+ if ($cookie instanceof Cookie && strcmp($cookie->getName(), $name) === 0) {
+ return $cookie->getValue();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/FormManipulationTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/FormManipulationTrait.php
new file mode 100644
index 000000000..5a94fc762
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/FormManipulationTrait.php
@@ -0,0 +1,168 @@
+findElement($xpath, 1);
+ $javascript = $this->javascriptTemplateRender("get_value.js.twig", array("xpath" => $xpath));
+ return $this->browser->evaluate($javascript);
+ }
+
+ /**
+ * @param string $xpath
+ * @param string $value
+ * @throws DriverException
+ */
+ public function setValue($xpath, $value) {
+ $this->findElement($xpath, 1);
+ //This stuff is BECAUSE the way the driver works for setting values when being checkboxes, radios, etc.
+ if (is_bool($value)) {
+ $value = $this->boolToString($value);
+ }
+
+ $javascript = $this->javascriptTemplateRender("set_value.js.twig", array("xpath" => $xpath, "value" => json_encode($value)));
+ $this->browser->evaluate($javascript);
+ }
+
+
+ /**
+ * Submits a form given an xpath selector
+ * @param string $xpath
+ * @throws DriverException
+ */
+ public function submitForm($xpath) {
+ $element = $this->findElement($xpath, 1);
+ $tagName = $this->browser->tagName($element["page_id"], $element["ids"][0]);
+ if (strcmp(strtolower($tagName), "form") !== 0) {
+ throw new DriverException("Can not submit something that is not a form");
+ }
+ $this->browser->trigger($element["page_id"], $element["ids"][0], "submit");
+ }
+
+ /**
+ * Helper method needed for twig and javascript stuff
+ * @param $boolValue
+ * @return string
+ */
+ protected function boolToString($boolValue) {
+ if ($boolValue === true) {
+ return "1";
+ }
+ return "0";
+ }
+
+ /**
+ * Selects an option
+ * @param string $xpath
+ * @param string $value
+ * @param bool $multiple
+ * @return bool
+ * @throws DriverException
+ */
+ public function selectOption($xpath, $value, $multiple = false) {
+ $element = $this->findElement($xpath, 1);
+ $tagName = strtolower($this->browser->tagName($element["page_id"], $element["ids"][0]));
+ $attributes = $this->browser->attributes($element["page_id"], $element["ids"][0]);
+
+ if (!in_array($tagName, array("input", "select"))) {
+ throw new DriverException(sprintf('Impossible to select an option on the element with XPath "%s" as it is not a select or radio input', $xpath));
+ }
+
+ if ($tagName === "input" && $attributes["type"] != "radio") {
+ throw new DriverException(sprintf('Impossible to select an option on the element with XPath "%s" as it is not a select or radio input', $xpath));
+ }
+
+ return $this->browser->selectOption($element["page_id"], $element["ids"][0], $value, $multiple);
+ }
+
+ /**
+ * Check control over an input element of radio or checkbox type
+ * @param $xpath
+ * @return bool
+ * @throws DriverException
+ */
+ protected function inputCheckableControl($xpath) {
+ $element = $this->findElement($xpath, 1);
+ $tagName = strtolower($this->browser->tagName($element["page_id"], $element["ids"][0]));
+ $attributes = $this->browser->attributes($element["page_id"], $element["ids"][0]);
+ if ($tagName != "input") {
+ throw new DriverException("Can not check when the element is not of the input type");
+ }
+ if (!in_array($attributes["type"], array("checkbox", "radio"))) {
+ throw new DriverException("Can not check when the element is not checkbox or radio");
+ }
+ return true;
+ }
+
+ /**
+ * We click on the checkbox or radio when possible and needed
+ * @param string $xpath
+ * @throws DriverException
+ */
+ public function check($xpath) {
+ $this->inputCheckableControl($xpath);
+ $javascript = $this->javascriptTemplateRender("check_element.js.twig", array("xpath" => $xpath, "check" => "true"));
+ $this->browser->evaluate($javascript);
+ }
+
+ /**
+ * We click on the checkbox or radio when possible and needed
+ * @param string $xpath
+ * @throws DriverException
+ */
+ public function uncheck($xpath) {
+ $this->inputCheckableControl($xpath);
+ $javascript = $this->javascriptTemplateRender("check_element.js.twig", array("xpath" => $xpath, "check" => "false"));
+ $this->browser->evaluate($javascript);
+ }
+
+ /**
+ * Checks if the radio or checkbox is checked
+ * @param string $xpath
+ * @return bool
+ * @throws DriverException
+ */
+ public function isChecked($xpath) {
+ $this->findElement($xpath, 1);
+ $javascript = $this->javascriptTemplateRender("is_checked.js.twig", array("xpath" => $xpath));
+ $checked = $this->browser->evaluate($javascript);
+
+ if ($checked === null) {
+ throw new DriverException("Can not check when the element is not checkbox or radio");
+ }
+
+ return $checked;
+ }
+
+ /**
+ * Checks if the option is selected or not
+ * @param string $xpath
+ * @return bool
+ * @throws DriverException
+ */
+ public function isSelected($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ $javascript = $this->javascriptTemplateRender("is_selected.js.twig", array("xpath" => $xpath));
+ $tagName = $this->browser->tagName($elements["page_id"], $elements["ids"][0]);
+ if (strcmp(strtolower($tagName), "option") !== 0) {
+ throw new DriverException("Can not assert on element that is not an option");
+ }
+
+ return $this->browser->evaluate($javascript);
+ }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/HeadersTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/HeadersTrait.php
new file mode 100644
index 000000000..1d6865aca
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/HeadersTrait.php
@@ -0,0 +1,40 @@
+browser->responseHeaders();
+ }
+
+ /**
+ * Current request status code response
+ * @return int
+ */
+ public function getStatusCode() {
+ return $this->browser->getStatusCode();
+ }
+
+ /**
+ * The name say its all
+ * @param string $name
+ * @param string $value
+ */
+ public function setRequestHeader($name, $value) {
+ $header = array();
+ $header[$name] = $value;
+ //TODO: as a limitation of the driver it self, we will send permanent for the moment
+ $this->browser->addHeader($header, true);
+ }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/JavascriptTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/JavascriptTrait.php
new file mode 100644
index 000000000..c5b7d6304
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/JavascriptTrait.php
@@ -0,0 +1,49 @@
+browser->execute($script);
+ }
+
+ /**
+ * Evaluates a script and returns the result
+ * @param string $script
+ * @return mixed
+ */
+ public function evaluateScript($script) {
+ return $this->browser->evaluate($script);
+ }
+
+ /**
+ * Waits some time or until JS condition turns true.
+ *
+ * @param integer $timeout timeout in milliseconds
+ * @param string $condition JS condition
+ * @return boolean
+ * @throws DriverException When the operation cannot be done
+ */
+ public function wait($timeout, $condition) {
+ $start = microtime(true);
+ $end = $start + $timeout / 1000.0;
+ do {
+ $result = $this->browser->evaluate($condition);
+ usleep(100000);
+ } while (microtime(true) < $end && !$result);
+
+ return (bool)$result;
+ }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/KeyboardTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/KeyboardTrait.php
new file mode 100644
index 000000000..2b0c96d20
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/KeyboardTrait.php
@@ -0,0 +1,95 @@
+findElement($xpath, 1);
+ $key = $this->normalizeCharForKeyEvent($char);
+ $modifier = $this->keyEventModifierControl($modifier);
+ return $this->browser->keyEvent($element["page_id"], $element["ids"][0], "keydown", $key, $modifier);
+ }
+
+ /**
+ * @param string $xpath
+ * @param string $char
+ * @param string $modifier
+ * @throws DriverException
+ */
+ public function keyPress($xpath, $char, $modifier = null) {
+ $element = $this->findElement($xpath, 1);
+ $key = $this->normalizeCharForKeyEvent($char);
+ $modifier = $this->keyEventModifierControl($modifier);
+ return $this->browser->keyEvent($element["page_id"], $element["ids"][0], "keypress", $key, $modifier);
+ }
+
+ /**
+ * Pressed up specific keyboard key.
+ *
+ * @param string $xpath
+ * @param string|integer $char could be either char ('b') or char-code (98)
+ * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta')
+ *
+ * @throws DriverException When the operation cannot be done
+ */
+ public function keyUp($xpath, $char, $modifier = null) {
+ $this->findElement($xpath, 1);
+ $element = $this->findElement($xpath, 1);
+ $key = $this->normalizeCharForKeyEvent($char);
+ $modifier = $this->keyEventModifierControl($modifier);
+ return $this->browser->keyEvent($element["page_id"], $element["ids"][0], "keyup", $key, $modifier);
+ }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/MouseTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/MouseTrait.php
new file mode 100644
index 000000000..c7cd2ebbc
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/MouseTrait.php
@@ -0,0 +1,57 @@
+findElement($xpath, 1);
+ $this->browser->hover($element["page_id"], $element["ids"][0]);
+ }
+
+ /**
+ * Clicks if possible on an element given by xpath
+ * @param string $xpath
+ * @return mixed
+ * @throws DriverException
+ */
+ public function click($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ $this->browser->click($elements["page_id"], $elements["ids"][0]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ /**
+ * Double click on element found via xpath
+ * @param string $xpath
+ * @throws DriverException
+ */
+ public function doubleClick($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ $this->browser->doubleClick($elements["page_id"], $elements["ids"][0]);
+ }
+
+ /**
+ * Right click on element found via xpath
+ * @param string $xpath
+ * @throws DriverException
+ */
+ public function rightClick($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ $this->browser->rightClick($elements["page_id"], $elements["ids"][0]);
+ }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/NavigationTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/NavigationTrait.php
new file mode 100644
index 000000000..88ca42934
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/NavigationTrait.php
@@ -0,0 +1,49 @@
+browser->visit($url);
+ }
+
+ /**
+ * Gets the current url if any
+ * @return string
+ */
+ public function getCurrentUrl() {
+ return $this->browser->currentUrl();
+ }
+
+
+ /**
+ * Reloads the page if possible
+ */
+ public function reload() {
+ $this->browser->reload();
+ }
+
+ /**
+ * Goes forward if possible
+ */
+ public function forward() {
+ $this->browser->goForward();
+ }
+
+ /**
+ * Goes back if possible
+ */
+ public function back() {
+ $this->browser->goBack();
+ }
+
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/PageContentTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PageContentTrait.php
new file mode 100644
index 000000000..7759a0fd7
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PageContentTrait.php
@@ -0,0 +1,72 @@
+browser->getBody();
+ }
+
+ /**
+ * Given xpath, will try to get ALL the text, visible and not visible from such xpath
+ * @param string $xpath
+ * @return string
+ * @throws DriverException
+ */
+ public function getText($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ //allText works only with ONE element so it will be the first one and also returns new lines that we will remove
+ $text = $this->browser->allText($elements["page_id"], $elements["ids"][0]);
+ $text = trim(str_replace(array("\r", "\r\n", "\n"), ' ', $text));
+ $text = preg_replace('/ {2,}/', ' ', $text);
+ return $text;
+ }
+
+ /**
+ * Returns the inner html of a given xpath
+ * @param string $xpath
+ * @return string
+ * @throws DriverException
+ */
+ public function getHtml($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ //allText works only with ONE element so it will be the first one
+ return $this->browser->allHtml($elements["page_id"], $elements["ids"][0], "inner");
+ }
+
+ /**
+ * Gets the outer html of a given xpath
+ * @param string $xpath
+ * @return string
+ * @throws DriverException
+ */
+ public function getOuterHtml($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ //allText works only with ONE element so it will be the first one
+ return $this->browser->allHtml($elements["page_id"], $elements["ids"][0], "outer");
+ }
+
+ /**
+ * Returns the binary representation of the current page we are in
+ * @throws DriverException
+ * @return string
+ */
+ public function getScreenshot() {
+ $options = array("full" => true, "selector" => null);
+ $b64ScreenShot = $this->browser->renderBase64("JPEG", $options);
+ if (($binaryScreenShot = base64_decode($b64ScreenShot, true)) === false) {
+ throw new DriverException("There was a problem while doing the screenshot of the current page");
+ }
+ return $binaryScreenShot;
+ }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/PhantomJSDriver.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PhantomJSDriver.php
new file mode 100644
index 000000000..ac251707d
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PhantomJSDriver.php
@@ -0,0 +1,164 @@
+browser->setHttpAuth($user, $password);
+ }
+
+ /**
+ * Gets the tag name of a given xpath
+ * @param string $xpath
+ * @return string
+ * @throws DriverException
+ */
+ public function getTagName($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ return $this->browser->tagName($elements["page_id"], $elements["ids"][0]);
+ }
+
+ /**
+ * Gets the attribute value of a given element and name
+ * @param string $xpath
+ * @param string $name
+ * @return string
+ * @throws DriverException
+ */
+ public function getAttribute($xpath, $name) {
+ $elements = $this->findElement($xpath, 1);
+ return $this->browser->attribute($elements["page_id"], $elements["ids"][0], $name);
+ }
+
+ /**
+ * Check if element given by xpath is visible or not
+ * @param string $xpath
+ * @return bool
+ * @throws DriverException
+ */
+ public function isVisible($xpath) {
+ $elements = $this->findElement($xpath, 1);
+ return $this->browser->isVisible($elements["page_id"], $elements["ids"][0]);
+ }
+
+ /**
+ * Drags one element to another
+ * @param string $sourceXpath
+ * @param string $destinationXpath
+ * @throws DriverException
+ */
+ public function dragTo($sourceXpath, $destinationXpath) {
+ $sourceElement = $this->findElement($sourceXpath, 1);
+ $destinationElement = $this->findElement($destinationXpath, 1);
+ $this->browser->drag($sourceElement["page_id"], $sourceElement["ids"][0], $destinationElement["ids"][0]);
+ }
+
+ /**
+ * Upload a file to the browser
+ * @param string $xpath
+ * @param string $path
+ * @throws DriverException
+ */
+ public function attachFile($xpath, $path) {
+ if (!file_exists($path)) {
+ throw new DriverException("Wow there the file does not exist, you can not upload it");
+ }
+
+ if (($realPath = realpath($path)) === false) {
+ throw new DriverException("Wow there the file does not exist, you can not upload it");
+ }
+
+ $element = $this->findElement($xpath, 1);
+ $tagName = $this->getTagName($xpath);
+ if ($tagName != "input") {
+ throw new DriverException("The element is not an input element, you can not attach a file to it");
+ }
+
+ $attributes = $this->getBrowser()->attributes($element["page_id"], $element["ids"][0]);
+ if (!isset($attributes["type"]) || $attributes["type"] != "file") {
+ throw new DriverException("The element is not an input file type element, you can not attach a file to it");
+ }
+
+ $this->browser->selectFile($element["page_id"], $element["ids"][0], $realPath);
+ }
+
+ /**
+ * Puts the browser control inside the IFRAME
+ * You own the control, make sure to go back to the parent calling this method with null
+ * @param string $name
+ */
+ public function switchToIFrame($name = null) {
+ //TODO: check response of the calls
+ if ($name === null) {
+ $this->browser->popFrame();
+ return;
+ } else {
+ $this->browser->pushFrame($name);
+ }
+ }
+
+ /**
+ * Focus on an element
+ * @param string $xpath
+ * @throws DriverException
+ */
+ public function focus($xpath) {
+ $element = $this->findElement($xpath, 1);
+ $this->browser->trigger($element["page_id"], $element["ids"][0], "focus");
+ }
+
+ /**
+ * Blur on element
+ * @param string $xpath
+ * @throws DriverException
+ */
+ public function blur($xpath) {
+ $element = $this->findElement($xpath, 1);
+ $this->browser->trigger($element["page_id"], $element["ids"][0], "blur");
+ }
+
+ /**
+ * Finds elements with specified XPath query.
+ * @param string $xpath
+ * @return NodeElement[]
+ * @throws DriverException When the operation cannot be done
+ */
+ public function find($xpath) {
+ $elements = $this->browser->find("xpath", $xpath);
+ $nodeElements = array();
+
+ if (!isset($elements["ids"])) {
+ return null;
+ }
+
+ foreach ($elements["ids"] as $i => $elementId) {
+ $nodeElements[] = new NodeElement(sprintf('(%s)[%d]', $xpath, $i + 1), $this->session);
+ }
+ return $nodeElements;
+ }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/check_element.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/check_element.js.twig
new file mode 100644
index 000000000..f3372ba7f
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/check_element.js.twig
@@ -0,0 +1,35 @@
+{% autoescape 'js' %}
+(function (xpath, check) {
+ function getPolterNode(xpath) {
+ var polterAgent = window.__poltergeist;
+ var ids = polterAgent.find("xpath", xpath, document);
+ return polterAgent.get(ids[0]);
+ }
+
+ var pNode = getPolterNode(xpath);
+
+ if (check && pNode.element.checked) {
+ //requested to check the element and is already check, do nothing.
+ return true;
+ }
+
+ if (!check && pNode.element.checked == false) {
+ //move along nothing to be done
+ return true;
+ }
+
+ if (check && pNode.element.checked == false) {
+ //we have to check the element, we will do so by triggering a click event so all change listeners are aware.
+ pNode.trigger("click");
+ pNode.element.checked = true;
+ }
+
+ if (!check && pNode.element.checked) {
+ //move along nothing to be done
+ pNode.trigger("click");
+ pNode.element.checked = false;
+ return true;
+ }
+ return false;
+}('{{xpath}}', {{check}}));
+{% endautoescape %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/execute_script.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/execute_script.js.twig
new file mode 100644
index 000000000..791276c51
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/execute_script.js.twig
@@ -0,0 +1,3 @@
+{% autoescape false %}
+ {{ script }};
+{% endautoescape %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/get_value.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/get_value.js.twig
new file mode 100644
index 000000000..8e040f854
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/get_value.js.twig
@@ -0,0 +1,63 @@
+{% autoescape 'js' %}
+(function (xpath) {
+ function getElement(xpath) {
+ var polterAgent = window.__poltergeist;
+ var ids = polterAgent.find("xpath", xpath, document);
+ var polterNode = polterAgent.get(ids[0]);
+ return polterNode.element;
+ }
+
+ function inputRadioGetValue(element){
+ var value = null;
+ var name = element.getAttribute('name');
+ if (!name){
+ return null;
+ }
+ var fields = window.document.getElementsByName(name);
+ var i;
+ var l = fields.length;
+ for (i = 0; i < l; i++) {
+ var field = fields.item(i);
+ if (field.form === element.form && field.checked) {
+ return field.value;
+ }
+ }
+ return null;
+ }
+
+ var node = getElement(xpath);
+ var tagName = node.tagName.toLowerCase();
+ var value = null;
+ if (tagName == "input") {
+ var type = node.type.toLowerCase();
+ if (type == "checkbox") {
+ value = node.checked ? node.value : null;
+ } else if (type == "radio") {
+ value = inputRadioGetValue(node);
+ } else {
+ value = node.value;
+ }
+ } else if (tagName == "textarea") {
+ value = node.value;
+ } else if (tagName == "select") {
+ if (node.multiple) {
+ value = [];
+ for (var i = 0; i < node.options.length; i++) {
+ if (node.options[i].selected) {
+ value.push(node.options[i].value);
+ }
+ }
+ } else {
+ var idx = node.selectedIndex;
+ if (idx >= 0) {
+ value = node.options.item(idx).value;
+ } else {
+ value = null;
+ }
+ }
+ } else {
+ value = node.value;
+ }
+ return value;
+}('{{ xpath }}'));
+{% endautoescape %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_checked.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_checked.js.twig
new file mode 100644
index 000000000..6b14cad6a
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_checked.js.twig
@@ -0,0 +1,31 @@
+{% autoescape 'js' %}
+(function (xpath) {
+ function getElement(xpath, within) {
+ var result;
+ if (within === null || within === undefined) {
+ within = document;
+ }
+ result = document.evaluate(xpath, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ if (result.snapshotLength !== 1) {
+ return null;
+ }
+ return result.snapshotItem(0);
+ }
+
+ var node = getElement(xpath);
+
+ if (node === null) {
+ return null;
+ }
+
+ if(node.tagName.toLowerCase() != "input"){
+ return null;
+ }
+
+ if(node.type.toLowerCase() != "checkbox" && node.type.toLowerCase() != "radio"){
+ return null;
+ }
+
+ return node.checked;
+}('{{ xpath }}'));
+{% endautoescape %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_selected.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_selected.js.twig
new file mode 100644
index 000000000..a3a18d307
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_selected.js.twig
@@ -0,0 +1,16 @@
+{% autoescape 'js' %}
+(function (xpath) {
+ function getElement(xpath) {
+ var polterAgent = window.__poltergeist;
+ var ids = polterAgent.find("xpath", xpath, document);
+ var polterNode = polterAgent.get(ids[0]);
+ return polterNode.element;
+ }
+
+ var node = getElement(xpath);
+ if(typeof node.selected == "undefined"){
+ return null;
+ }
+ return node.selected;
+}('{{xpath}}'));
+{% endautoescape %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/set_value.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/set_value.js.twig
new file mode 100644
index 000000000..605aec37a
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/set_value.js.twig
@@ -0,0 +1,213 @@
+{% autoescape 'js' %}
+(function (xpath, value) {
+ function getElement(xpath, within) {
+ var result;
+ if (within === null || within === undefined) {
+ within = document;
+ }
+ result = document.evaluate(xpath, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ if (result.snapshotLength !== 1) {
+ return null;
+ }
+ return result.snapshotItem(0);
+ }
+
+ function isInput(element) {
+ if (element === null || element === undefined) {
+ return false;
+ }
+ return (element.tagName.toLowerCase() == "input");
+ }
+
+ function isTextArea(element) {
+ if (element === null || element === undefined) {
+ return false;
+ }
+ return (element.tagName.toLowerCase() == "textarea");
+ }
+
+ function isSelect(element) {
+ if (element === null || element === undefined) {
+ return false;
+ }
+ return (element.tagName.toLowerCase() == "select");
+ }
+
+ function deselectAllOptions(element) {
+ var i, l = element.options.length;
+ for (i = 0; i < l; i++) {
+ element.options[i].selected = false;
+ }
+ }
+
+ function xpathStringLiteral(s) {
+ if (s.indexOf('"') === -1)
+ return '"' + s + '"';
+ if (s.indexOf("'") === -1)
+ return "'" + s + "'";
+ return 'concat("' + s.replace(/"/g, '",\'"\',"') + '")';
+ }
+
+ function clickOnElement(element) {
+ // create a mouse click event
+ var event = document.createEvent('MouseEvents');
+ event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+ // send click to element
+ element.dispatchEvent(event);
+
+ //After dispatching the event let's wait for 2 seconds at least...
+ return setTimeout(function () {
+ }, 2);
+ }
+
+ function dispatchChange(element) {
+ var tagName =element.tagName.toLowerCase();
+ var elementType = element.getAttribute("type");
+ if (tagName != "option" || (tagName == "input" && elementType == "radio")){
+ return true;
+ }
+ //Force the change when element is option
+ var event;
+ event = document.createEvent('HTMLEvents');
+ event.initEvent('change', true, false);
+ element.dispatchEvent(event);
+ return true;
+ }
+
+ function selectOptionOnElement(element, option, multiple) {
+ var polterAgent = window.__poltergeist;
+ var escapedOption = xpathStringLiteral(option);
+ // The value of an option is the normalized version of its text when it has no value attribute
+ var optionQuery = ".//option[@value = " + escapedOption + " or (not(@value) and normalize-space(.) = " + escapedOption + ")]";
+ var ids = polterAgent.find("xpath", optionQuery, element);
+ var polterNode = polterAgent.get(ids[0]);
+ var optionElement = polterNode.element;
+
+ if (multiple || !element.multiple) {
+ if (!optionElement.selected) {
+ clickOnElement(optionElement);
+ optionElement.selected = true;
+ }
+ return dispatchChange(optionElement);
+ }
+
+ deselectAllOptions(element);
+ clickOnElement(optionElement);
+ optionElement.selected = true;
+ return dispatchChange(optionElement);
+ }
+
+ function selectSetValue(element, value) {
+ var option;
+ if ((Array.isArray && Array.isArray(value)) || (value instanceof Array)) {
+ deselectAllOptions(element);
+ for (option in value) {
+ if (value.hasOwnProperty(option)) {
+ selectOptionOnElement(element, value[option], true);
+ }
+ }
+ return true;
+ }
+
+ selectOptionOnElement(element, value, false);
+ return true;
+ }
+
+ function selectRadioValue(element, value) {
+ if (element.value === value) {
+ clickOnElement(element);
+ element.checked=true;
+ dispatchChange(element);
+ return true;
+ }
+
+ var formElements = element.form.elements;
+ var name = element.getAttribute("name");
+ var radioElement, i;
+
+ if (!name) {
+ return null;
+ }
+
+ for (i = 0; i < formElements.length; i++) {
+ radioElement = formElements[i];
+ if (radioElement.tagName.toLowerCase() == 'input' && radioElement.type.toLowerCase() == 'radio' && radioElement.name === name) {
+ if (value === radioElement.value) {
+ clickOnElement(radioElement);
+ radioElement.checked=true;
+ dispatchChange(radioElement);
+ return true;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ function inputSetValue(element, value, elementXpath) {
+ var allowedTypes = ['submit', 'image', 'button', 'reset'];
+ var elementType = element.type.toLowerCase();
+ var textLikeInputType = ['file', 'text', 'password', 'url', 'email', 'search', 'number', 'tel', 'range', 'date', 'month', 'week', 'time', 'datetime', 'color', 'datetime-local'];
+
+ if (allowedTypes.indexOf(elementType) !== -1) {
+ return null;
+ }
+
+ if (elementType == "checkbox") {
+ var booleanValue = false;
+ if (value == "1" || value == 1) {
+ booleanValue = true;
+ } else if (value == "0" || value == 0) {
+ booleanValue = false;
+ }
+ if ((element.checked && !booleanValue) || (!element.checked && booleanValue)) {
+ clickOnElement(element);
+ dispatchChange(element);
+ }
+ return true;
+ }
+
+ if (elementType == "radio") {
+ return selectRadioValue(element, value);
+ }
+
+ if (textLikeInputType.indexOf(elementType) !== -1) {
+ return textAreaSetValue(elementXpath, value);
+ }
+
+ //No support for the moment for file stuff or other input types
+ return null;
+
+ }
+
+ function textAreaSetValue(elementXpath, value) {
+ var polterAgent = window.__poltergeist;
+ var ids = polterAgent.find("xpath", elementXpath, document);
+ var polterNode = polterAgent.get(ids[0]);
+ polterNode.set(value);
+ return true;
+ }
+
+ var node = getElement(xpath);
+ if (node === null) {
+ return null;
+ }
+
+ if (isSelect(node)) {
+ return selectSetValue(node, value);
+ }
+
+ if (isInput(node)) {
+ return inputSetValue(node, value, xpath);
+ }
+
+ if (isTextArea(node)) {
+ return textAreaSetValue(xpath, value);
+ }
+
+ //for the moment everything else also to textArea stuff
+ return textAreaSetValue(xpath, value);
+
+}('{{xpath}}', JSON.parse('{{ value }}')));
+{% endautoescape %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/SessionTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/SessionTrait.php
new file mode 100644
index 000000000..6443dffac
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/SessionTrait.php
@@ -0,0 +1,50 @@
+started = true;
+ }
+
+ /**
+ * Tells if the session is started or not
+ * @return bool
+ */
+ public function isStarted() {
+ return $this->started;
+ }
+
+ /**
+ * Stops the session completely, clean slate for the browser
+ * @return bool
+ */
+ public function stop() {
+ //Since we are using a remote browser "API", stopping is just like resetting, say good bye to cookies
+ //TODO: In the future we may want to control a start / stop of the remove browser
+ return $this->reset();
+ }
+
+ /**
+ * Clears the cookies in the browser, all of them
+ * @return bool
+ */
+ public function reset() {
+ $this->getBrowser()->clearCookies();
+ $this->getBrowser()->reset();
+ $this->started = false;
+ return true;
+ }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/WindowTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/WindowTrait.php
new file mode 100644
index 000000000..92fc6ee3a
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/WindowTrait.php
@@ -0,0 +1,64 @@
+browser->windowName();
+ }
+
+ /**
+ * Return all the window handles currently present in phantomjs
+ * @return array
+ */
+ public function getWindowNames() {
+ return $this->browser->windowHandles();
+ }
+
+ /**
+ * Switches to window by name if possible
+ * @param $name
+ * @throws DriverException
+ */
+ public function switchToWindow($name = null) {
+ $handles = $this->browser->windowHandles();
+ if ($name === null) {
+ //null means back to the main window
+ return $this->browser->switchToWindow($handles[0]);
+ }
+
+ $windowHandle = $this->browser->windowHandle($name);
+ if (!empty($windowHandle)) {
+ $this->browser->switchToWindow($windowHandle);
+ } else {
+ throw new DriverException("Could not find window handle by a given window name: $name");
+ }
+
+ }
+
+ /**
+ * Resizing a window with specified size
+ * @param int $width
+ * @param int $height
+ * @param string $name
+ * @throws DriverException
+ */
+ public function resizeWindow($width, $height, $name = null) {
+ if ($name !== null) {
+ //TODO: add this on the phantomjs stuff
+ throw new DriverException("Resizing other window than the main one is not supported yet");
+ }
+ $this->browser->resize($width, $height);
+ }
+
+}