phantomjs (2.1.1+dfsg-2) unstable; urgency=medium

* New patch to allow comilation with QT versions other than 5.5
    (Closes: #827421).
  * Run tests under "dbus-launch"; Build-Depends += "dbus-x11".
  * Respect DEB_BUILD_OPTIONS="nocheck".
  * README: added note about lack of headless functionality.
  * Corrected short package description (Closes: #826541).
    Thanks, Mario Lang.
  * Standards-Version: 3.9.8.
  * Corrected Vcs-Git URL.

# imported from the archive
This commit is contained in:
Dmitry Smirnov 2016-06-16 09:26:25 +02:00
commit 0a624cd9a7
600 changed files with 104977 additions and 0 deletions

57
.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
.DS_Store
*.pro.user*
*.xcodeproj
Makefile*
*~
*.moc
moc_*
qrc_*
.qmake.stash
*.o
*.swp
*.pyc
*.a
/deploy/qt-*.tar.gz
/deploy/Qt-*
/symbols
/src/qt/qtc-debugging-helper
/src/phantomjs_plugin_import.cpp
# ignore ctags
/tags
/tools/dump_syms.app/
# Ignore Visual Studio temporary files, build results, etc
*.suo
*.user
*.sln.docstates
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.sdf
*.vcxproj
*.vcxproj.filters
*.lib
*.prl
*.intermediate.manifest
# Build results
[Dd]ebug*/
[Rr]elease/
bin/
*.class
build/
.gradle/

11
.gitmodules vendored Normal file
View File

@ -0,0 +1,11 @@
[submodule "qtbase"]
path = src/qt/qtbase
url = https://github.com/Vitallium/qtbase.git
branch = phantomjs
[submodule "qtwebkit"]
path = src/qt/qtwebkit
url = https://github.com/Vitallium/qtwebkit.git
branch = phantomjs
[submodule "3rdparty-win"]
path = src/qt/3rdparty
url = https://github.com/Vitallium/phantomjs-3rdparty-win.git

87
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,87 @@
# Contribution Guide
This page describes how to contribute changes to PhantomJS.
Please do **not** create a pull request without reading this guide first. Failure to do so may result in the **rejection** of the pull request.
## For The Impatients
**Work on a feature branch**.
If your changes need to be modified due to some reviews, it is less clutter to tweak an isolated feature branch and push it again.
**Create a ticket in the issue tracker**.
This serves as a placeholder for important feedback, review, or any future updates.
In the commit message:
* **Keep the first line < 72 characters**. Write additional paragraphs
if necessary.
* **Put the link to the issue** (see above). This is important for cross-referencing purposes.
## Communicate
*Second opinion is always important.*
**Bug fixing**. If you have a fix for a bug, please attach your patch in the corresponding issue in the [issue tracker](https://github.com/ariya/phantomjs/issues). If there is no entry for the bug yet, then please create a new one. If you are confident working with Git, see the Get Ready section below on how to submit your change.
**Improvement and feature request**. If you have an improvement idea, please send an email to the [mailing list](http://groups.google.com/group/phantomjs) (rather than contacting the developers directly) so that other people can give their insights and opinions. This is also important to avoid duplicate work.
**Task management**. Once the feature idea is agreed upon and translated into concrete actions and tasks, please use the [issue tracker](https://github.com/ariya/phantomjs/issues) to create an issue for each individual task. Further technical discussion about the task and the implementation details should be carried out in the issue tracker.
**Extending with new API**. Whenever you want to introduce a new API, please send an email to the mailing list along with the link to the issue. Consider good API name for the object or function, read the [API Design Principle](http://wiki.qt.io/API_Design_Principles) article. It may require few iterations to agree on the final API and hence it is important to engage all interested parties as early as possible.
## Get Ready
For your proposed change, you need to have:
* **an issue** (in the issue tracker) which describe your bug or feature
* **a feature branch** in your git fork
### Refer the Issue
The commit message needs to link to the issue. This cross-reference is [very important](http://ariya.ofilabs.com/2012/01/small-scale-software-craftsmanship.html) for the following reasons.
First, the commit log is frozen and can not be changed. If it contains a mistake or outdated information, the log can not be amended. However, further updates can be still posted to the linked issue, which can be followed from the commit log itself.
Second, it provides a placeholder for code review and other feedback.
An example of a bad commit log:
Fix Mountain Lion
The above log is too short and useless in the long run. A better version (and note the issue link):
Better support for OS X Mountain Lion.
require('system').os.version should give "10.8 (Mountain Lion)".
https://github.com/ariya/phantomjs/issues/10688
### Use Feature Branch
To isolate your change, please avoid working on the master branch. Instead, work on a *feature branch* (often also known as *topic branch*). You can create a new branch (example here crash-fix) off the master branch by using:
git checkout -b crash-fix master
Refer to your favorite Git tutorial/book for further detailed help.
Some good practices for the feature branch:
* Give it a meaningful name instead of, e.g. `prevent-zero-divide` instead of just `fix`
* Make *granular* and *atomic* commits, e.g. do not mix a typo fix with some major refactoring
* Keep one branch for one specific issue. If you need to work on other unrelated issues, create another branch.
## Review and Merge
When your branch is ready, send the pull request.
While it is not always the case, often it is necessary to improve parts of your code in the branch. This is the actual review process.
Here is a check list for the review:
* It does not break the test suite
* There is no typo
* The coding style follows the existing one
* There is a reasonable amount of comment
* The license header is intact
* All examples are still working

401
ChangeLog Normal file
View File

@ -0,0 +1,401 @@
Please see also http://phantomjs.org/releases.html.
2016-01-23: Version 2.1.0
New features
* Upgraded Qt to 5.5.1 (issue #13377)
* Added support for SSL Client Authentication (issue #11275)
* Added support for context menu event (issue #11429)
* Allow remote debugging to use random port assigned by the OS (issue #13432)
Improvements
* Allow outer context to access arbitrary URLs (issue #11217)
* Fixed --local-storage-path and localStoragePath config option (issue #11596)
* Restored --local-url-access=no regression (issue #13412)
* Fixed an issue with loading JS modules contains a last-line comment (issue #12868)
* Fixed an issue with returning binary content in WebServer module (issue #13026)
* Fixed encoded URL loading on Windows (issue #12953)
* Fixed building with GCC 5 (issue #13518)
* Fixed file upload (issue #12506)
* Fixed latest OS detection (issue #13829)
2015-01-23: Version 2.0.0
New features
* Switched to Qt 5 and updated WebKit (issue 10448)
* Implemented clearing of memory cache (issue 10357)
* Added support for HTTP header change for every request (issue 11299)
Improvements
* Fixed rendering of CJK text by always linking the codecs (issue 10249)
* Ensured onResourceReceived is still fired on an error (issue 11163)
* Fixed possible crash in handling network requests (issue 11252)
* Removed hardcoded GhostDriver launching message (issue 12681)
* Allowed disk cache more than 2 GB (issue 12303)
Examples
* Netsniff example should exit when fails to load (issue 11333)
2014-01-25: Version 1.9.7
* Reverted to GhostDriver 1.1.0 instead of 1.1.1 (issue 11915)
* Fixed another warning of obsolete userSpaceScaleFactor on OS X 10.9 (issue 11612)
2014-01-20: Version 1.9.6
* Updated GhostDriver to version 1.1.1 (issue 11877, 11893)
2014-01-19: Version 1.9.3
* Fixed CoreText performance note on OS X 10.9 (issue 11418)
* Fixed warning of obsolete userSpaceScaleFactor on OS X 10.9 (issue 11612)
2013-09-06: Version 1.9.2
* Fixed graphical artifacts with transparent background on Windows (issue 11276, 11007, 11366)
* Updated GhostDriver to version 1.0.4 (issue 11452)
2013-06-04: Version 1.9.1
Critical bug fixes:
* Fixed problems with specifying proxy server (issue 10811, 11117)
* Fixed UTF-8 encoding with system.stdout and system.stderr (issue 11162)
* Ensured that onResourceReceived will be always invoked (issue 11163)
* Fixed module loading from an absolute path on Windows (issue 11165)
* Fixed typo in the command-line option for setting the cache size (11219)
* Fixed possible crash when handling network requests (issue 11252, 11338)
2013-03-20: Version 1.9.0 "Sakura"
New features
* Added spawn and execFile to execute external programs (issue 10219)
* Added the ability to abort network requests (issue 10230)
* Added system access to stdin, stdout, and stderr (issue 10333)
* Added support for custom CA certificates location (issue 10916)
* Added seek function to the File stream (issue 10937)
* Implemented file read for a specified number of bytes (issue 10938)
* Added a callback to handle network error (issue 10954, 10997)
* Added custom encoding support when opening a page (issue 11043)
* Implemented require.stub() support for a factory function (issue 11044)
* Added page loading indicator and progress (issue 11091)
* Added a timeout option for network requests (issue 11129)
Improvements
* Fixed the build on FreeBSD (issue 10597)
* Ensured a consistent 72 dpi for Linux headless rendering (issue 10659)
* Fixed possible PDF error due to invalid CreationDate field (issue 10663)
* Fixed crash when uploading non existing files (issue 10941)
* Improved the autocomplete internal of the interactive/REPL mode (issue 10943)
* Fixed possible crash when accessing inline frames (issue 10947)
* Changed Linux binary package setup to be built on CentOS 5 (issue 10963)
* Extended SSL ignore setting to synchronous XHR (issue 10985)
* Added convenient constants for modifier keys (issue 11056)
* Fixed incorrect date handling in the cookies (issue 11068)
* Updated GhostDriver to version 1.0.3 (issue 11146)
Examples
* Fixed invalid data URI in the netsniff example (issue 10740)
* Implemented a new weather example (issue 10794)
* Fixed rendering issues in render_multi_url (issue 11021)
* Fixed proper event sequence in page_events example (issue 11028)
* Miscellanous tweaks (issue 11082)
2013-03-02: Version 1.8.2
Critical bug fixes:
* Fixed possible PDF error due to invalid CreationDate field (issue 663)
* Fixed crash when uploading non existing files (issue 941)
* Fixed possible crash when accessing inline frames (issue 947)
* Extended SSL ignore setting to synchronous XHR (issue 985)
* Fixed incorrect date handling in the cookies (issue 1068)
2013-01-06: Version 1.8.1
Critical bug fix:
* Mac OS X: Fix possible crash when using some TrueType fonts (issue 690)
2012-12-21: Version 1.8.0 "Blue Winter Rose"
New features
* Integrated GhostDriver as the WebDriver implementation (issue 49)
* Added an option to specify the SSL protocol (issue 174)
* Added encoding support for WebServer's response (issue 505)
* Added process ID (PID) to the System module (issue 769)
* Added properties to obtain page and frame title (issue 799)
* Added page navigation methods (issue 808)
* Added support for modifier keys in keyboard events (issue 835)
* Added onFilePicker callback for more generic file upload API (issue 843)
* Added the ability to set the page content and location (issue 909)
Improvements
* Fixed date parsing in ISO8601 format (issue 187, 267)
* Fixed window.location (issue 530, 632)
* Deregistered multiple callback handler (issue 807)
* Fixed sending of double-click events (issue 848)
* Increases maximum number of redirects (issue 849)
* Fixed keycodes sent for lowercase characters (issue 852)
* Fixed a regression in table row page break (issue 880)
* Completed the CoffeeScript version of the examples (issue 907)
* Updated Qt to version 4.8.4 (issue 918)
* Fixed potential hang in some example scripts (issue 922)
2012-09-22: Version 1.7.0 "Blazing Star"
New features
* Added a module system modelled after CommonJS/Node.js (issue 47)
* Added support for window pop-up (issue 151)
* Static build on Linux (issue 413)
* Added run-time detection of SSL support (issue 484)
* Added more events support (issue 492, 712)
* Added support for disabling automatic proxy detection (issue 580)
* Provided page closing callback (issue 678)
* Added methods to access URL, frames URL, frame Content (issue 758)
* Added more cookies-related API (issue 761)
Improvements
* Refactored command-line options handling (issue 55)
* Improved the workflow for producing release builds (issue 599)
* Improved cookies API and implementation (issue 603, 761)
* Improved frame switching API (issue 654)
* Fixed iframe handling regression (issue 683)
* Fixed OS version number with Windows 8 and Mountain Lion (issue 684, 688)
* Fixed HAR navigation info in the netsniff example (issue 733)
* Fixed compile warnings with Visual Studio (issue 744)
* Removed hacks for static linking on Windows (issue 753)
* Added ICO image handling on Windows (issue 779)
* Fixed font antialiasing on Windows (issue 785)
* Improved Jasmine test runner for Jasmine 1.2 (issue 792)
2012-07-22: Version 1.6.1
Bug fixes
* Don't build the deploy in debug mode (issue 599)
* Fixed building on Windows (issue 424)
* Fixed remote inspector when building statically (issue 430)
2012-06-20: Version 1.6.0 "Lavender"
New features
* Added support for passing arguments to WebPage's evaluate (issue 132)
* Added callbacks for JavaScript onConfirm and onPrompt (issue 133)
* Added stack trace when error occurs (issue 166)
* Added support for local storage path and quota (issue 300)
* Added initial support for cookies handling (issue 354)
* Added support for header footer when printing the page (issue 410, 512)
* Added headers support in the loading request (issue 452)
* Added support to render the web page as base64-encoded string (issue 547)
* Added hooks for navigation event (issue 562)
* Added command-line option to show debug messages (issue 575)
* Added support for the zoom factor for web page rendering (issue 579)
* Added crash reporter for Mac OS X and Linux, based on Google Breakpad (issue 576)
* Added 'os' object to the system module (issue 585)
* Added support for asynchronous evaluation (issue 593)
Improvements
* Fixed remote debugging to work on Mac OS X and Windows (issue 430)
* Fixed web server getting the dropped connection for empty response (issue 451)
* Fixed text rendered as boxes (squares) on headless Linux (issue 460)
* Updated Qt to version 4.8.2 (issue 495)
* Updated CoffeeScript compiler to version 1.3.3 (issue 496)
* Fixed the build script to detect and use MAKEFLAGS (issue 503)
* Fixed the build script to properly pass Qt config flags (issue 507)
* Changed Info.plist to be embedded in Mac OS X executable (issue 528)
* Fixed wrong module require in the imagebin example (issue 536)
* Fixed example scripts to exit with the right exit code (issue 544)
* Fixed build failure with glib 2.31.0+ (issue 559)
* Fixed error handler failures in some cases (issue 589)
* Fixed Twitter-related examples to work with the new site (issue 609)
2012-03-20: Version 1.5.0 "Ghost Flower"
New features
* Added interactive mode, also known as REPL (issue 252)
* Added setting for web security, to allow cross domain XHR (issue 28)
* Added error handler for WebPage object (issue 166)
* Added support for custom HTTP header in the network request (issue 77)
* Added support for read write encoding in the file system module (issue 367)
* Added remote debugging support on Linux (issue 6)
* Added support for proxy authentication (issue 105)
* Added System module, to retrieve environment variables (issue 271) and arguments (issue 276)
* Added fs.readLink function (issue 329)
* Added support for reading and writing binary data (issue 400)
* Added support to retrieve request data in the WebServer? module (issue 340)
* Added support for individual top/bottom/left/right print margins (issue 388)
* Added command-line option --help (issue 347)
* Added short command-line options -v and -h (issue 408)
* Removed support for Flash and other plugins (issue 418)
Bug fixes
* Fixed multiple console.log arguments (issue 36)
* Fixed file upload (issue 307)
* Fixed the web server instance to be asynchronous (issue 326) and still support Keep Alive (issue 416)
* Workaround Qt 4.8.0 crash due to empty URL scheme (issue 365)
* Fixed a Content-Type problem where POST does not work (issue 337)
* Fixed reading body request in the web server even without specific Content-Type (issue 439)
* Fixed Jasmine test runner with Jasmine 1.1 (issue 402)
* Fixed request URL formatting in the web server (issue 437)
* Don't display debugging and warning messages (issue 323)
2011-12-31: Version 1.4.1
Bug fixes
* Fix setting the proxy type (issue 266)
* Workaround for file upload regression (issue 307)
* Fix extraneous messsages in non-debug mode (issue 323)
2011-12-22: Version 1.4.0 "Glory of the Snow"
New features
* Added embedded HTTP server (issue 115)
* Added convenient build script for Linux (issue 197)
* Added support for SOCKS5 proxy (issue 266)
* Updated CoffeeScript compiler to version 1.2 (issue 312)
Bug fixes
* Fix potential crash in QUrl with Qt 4.8 (issue 304)
* Fix bug in CookieJar with QSettings and string (PyPhantomJS issue 10)
* Prevent showing the icon on Mac OS X Dock (issue 281)
Examples
* Added a new example to detect browsers sniffing (issue 263)
* Added HTTP server example (issue 115)
2011-09-23: Version 1.3.0 "Water Lily"
Bug fixes
* Fixed open() and POST method, without specifying the finished handler
* Fixed script execution warning dialog (issue 165)
* Added WebPage.release() to free the web page from memory (issue 154)
* Added special handling of about:blank (issue 235)
* Made a separate network access manager for each page (issue 190)
New features
* Introduced file system API based on CommonJS Filesystem proposal (issue 129)
* Added support for persistent cookies (issue 91)
* Added event handling, currently only for mouse events (issue 234)
* Added page scroll position (issue 162)
* Added HTTP authentication support (issue 45)
* Added callback for page initialization (issue 143)
* Added support to specify script and output encoding (issue 186)
* Added option to allow local content to do cross-domain access (issue 28)
* Added support to apply configurations from a JSON file (issue 180)
* Added a convenient WebPage initialization construction (issue 206)
* Added option to limit the size of disk cache (issue 220)
Examples
* Added a new example on using Modernizr to detect features (issue 144)
* Fixed pizza.js example to use Mobile Yelp (issue 200)
* Fixed netsniff.coffee example due to wrong indentation (issue 225)
* Added an example to show live network traffic (issue 227)
* Added an example demonstrating different output encodings (issue 186)
2011-06-21: Version 1.2.0 "Birds of Paradise"
Version 1.2.0 is a major update. It introduces a whole set of new API.
Bug fixes
* Fixed rendering a very large web page (issue 54)
* Fixed reporting of CoffeeScript compile error (issue 125)
New features
* Added callback for console message (issue 12)
* Improved security model via WebPage object (issue 41)
* Added support for POST, HEAD, PUT, and DELETE (issue 88)
* Scripts filename is now passed as phantom.scriptName
* Added callback to capture resource requests and responses (issue 2)
* Added the ability to load external JavaScript (issue 32)
Examples
* Ported examples to use WebPage object
* Added a new example to upload an image to imagebin.org
* Added a new example to show HTTP POST feature
* Added a new example to sniff network traffic and save it in HAR format
2011-04-27: Version 1.1.0 "Cherry Blossom"
Fixed the script loading to use UTF-8 encoding (Yasuhiro Matsumoto).
Added check for system proxy setting (Yasuhiro Matsumoto).
Fixed building with Cygwin and Qt 4.5 (John Dalton).
Added a new example: driver for QUnit tests (Łukasz Korecki).
Fixed issue #20: problem with JPG transparent color (Alessandro Portale).
Fixed issue #9: ignore first line starting with #! (Matthias, aka fourplusone).
Fixed issue #7: support for file upload for form submission (Matthias, aka fourplusone).
Fixed issue #35: support for disabling images loading (Ariya Hidayat).
Fixed issue #14: enable or disable plugins (Ariya Hidayat).
Added a new example: using Canvas to produce the color wheel (Ariya Hidayat).
Added support for rasterizing as GIF image (Ariya Hidayat).
Added support for CoffeeScript (Ariya Hidayat).
Fixed issue #19: option for setting the proxy (Clint Berry, Ariya Hidayat).
Python implementation using PyQt (James Roe).
Fixed issue #17: Specify paper size for PDF export (Alessandro Portale).
Fixed issue #60: Win32 and OS/2 icon files (Salvador Parra Camacho).
Added clipping rectangle to the render function (Wouter de Bie).
Added an example on sychronous waiting (Gabor Torok).
Added command line option to use disk cache (Jon Turner).
Added text extracting example (Weston Ruter).
Fixed issue #93: Build with Qt < 4.7 (Ariya Hidayat).
Ported all examples to CoffeeScript (Robert Gieseke).
2011-01-17: Version 1.0.0
Initial launch.
The API is centralized at the 'phantom' object (as child of
window object) which has the properties: args, content,
loadStatus, state, userAgent, version, viewportSize, and
the following functions: exit, open, render, sleep.
Several examples are included, among others: web page rasterizer,
weather service, headless test framework driver, and many others.

22
LICENSE.BSD Normal file
View File

@ -0,0 +1,22 @@
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# [PhantomJS](http://phantomjs.org) - Scriptable Headless WebKit
PhantomJS ([phantomjs.org](http://phantomjs.org)) is a headless WebKit scriptable with JavaScript. The latest [stable release](http://phantomjs.org/release-2.0.html) is version 2.0.
**Note**: Please **do not** create a GitHub pull request **without** reading the [Contribution Guide](https://github.com/ariya/phantomjs/blob/master/CONTRIBUTING.md) first. Failure to do so may result in the rejection of the pull request.
## Use Cases
- **Headless web testing**. Lightning-fast testing without the browser is now possible!
- **Page automation**. [Access and manipulate](http://phantomjs.org/page-automation.html) web pages with the standard DOM API, or with usual libraries like jQuery.
- **Screen capture**. Programmatically [capture web contents](http://phantomjs.org/screen-capture.html), including CSS, SVG and Canvas. Build server-side web graphics apps, from a screenshot service to a vector chart rasterizer.
- **Network monitoring**. Automate performance analysis, track [page loading](http://phantomjs.org/network-monitoring.html) and export as standard HAR format.
## Features
- **Multiplatform**, available on major operating systems: Windows, Mac OS X, Linux, and other Unices.
- **Fast and native implementation** of web standards: DOM, CSS, JavaScript, Canvas, and SVG. No emulation!
- **Pure headless (no X11) on Linux**, ideal for continuous integration systems. Also runs on Amazon EC2, Heroku, and Iron.io.
- **Easy to install**: [Download](http://phantomjs.org/download.html), unpack, and start having fun in just 5 minutes.
## Questions?
- Explore the complete [documentation](http://phantomjs.org/documentation/).
- Read tons of [user articles](http://phantomjs.org/buzz.html) on using PhantomJS.
- Join the [mailing-list](http://groups.google.com/group/phantomjs) and discuss with other PhantomJS fans.
PhantomJS is free software/open source, and is distributed under the [BSD license](http://opensource.org/licenses/BSD-3-Clause). It contains third-party code, see the included `third-party.txt` file for the license information on third-party code.
PhantomJS is created and maintained by [Ariya Hidayat](http://ariya.ofilabs.com/about) (Twitter: [@ariyahidayat](http://twitter.com/ariyahidayat)), with the help of [many contributors](https://github.com/ariya/phantomjs/contributors). Follow the official Twitter stream [@PhantomJS](http://twitter.com/PhantomJS) to get the frequent development updates.

458
build.py Executable file
View File

@ -0,0 +1,458 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of the PhantomJS project from Ofi Labs.
#
# Copyright (C) 2014 Milian Wolff, KDAB <milian.wolff@kdab.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import argparse
import os
import platform
import sys
import subprocess
import re
import multiprocessing
root = os.path.abspath(os.path.dirname(__file__))
third_party_names = ["libicu", "libxml", "openssl", "zlib"]
third_party_path = os.path.join(root, "src", "qt", "3rdparty")
openssl_search_paths = [{
"name": "Brew",
"header": "/usr/local/opt/openssl/include/openssl/opensslv.h",
"flags": [
"-I/usr/local/opt/openssl/include",
"-L/usr/local/opt/openssl/lib"
]
}, {
"name": "MacPorts",
"header": "/opt/local/include/openssl/opensslv.h",
"flags": [
"-I/opt/local/include",
"-L/opt/local/lib"
]
}]
# check if path points to an executable
# source: http://stackoverflow.com/a/377028
def isExe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
# find path to executable in PATH environment variable, similar to UNIX which command
# source: http://stackoverflow.com/a/377028
def which(program):
if isExe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip("")
exe_file = os.path.join(path, program)
if isExe(exe_file):
return exe_file
return None
# returns the path to the QMake executable which gets built in our internal QtBase fork
def qmakePath():
exe = "qmake"
if platform.system() == "Windows":
exe += ".exe"
return os.path.abspath("/usr/bin/" + exe)
# returns paths for 3rd party libraries (Windows only)
def findThirdPartyDeps():
include_dirs = []
lib_dirs = []
for dep in third_party_names:
include_dirs.append("-I")
include_dirs.append(os.path.join(third_party_path, dep, "include"))
lib_dirs.append("-L")
lib_dirs.append(os.path.join(third_party_path, dep, "lib"))
return (include_dirs, lib_dirs)
class PhantomJSBuilder(object):
options = {}
makeCommand = []
def __init__(self, options):
self.options = options
# setup make command or equivalent with arguments
if platform.system() == "Windows":
makeExe = which("jom.exe")
if not makeExe:
makeExe = "nmake"
self.makeCommand = [makeExe, "/NOLOGO"]
else:
flags = []
if self.options.jobs:
# number of jobs explicitly given
flags = ["-j", self.options.jobs]
elif not re.match("-j\s*[0-9]+", os.getenv("MAKEFLAGS", "")):
# if the MAKEFLAGS env var does not contain any "-j N", set a sane default
flags = ["-j", multiprocessing.cpu_count()]
self.makeCommand = ["make"]
self.makeCommand.extend(flags)
# if there is no git subdirectory, automatically go into no-git
# mode
if not os.path.isdir(".git"):
self.options.skip_git = True
# run the given command in the given working directory
def execute(self, command, workingDirectory):
# python 2 compatibility: manually convert to strings
command = [str(c) for c in command]
workingDirectory = os.path.abspath(workingDirectory)
print("Executing in %s: %s" % (workingDirectory, " ".join(command)))
if self.options.dry_run:
return 0
process = subprocess.Popen(command, stdout=sys.stdout, stderr=sys.stderr, cwd=workingDirectory)
process.wait()
return process.returncode
# run git clean in the specified path
def gitClean(self, path):
if self.options.skip_git: return 0
return self.execute(["git", "clean", "-xfd"], path)
# run make, nmake or jom in the specified path
def make(self, path):
return self.execute(self.makeCommand, path)
# run qmake in the specified path
def qmake(self, path, options):
qmake = qmakePath()
# verify that qmake was properly built
if not isExe(qmake) and not self.options.dry_run:
raise RuntimeError("Could not find QMake executable: %s" % qmake)
command = [qmake]
if self.options.qmake_args:
command.extend(self.options.qmake_args)
if options:
command.extend(options)
return self.execute(command, path)
# returns a list of platform specific Qt Base configure options
def platformQtConfigureOptions(self):
platformOptions = []
if platform.system() == "Windows":
platformOptions = [
"-mp",
"-static-runtime",
"-no-cetest",
"-no-angle",
"-icu",
"-openssl",
"-openssl-linked",
]
deps = findThirdPartyDeps()
platformOptions.extend(deps[0])
platformOptions.extend(deps[1])
else:
# Unix platform options
platformOptions = [
# use the headless QPA platform
"-qpa", "phantom",
# explicitly compile with SSL support, so build will fail if headers are missing
"-openssl", "-openssl-linked",
# disable unnecessary Qt features
"-no-openvg",
"-no-eglfs",
"-no-egl",
"-no-glib",
"-no-gtkstyle",
"-no-cups",
"-no-sm",
"-no-xinerama",
"-no-xkb",
"-no-xcb",
"-no-kms",
"-no-linuxfb",
"-no-directfb",
"-no-mtdev",
"-no-libudev",
"-no-evdev",
"-no-pulseaudio",
"-no-alsa",
"-no-feature-PRINTPREVIEWWIDGET"
]
if self.options.silent:
platformOptions.append("-silent")
if platform.system() == "Darwin":
# Mac OS specific options
# NOTE: fontconfig is not required on Darwin (we use Core Text for font enumeration)
platformOptions.extend([
"-no-pkg-config",
"-no-c++11", # Build fails on mac right now with C++11 TODO: is this still valid?
])
# Dirty hack to find OpenSSL libs
openssl = os.getenv("OPENSSL", "")
if openssl == "":
# search for OpenSSL
openssl_found = False
for search_path in openssl_search_paths:
if os.path.exists(search_path["header"]):
openssl_found = True
platformOptions.extend(search_path["flags"])
print("Found OpenSSL installed via %s" % search_path["name"])
if not openssl_found:
raise RuntimeError("Could not find OpenSSL")
else:
# TODO: Implement
raise RuntimeError("Not implemented")
else:
# options specific to other Unixes, like Linux, BSD, ...
platformOptions.extend([
"-fontconfig", # Fontconfig for better font matching
"-icu", # ICU for QtWebKit (which provides the OSX headers) but not QtBase
])
return platformOptions
# configure Qt Base
def configureQtBase(self):
print("configuring Qt Base, please wait...")
configureExe = os.path.abspath("src/qt/qtbase/configure")
if platform.system() == "Windows":
configureExe += ".bat"
configure = [configureExe,
"-static",
"-opensource",
"-confirm-license",
# we use an in-source build for now and never want to install
"-prefix", os.path.abspath("src/qt/qtbase"),
# use the bundled libraries, vs. system-installed ones
"-qt-zlib",
"-qt-libpng",
"-qt-libjpeg",
"-qt-pcre",
# disable unnecessary Qt features
"-nomake", "examples",
"-nomake", "tools",
"-nomake", "tests",
"-no-qml-debug",
"-no-dbus",
"-no-opengl",
"-no-audio-backend",
"-D", "QT_NO_GRAPHICSVIEW",
"-D", "QT_NO_GRAPHICSEFFECT",
"-D", "QT_NO_STYLESHEET",
"-D", "QT_NO_STYLE_CDE",
"-D", "QT_NO_STYLE_CLEANLOOKS",
"-D", "QT_NO_STYLE_MOTIF",
"-D", "QT_NO_STYLE_PLASTIQUE",
"-D", "QT_NO_PRINTPREVIEWDIALOG"
]
configure.extend(self.platformQtConfigureOptions())
if self.options.qt_config:
configure.extend(self.options.qt_config)
if self.options.debug:
configure.append("-debug")
elif self.options.release:
configure.append("-release")
else:
# build Release by default
configure.append("-release")
if self.execute(configure, "src/qt/qtbase") != 0:
raise RuntimeError("Configuration of Qt Base failed.")
# build Qt Base
def buildQtBase(self):
if self.options.skip_qtbase:
print("Skipping build of Qt Base")
return
if self.options.git_clean_qtbase:
self.gitClean("src/qt/qtbase")
if self.options.git_clean_qtbase or not self.options.skip_configure_qtbase:
self.configureQtBase()
print("building Qt Base, please wait...")
if self.make("src/qt/qtbase") != 0:
raise RuntimeError("Building Qt Base failed.")
# build Qt WebKit
def buildQtWebKit(self):
if self.options.skip_qtwebkit:
print("Skipping build of Qt WebKit")
return
if self.options.git_clean_qtwebkit:
self.gitClean("src/qt/qtwebkit")
os.putenv("SQLITE3SRCDIR", os.path.abspath("src/qt/qtbase/src/3rdparty/sqlite"))
print("configuring Qt WebKit, please wait...")
configureOptions = [
# disable some webkit features we do not need
"WEBKIT_CONFIG-=build_webkit2",
"WEBKIT_CONFIG-=netscape_plugin_api",
"WEBKIT_CONFIG-=use_gstreamer",
"WEBKIT_CONFIG-=use_gstreamer010",
"WEBKIT_CONFIG-=use_native_fullscreen_video",
"WEBKIT_CONFIG-=video",
"WEBKIT_CONFIG-=web_audio",
]
if self.options.webkit_qmake_args:
configureOptions.extend(self.options.webkit_qmake_args)
if self.qmake("src/qt/qtwebkit", configureOptions) != 0:
raise RuntimeError("Configuration of Qt WebKit failed.")
print("building Qt WebKit, please wait...")
if self.make("src/qt/qtwebkit") != 0:
raise RuntimeError("Building Qt WebKit failed.")
# build PhantomJS
def buildPhantomJS(self):
print("Configuring PhantomJS, please wait...")
if self.qmake(".", self.options.phantomjs_qmake_args) != 0:
raise RuntimeError("Configuration of PhantomJS failed.")
print("Building PhantomJS, please wait...")
if self.make(".") != 0:
raise RuntimeError("Building PhantomJS failed.")
# ensure the git submodules are all available
def ensureSubmodulesAvailable(self):
if self.options.skip_git: return
if self.execute(["git", "submodule", "init"], ".") != 0:
raise RuntimeError("Initialization of git submodules failed.")
if self.execute(["git", "submodule", "update", "--init"], ".") != 0:
raise RuntimeError("Initial update of git submodules failed.")
# run all build steps required to get a final PhantomJS binary at the end
def run(self):
self.ensureSubmodulesAvailable();
self.buildQtBase()
self.buildQtWebKit()
self.buildPhantomJS()
# parse command line arguments and return the result
def parseArguments():
parser = argparse.ArgumentParser(description="Build PhantomJS from sources.")
parser.add_argument("-r", "--release", action="store_true",
help="Enable compiler optimizations.")
parser.add_argument("-d", "--debug", action="store_true",
help="Build with debug symbols enabled.")
parser.add_argument("-j", "--jobs", type=int,
help="How many parallel compile jobs to use. Defaults to %d." % multiprocessing.cpu_count())
parser.add_argument("-c", "--confirm", action="store_true",
help="Silently confirm the build.")
parser.add_argument("-n", "--dry-run", action="store_true",
help="Only print what would be done without actually executing anything.")
# NOTE: silent build does not exist on windows apparently
if platform.system() != "Windows":
parser.add_argument("-s", "--silent", action="store_true",
help="Reduce output during compilation.")
advanced = parser.add_argument_group("advanced options")
advanced.add_argument("--qmake-args", type=str, action="append",
help="Additional arguments that will be passed to all QMake calls.")
advanced.add_argument("--webkit-qmake-args", type=str, action="append",
help="Additional arguments that will be passed to the Qt WebKit QMake call.")
advanced.add_argument("--phantomjs-qmake-args", type=str, action="append",
help="Additional arguments that will be passed to the PhantomJS QMake call.")
advanced.add_argument("--qt-config", type=str, action="append",
help="Additional arguments that will be passed to Qt Base configure.")
advanced.add_argument("--git-clean-qtbase", action="store_true",
help="Run git clean in the Qt Base folder.\n"
"ATTENTION: This will remove all untracked files!")
advanced.add_argument("--git-clean-qtwebkit", action="store_true",
help="Run git clean in the Qt WebKit folder.\n"
"ATTENTION: This will remove all untracked files!")
advanced.add_argument("--skip-qtbase", action="store_true",
help="Skip Qt Base completely and do not build it.\n"
"Only enable this option when Qt Base was built "
"previously and no update is required.")
advanced.add_argument("--skip-configure-qtbase", action="store_true",
help="Skip configure step of Qt Base, only build it.\n"
"Only enable this option when the environment has "
"not changed and only an update of Qt Base is required.")
advanced.add_argument("--skip-qtwebkit", action="store_true",
help="Skip Qt WebKit completely and do not build it.\n"
"Only enable this option when Qt WebKit was built "
"previously and no update is required.")
advanced.add_argument("--skip-configure-qtwebkit", action="store_true",
help="Skip configure step of Qt WebKit, only build it.\n"
"Only enable this option when neither the environment nor Qt Base "
"has changed and only an update of Qt WebKit is required.")
advanced.add_argument("--skip-git", action="store_true",
help="Skip all actions that require Git. For use when building from "
"a tarball release.")
options = parser.parse_args()
if options.debug and options.release:
raise RuntimeError("Cannot build with both debug and release mode enabled.")
return options
# main entry point which gets executed when this script is run
def main():
# change working directory to the folder this script lives in
os.chdir(os.path.dirname(os.path.realpath(__file__)))
try:
options = parseArguments()
if not options.confirm:
print("""\
----------------------------------------
WARNING
----------------------------------------
Building PhantomJS from source takes a very long time, anywhere from 30 minutes
to several hours (depending on the machine configuration). It is recommended to
use the premade binary packages on supported operating systems.
For details, please go the the web site: http://phantomjs.org/download.html.
""")
while True:
sys.stdout.write("Do you want to continue (Y/n)? ")
sys.stdout.flush()
answer = sys.stdin.readline().strip().lower()
if answer == "n":
print("Cancelling PhantomJS build.")
return
elif answer == "y" or answer == "":
break
else:
print("Invalid answer, try again.")
builder = PhantomJSBuilder(options)
builder.run()
except RuntimeError as error:
sys.stderr.write("\nERROR: Failed to build PhantomJS! %s\n" % error)
sys.stderr.flush()
sys.exit(1)
if __name__ == "__main__":
main()

28
debian/README.Debian vendored Normal file
View File

@ -0,0 +1,28 @@
## Limitations
Unlike original "phantomjs" binary that is statically linked with modified
QT+WebKit, Debian package is built with system libqt5webkit5. Unfortunately
the latter do not have webSecurity extensions therefore "--web-security=no"
is expected to fail.
https://github.com/ariya/phantomjs/issues/13727#issuecomment-155609276
---
Ghostdriver is crippled due to removed source-less pre-built blobs:
src/ghostdriver/third_party/webdriver-atoms/*
Therefore all PDF functionality is broken.
---
PhantomJS cannot run in headless mode (if there is no X server available).
Unfortunately it can not be fixed in Debian. To achieve headless-ness
upstream statically link with customised QT + Webkit. We don't want to ship
forks of those projects. It would be great to eventually convince upstream
to use standard libraries.
Meanwhile one can use "xvfb-run" from "xvfb" package:
xvfb-run --server-args="-screen 0 640x480x16" phantomjs

15
debian/TODO.Debian vendored Normal file
View File

@ -0,0 +1,15 @@
## Bug Selenium and Ghostdriver projects about source-less blobs.
## Fugure out remaining Build-Depends to successfully run test suite.
Some of those deps probably should be recommended by binary package.
In pbuilder tests fail with
Qt: Session management error: Could not open network socket
## Figure out how to produce QPA (Lighthouse) enabled binary
http://ariya.ofilabs.com/2012/03/pure-headless-phantomjs-no-x11-or-xvfb.html

5
debian/bin/phantomjs vendored Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
LD_LIBRARY_PATH="/usr/lib/phantomjs:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH
exec "/usr/lib/phantomjs/phantomjs" "$@"

166
debian/changelog vendored Normal file
View File

@ -0,0 +1,166 @@
phantomjs (2.1.1+dfsg-2) unstable; urgency=medium
* New patch to allow comilation with QT versions other than 5.5
(Closes: #827421).
* Run tests under "dbus-launch"; Build-Depends += "dbus-x11".
* Respect DEB_BUILD_OPTIONS="nocheck".
* README: added note about lack of headless functionality.
* Corrected short package description (Closes: #826541).
Thanks, Mario Lang.
* Standards-Version: 3.9.8.
* Corrected Vcs-Git URL.
-- Dmitry Smirnov <onlyjob@debian.org> Thu, 16 Jun 2016 17:26:25 +1000
phantomjs (2.1.1+dfsg-1) unstable; urgency=medium
* New upstream release [January 2015].
* Split refreshed "port-to-qt55.patch" into three patches:
+ build-qt55-evaluateJavaScript.patch
+ build-qt55-no-websecurity.patch
+ build-qt55-print.patch
* Disabled Ghostdriver due to pre-built source-less Selenium blobs.
* Added README.Debian explaining differences from upstream "phantomjs".
* Copyright review and DFSG clean-up/repackaging.
* Run upstream test suite.
* Updated watch file.
* Added man page.
* New maintainer.
* Build-Depends:
+ python
+ xvfb, xauth, libgl1-mesa-dri
+ gtk-vector-screenshot
+ libatk-adaptor
* New patches:
+ build-hardening.patch
+ build-no-ghostdriver.patch
+ build-qt-components.patch
+ build-qtpath.patch
* New lintian-overrides.
* Removed obsolete patches:
- dont-fail-on-unrecognised-makeflags.patch
[ Mattia Rizzolo <mattia@debian.org> ]
* debian/control: fix Vcs-Browser url
-- Dmitry Smirnov <onlyjob@debian.org> Thu, 18 Feb 2016 03:25:42 +1100
phantomjs (2.0.0+dfsg-1) unstable; urgency=medium
[ TANIGUCHI Takaki ]
* debian/watch: Moved to bitbucket.
* Imported Upstream version 2.0.0 (Closes: #803334, #732376, #757699)
+ Fix "PDF content is scaled by 1.5277777777777777" in upstrem.
(Closes: #726254)
+ Fix "SVG is not rendered in pdf" in upstream. (Closes: #726257)
+ Upstream drop support CoffeeScript. (Closes: #730087)
[ Ximin Luo ]
* Don't distribute a full obsolete copy of Qt.
* Port to Qt 5.5.
[ TANIGUCHI Takaki ]
* Use system Qt5 library. (Closes: #784506, #679759, #699435)
* Fix "ftbfs with GCC-5" (Closes: #778061)
* debian/watch: Move to bitbucket. (Closes: #754654)
* debian/control:
+ Bump-Standards Verision to 3.9.6.
+ Update VCS-*.
* debian/copyright: Update source URL.
-- TANIGUCHI Takaki <takaki@debian.org> Wed, 25 Nov 2015 17:41:05 +0900
phantomjs (1.9.2-1) unstable; urgency=low
* Imported Upstream version 1.9.2
* debian/control: Bump Standards-Version to 3.9.4 (with no changes).
* debian/control: Update B-D debhelper version to 9.
-- TANIGUCHI Takaki <takaki@debian.org> Wed, 09 Oct 2013 17:04:18 +0900
phantomjs (1.9.0-1) unstable; urgency=low
* Imported Upstream version 1.9.0 (Closes: #685404, #702381)
+ Fix ""Hello world" console.log() example prints nothing" (Closes: #696212)
* Fix "FTBFS: /usr/bin/ld: cannot find -ljpeg" (Closes: #679760)
* debian/patches/0001-build-with-libjs-coffeesciprt.patch: refresh patche
* debian/install: delete src/qt/*
* debian/patches/0002-Don-t-use-ld.gold-when-building-webkit.patch:
Fix FTBFS on i386. (Closes: #693761)
-- TANIGUCHI Takaki <takaki@debian.org> Wed, 15 May 2013 10:40:40 +0900
phantomjs (1.6.0-5) unstable; urgency=low
* debian/install: install all customized Qt ralted libraries.
(Closes: #681505, #681829)
-- TANIGUCHI Takaki <takaki@debian.org> Wed, 18 Jul 2012 15:42:26 +0900
phantomjs (1.6.0-4) unstable; urgency=low
* debian/patches/return_build_error: Removed.
* debian/bin/phantomjs: Exec with args. (Closes: #681096)
-- TANIGUCHI Takaki <takaki@debian.org> Wed, 11 Jul 2012 11:04:04 +0900
phantomjs (1.6.0-3) unstable; urgency=low
* debian/patches/return_build_error: To fail after build error immediately.
* debian/control: libx11-dev and libxext-dev to Build-Depends.
-- TANIGUCHI Takaki <takaki@debian.org> Thu, 05 Jul 2012 09:16:30 +0900
phantomjs (1.6.0-2) unstable; urgency=low
* Bump Standards-Version to 3.9.3.
* debian/rules: add hardening options.
* debian/control: Add some devel libraries to B-D. (Closes: #679760)
-- TANIGUCHI Takaki <takaki@debian.org> Tue, 03 Jul 2012 21:32:47 +0900
phantomjs (1.6.0-1) unstable; urgency=low
* New upstream (Closes: #666397)
* Drop python-pyphantomjs. It has been removed from upstream.
-- TANIGUCHI Takaki <takaki@debian.org> Sun, 01 Jul 2012 02:50:03 +0900
phantomjs (1.4.1+dfsg-1) unstable; urgency=low
* Imported Upstream version 1.4.1
-- TANIGUCHI Takaki <takaki@debian.org> Fri, 10 Feb 2012 10:24:27 +0900
phantomjs (1.4.0+dfsg-1) unstable; urgency=low
* New upstream
* debian/patches/0001-build-with-libjs-coffeesciprt.patch: Follow
upstream changes.
-- TANIGUCHI Takaki <takaki@debian.org> Tue, 27 Dec 2011 22:56:20 +0900
phantomjs (1.3.0+dfsg-4) unstable; urgency=low
* debian/patches/0001-build-with-libjs-coffeesciprt.patch: Fixed
coffee-script.js path. (Closes: #651171)
-- TANIGUCHI Takaki <takaki@debian.org> Tue, 06 Dec 2011 22:37:13 +0900
phantomjs (1.3.0+dfsg-3) unstable; urgency=low
* debian/control: Add python-qt4 to Depends (Closes: #648409)
-- TANIGUCHI Takaki <takaki@debian.org> Tue, 15 Nov 2011 11:31:06 +0900
phantomjs (1.3.0+dfsg-2) unstable; urgency=low
* debian/watch: improve pattern to return correct value.
-- TANIGUCHI Takaki <takaki@debian.org> Tue, 01 Nov 2011 09:16:37 +0900
phantomjs (1.3.0+dfsg-1) unstable; urgency=low
* Initial release (Closes: #646556)
-- TANIGUCHI Takaki <takaki@debian.org> Tue, 25 Oct 2011 15:13:08 +0900

66
debian/clean vendored Normal file
View File

@ -0,0 +1,66 @@
test/www/*.pyc
src/*.o
src/moc_*.cpp
bin/phantomjs
src/qt/lib/fonts
src/qt/mkspecs/default
src/qt/bin/qmake
src/qt/config.tests/unix/3dnow/3dnow.o
src/qt/config.tests/unix/3dnow/3dnow
src/qt/config.tests/unix/alsa/alsatest.o
src/qt/config.tests/unix/alsa/alsa
src/qt/config.tests/unix/avx/avx.o
src/qt/config.tests/unix/avx/avx
src/qt/config.tests/unix/clock-gettime/clock-gettime.o
src/qt/config.tests/unix/clock-gettime/clock-gettime
src/qt/config.tests/unix/clock-monotonic/clock-monotonic.o
src/qt/config.tests/unix/clock-monotonic/clock-monotonic
src/qt/config.tests/unix/floatmath/floatmath.o
src/qt/config.tests/unix/floatmath/floatmath
src/qt/config.tests/unix/getaddrinfo/getaddrinfotest.o
src/qt/config.tests/unix/getaddrinfo/getaddrinfo
src/qt/config.tests/unix/getifaddrs/getifaddrs.o
src/qt/config.tests/unix/getifaddrs/getifaddrs
src/qt/config.tests/unix/inotify/inotifytest.o
src/qt/config.tests/unix/inotify/inotify
src/qt/config.tests/unix/ipc_sysv/ipc.o
src/qt/config.tests/unix/ipc_sysv/ipc_sysv
src/qt/config.tests/unix/ipv6/ipv6test.o
src/qt/config.tests/unix/ipv6/ipv6
src/qt/config.tests/unix/ipv6ifname/ipv6ifname.o
src/qt/config.tests/unix/ipv6ifname/ipv6ifname
src/qt/config.tests/unix/mmx/mmx.o
src/qt/config.tests/unix/mmx/mmx
src/qt/config.tests/unix/mremap/mremap.o
src/qt/config.tests/unix/mremap/mremap
src/qt/config.tests/unix/nis/nis.o
src/qt/config.tests/unix/nis/nis
src/qt/config.tests/unix/openssl/openssl.o
src/qt/config.tests/unix/openssl/openssl
src/qt/config.tests/unix/ptrsize/ptrsizetest.o
src/qt/config.tests/unix/ptrsize/ptrsizetest
src/qt/config.tests/unix/sse/sse.o
src/qt/config.tests/unix/sse/sse
src/qt/config.tests/unix/sse2/sse2.o
src/qt/config.tests/unix/sse2/sse2
src/qt/config.tests/unix/sse3/sse3.o
src/qt/config.tests/unix/sse3/sse3
src/qt/config.tests/unix/sse4_1/sse4_1.o
src/qt/config.tests/unix/sse4_1/sse4_1
src/qt/config.tests/unix/sse4_2/sse4_2.o
src/qt/config.tests/unix/sse4_2/sse4_2
src/qt/config.tests/unix/ssse3/ssse3.o
src/qt/config.tests/unix/ssse3/ssse3
src/qt/config.tests/unix/stdint/main.o
src/qt/config.tests/unix/stdint/stdint
src/qt/config.tests/unix/stl/stltest.o
src/qt/config.tests/unix/stl/stl
src/qt/config.tests/x11/fontconfig/fontconfig.o
src/qt/config.tests/x11/fontconfig/fontconfig
src/qt/lib/libjscore.a
src/qt/lib/libwebcore.a
src/qt/src/3rdparty/webkit/Source/JavaScriptCore/release/libjscore.a
src/qt/src/3rdparty/webkit/Source/WebCore/release/libwebcore.a
src/qt/.qmake.vars

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

43
debian/control vendored Normal file
View File

@ -0,0 +1,43 @@
Source: phantomjs
Section: web
Priority: extra
Maintainer: Dmitry Smirnov <onlyjob@debian.org>
Uploaders: TANIGUCHI Takaki <takaki@debian.org>
Build-Depends: debhelper (>= 9),
libjs-coffeescript,
libfreetype6-dev,
libjpeg-dev,
libpng-dev,
libsqlite3-dev,
libssl-dev,
libz-dev,
libfontconfig1-dev,
libx11-dev,
libxext-dev,
libqt5webkit5-dev,
python,
qt5-qmake,
qtchooser
# Generic tests:
,dbus-x11
,gtk-vector-screenshot
,libatk-adaptor
,xvfb, xauth, libgl1-mesa-dri
# Ghostdriver tests:
# ,libgradle-core-java
Standards-Version: 3.9.8
Homepage: http://www.phantomjs.org/
Vcs-Git: https://anonscm.debian.org/git/collab-maint/phantomjs.git
Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/phantomjs.git
Package: phantomjs
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: minimalistic headless WebKit-based browser with JavaScript API
PhantomJS is a headless WebKit with JavaScript API. It has fast and native
support for various web standards: DOM handling, CSS selector, JSON, Canvas,
and SVG.
.
PhantomJS is an optimal solution for headless testing of web-based
applications, site scraping, pages capture, SVG renderer, PDF converter
and many other use cases.

197
debian/copyright vendored Normal file
View File

@ -0,0 +1,197 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: phantomjs
Source: https://github.com/ariya/phantomjs
Files-Excluded:
src/qt/*
src/ghostdriver/third_party/webdriver-atoms/*
test/ghostdriver-test/java/gradle/gradle-wrapper.jar
test/ghostdriver-test/fixtures/common/css/ui-lightness/jquery-ui-1.8.10.custom.css
test/ghostdriver-test/fixtures/common/js/jquery-1.4.4.min.js
test/ghostdriver-test/fixtures/common/js/jquery-ui-1.8.10.custom.min.js
test/www/regression/pjs-10690/Windsong.ttf
Files: *
Copyright:
2011-2012 Ariya Hidayat <ariya.hidayat@gmail.com>
2011-2012 Ivan De Marino <ivan.de.marino@gmail.com>
2012-2013 James M. Greene <james.m.greene@gmail.com>
2011 James Roe <roejames12@hotmail.com>
2013 Joseph Rollinson <jtrollinson@gmail.com>
2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
2012,2014 Milian Wolff, KDAB <milian.wolff@kdab.com>
2015 Zachary Weinberg <zackw@panix.com>
2011-2012 execjosh, http://execjosh.blogspot.com
License: BSD-3-Clause
Files: examples/modernizr.js
Copyright: 2009-2011 Faruk Ates, Paul Irish, Alex Sexton
License: BSD-3-Clause or Expat
Files: deploy/brandelf.c
Copyright:
2000,2001 David O'Brien
1996 Søren Schmidt
License: BSD-3-Clause
Files:
src/ghostdriver/*
test/ghostdriver-test/java/src/test/java/ghostdriver/*
Copyright:
2012-2014 Ivan De Marino <http://ivandemarino.me>
2014 Alex Anderson <@alxndrsn>
2014 Jim Evans <james.h.evans.jr@gmail.com> - Salesforce.com
2014 Steven Levithan <stevenlevithan.com>
License: BSD-2-Clause
Files:
test/ghostdriver-test/fixtures/common/form_handling_js_submit.html
Copyright:
2012 Selenium committers
2012 Software Freedom Conservancy
License: Apache-2.0
Files:
test/ghostdriver-test/fixtures/common/jquery-1.3.2.js
test/ghostdriver-test/fixtures/common/css/ui-lightness/*
Copyright:
2010-2011 AUTHORS.txt (http://jqueryui.com/about)
2009-2010 John Resig
2009-2010 the Dojo Foundation
License: Expat
Files:
src/ghostdriver/third_party/uuid.js
Copyright:
2010 Robert Kieffer
License: Expat
Comment: https://github.com/broofa/node-uuid/blob/master/LICENSE.md
Files:
src/linenoise/*
Copyright:
2010-2014 Salvatore Sanfilippo <antirez@gmail.com>
2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com>
2010-2011 Steve Bennett <steveb@workware.net.au>
2011 Alan DeKok <steveb@freeradius.org>
License: BSD-2-Clause
Comment: https://github.com/antirez/linenoise/blob/master/LICENSE
Files:
src/mongoose/*
Copyright:
2004-2010 Sergey Lyubka
License: Expat
Files:
src/qcommandline/*
Copyright:
2010-2011 Corentin Chary <corentin.chary@gmail.com>
License: LGPL-2+
Files:
test/www/regression/pjs-10690/jquery.js
Copyright:
2012 jQuery Foundation and other contributors
License: Expat
Files: debian/*
Copyright:
2011-2015 TANIGUCHI Takaki <takaki@debian.org>
2016 Dmitry Smirnov <onlyjob@debian.org>
License: BSD-3-Clause
License: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
On Debian systems, the complete text of the Apache License,
Version 2.0 can be found in "/usr/share/common-licenses/Apache-2.0".
License: BSD-2-Clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
.
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: BSD-3-Clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: Expat
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.
License: LGPL-2+
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
On Debian systems, the complete text of the GNU Library General Public
License Version 2 can be found in "/usr/share/common-licenses/LGPL-2".

1
debian/docs vendored Normal file
View File

@ -0,0 +1 @@
README.md

1
debian/examples vendored Normal file
View File

@ -0,0 +1 @@
examples/*

7
debian/gbp.conf vendored Normal file
View File

@ -0,0 +1,7 @@
[dch]
id-length = 0
[import-orig]
pristine-tar = True
merge = True

3
debian/install vendored Normal file
View File

@ -0,0 +1,3 @@
debian/bin/phantomjs usr/bin
bin/phantomjs usr/lib/phantomjs
# src/qt/lib/lib*so* usr/lib/phantomjs

1
debian/manpages vendored Normal file
View File

@ -0,0 +1 @@
debian/*.1

16
debian/patches/build-hardening.patch vendored Normal file
View File

@ -0,0 +1,16 @@
Last-Update: 2016-02-15
Forwarded: not-needed
Author: Dmitry Smirnov <onlyjob@debian.org>
Description: QMAKE hardening.
--- a/src/phantomjs.pro
+++ b/src/phantomjs.pro
@@ -1,4 +1,8 @@
+## https://wiki.debian.org/Hardening#Notes_for_packages_using_QMake
+QMAKE_CFLAGS *= $$(CFLAGS) $$(CPPFLAGS)
+QMAKE_CXXFLAGS *= $$(CXXFLAGS) $$(CPPFLAGS)
+QMAKE_LFLAGS *= $$(LDFLAGS)
if(!equals(QT_MAJOR_VERSION, 5)|!equals(QT_MINOR_VERSION, 5)) {
error("This program can only be compiled with Qt 5.5.x.")
}

View File

@ -0,0 +1,63 @@
Last-Update: 2016-02-15
Forwarded: not-needed
Author: Dmitry Smirnov <onlyjob@debian.org>
Description: fix compilation without non-DFSG Selenium "ghostdriver/third_party/webdriver-atoms/*" files
--- a/src/ghostdriver/ghostdriver.qrc
+++ b/src/ghostdriver/ghostdriver.qrc
@@ -17,55 +17,8 @@
<file>third_party/console++.js</file>
<file>third_party/har.js</file>
<file>third_party/parseuri.js</file>
<file>third_party/uuid.js</file>
- <file>third_party/webdriver-atoms/active_element.js</file>
- <file>third_party/webdriver-atoms/clear.js</file>
- <file>third_party/webdriver-atoms/clear_local_storage.js</file>
- <file>third_party/webdriver-atoms/clear_session_storage.js</file>
- <file>third_party/webdriver-atoms/click.js</file>
- <file>third_party/webdriver-atoms/double_click.js</file>
- <file>third_party/webdriver-atoms/drag.js</file>
- <file>third_party/webdriver-atoms/execute_async_script.js</file>
- <file>third_party/webdriver-atoms/execute_script.js</file>
- <file>third_party/webdriver-atoms/execute_sql.js</file>
- <file>third_party/webdriver-atoms/find_element.js</file>
- <file>third_party/webdriver-atoms/find_elements.js</file>
- <file>third_party/webdriver-atoms/focus_on_element.js</file>
- <file>third_party/webdriver-atoms/get_appcache_status.js</file>
- <file>third_party/webdriver-atoms/get_attribute.js</file>
- <file>third_party/webdriver-atoms/get_attribute_value.js</file>
- <file>third_party/webdriver-atoms/get_current_position.js</file>
- <file>third_party/webdriver-atoms/get_local_storage_item.js</file>
- <file>third_party/webdriver-atoms/get_local_storage_keys.js</file>
- <file>third_party/webdriver-atoms/get_local_storage_size.js</file>
- <file>third_party/webdriver-atoms/get_location.js</file>
- <file>third_party/webdriver-atoms/get_location_in_view.js</file>
- <file>third_party/webdriver-atoms/get_session_storage_item.js</file>
- <file>third_party/webdriver-atoms/get_session_storage_keys.js</file>
- <file>third_party/webdriver-atoms/get_session_storage_size.js</file>
- <file>third_party/webdriver-atoms/get_size.js</file>
- <file>third_party/webdriver-atoms/get_text.js</file>
- <file>third_party/webdriver-atoms/get_value_of_css_property.js</file>
- <file>third_party/webdriver-atoms/is_displayed.js</file>
- <file>third_party/webdriver-atoms/is_enabled.js</file>
- <file>third_party/webdriver-atoms/is_online.js</file>
- <file>third_party/webdriver-atoms/is_selected.js</file>
- <file>third_party/webdriver-atoms/lastupdate</file>
- <file>third_party/webdriver-atoms/move_mouse.js</file>
- <file>third_party/webdriver-atoms/pinch.js</file>
- <file>third_party/webdriver-atoms/remove_local_storage_item.js</file>
- <file>third_party/webdriver-atoms/remove_session_storage_item.js</file>
- <file>third_party/webdriver-atoms/right_click.js</file>
- <file>third_party/webdriver-atoms/rotate.js</file>
- <file>third_party/webdriver-atoms/scroll_into_view.js</file>
- <file>third_party/webdriver-atoms/scroll_mouse.js</file>
- <file>third_party/webdriver-atoms/set_local_storage_item.js</file>
- <file>third_party/webdriver-atoms/set_session_storage_item.js</file>
- <file>third_party/webdriver-atoms/submit.js</file>
- <file>third_party/webdriver-atoms/swipe.js</file>
- <file>third_party/webdriver-atoms/tap.js</file>
- <file>third_party/webdriver-atoms/type.js</file>
<file>webdriver_atoms.js</file>
<file>webelementlocator.js</file>
</qresource>
</RCC>

View File

@ -0,0 +1,18 @@
Last-Update: 2016-02-15
Forwarded: no
Author: Dmitry Smirnov <onlyjob@debian.org>
Description: fix FTBFS.
--- a/src/phantomjs.pro
+++ b/src/phantomjs.pro
@@ -8,9 +8,9 @@
}
TEMPLATE = app
TARGET = phantomjs
-QT += network webkitwidgets
+QT += network webkitwidgets printsupport
CONFIG += console
DESTDIR = ../bin

View File

@ -0,0 +1,169 @@
Last-Update: 2016-02-15
Forwarded: no
Bug-Upstream: https://github.com/ariya/phantomjs/issues/13727
Author: Ximin Luo <infinity0@debian.org>
Reviewed-By: Dmitry Smirnov <onlyjob@debian.org>
Description: Port to Qt 5.5
- Remove second argument to evaluateJavascript(), which was not really used
for anything, anyways
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -37,8 +37,9 @@
#include <QDateTime>
#include <QDir>
#include <QtWebKitWidgets/QWebFrame>
+
static QString findScript(const QString& jsFilePath, const QString& libraryPath)
{
if (!jsFilePath.isEmpty()) {
QFile jsFile;
@@ -131,9 +132,9 @@
}
return false;
}
// Execute JS code in the context of the document
- targetFrame->evaluateJavaScript(scriptBody, QString(JAVASCRIPT_SOURCE_CODE_URL).arg(QFileInfo(scriptPath).fileName()));
+ targetFrame->evaluateJavaScript(scriptBody);
return true;
}
bool loadJSForDebug(const QString& jsFilePath, const QString& libraryPath, QWebFrame* targetFrame, const bool autorun)
@@ -146,12 +147,12 @@
QString scriptPath = findScript(jsFilePath, libraryPath);
QString scriptBody = jsFromScriptFile(scriptPath, jsFileLanguage, jsFileEnc);
scriptBody = QString("function __run() {\n%1\n}").arg(scriptBody);
- targetFrame->evaluateJavaScript(scriptBody, QString(JAVASCRIPT_SOURCE_CODE_URL).arg(QFileInfo(scriptPath).fileName()));
+ targetFrame->evaluateJavaScript(scriptBody);
if (autorun) {
- targetFrame->evaluateJavaScript("__run()", QString());
+ targetFrame->evaluateJavaScript("__run()");
}
return true;
}
--- a/src/repl.cpp
+++ b/src/repl.cpp
@@ -146,9 +146,9 @@
// Set the static callback to offer Completions to the User
linenoiseSetCompletionCallback(REPL::offerCompletion);
// Inject REPL utility functions
- m_webframe->evaluateJavaScript(Utils::readResourceFileUtf8(":/repl.js"), QString(JAVASCRIPT_SOURCE_PLATFORM_URL).arg("repl.js"));
+ m_webframe->evaluateJavaScript(Utils::readResourceFileUtf8(":/repl.js"));
// Add self to JavaScript world
m_webframe->addToJavaScriptWindowObject("_repl", this);
@@ -183,10 +183,9 @@
// This will return an array of String with the possible completions
QStringList completions = REPL::getInstance()->m_webframe->evaluateJavaScript(
QString(JS_RETURN_POSSIBLE_COMPLETIONS).arg(
toInspect,
- toComplete),
- QString()
+ toComplete)
).toStringList();
foreach(QString c, completions) {
if (lastIndexOfDot > -1) {
@@ -209,9 +208,9 @@
if (userInput[0] != '\0') {
// Send the user input to the main Phantom frame for evaluation
m_webframe->evaluateJavaScript(
QString(JS_EVAL_USER_INPUT).arg(
- QString(userInput).replace('"', "\\\"")), QString("phantomjs://repl-input"));
+ QString(userInput).replace('"', "\\\"")));
// Save command in the REPL history
linenoiseHistoryAdd(userInput);
linenoiseHistorySave(m_historyFilepath.data()); //< requires "char *"
--- a/src/phantom.cpp
+++ b/src/phantom.cpp
@@ -380,9 +380,9 @@
"require.cache['" + filename + "']._getRequire()," +
"require.cache['" + filename + "'].exports," +
"require.cache['" + filename + "']" +
"));";
- m_page->mainFrame()->evaluateJavaScript(scriptSource, QString(JAVASCRIPT_SOURCE_PLATFORM_URL).arg(QFileInfo(filename).fileName()));
+ m_page->mainFrame()->evaluateJavaScript(scriptSource);
}
bool Phantom::injectJs(const QString& jsFilePath)
{
@@ -477,10 +477,9 @@
m_page->mainFrame()->addToJavaScriptWindowObject("phantom", this);
// Bootstrap the PhantomJS scope
m_page->mainFrame()->evaluateJavaScript(
- Utils::readResourceFileUtf8(":/bootstrap.js"),
- QString(JAVASCRIPT_SOURCE_PLATFORM_URL).arg("bootstrap.js")
+ Utils::readResourceFileUtf8(":/bootstrap.js")
);
}
bool Phantom::setCookies(const QVariantList& cookies)
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -176,9 +176,9 @@
QWebPage webPage;
// Add this object to the global scope
webPage.mainFrame()->addToJavaScriptWindowObject("config", this);
// Apply the JSON config settings to this very object
- webPage.mainFrame()->evaluateJavaScript(configurator.arg(jsonConfig), QString());
+ webPage.mainFrame()->evaluateJavaScript(configurator.arg(jsonConfig));
}
QString Config::helpText() const
{
--- a/src/webpage.cpp
+++ b/src/webpage.cpp
@@ -752,10 +752,10 @@
qDebug() << "WebPage - evaluateJavaScript" << function;
evalResult = m_currentFrame->evaluateJavaScript(
- function, //< function evaluated
- QString("phantomjs://webpage.evaluate()")); //< reference source file
+ function //< function evaluated
+ );
qDebug() << "WebPage - evaluateJavaScript result" << evalResult;
return evalResult;
@@ -925,9 +925,9 @@
networkOp = QNetworkAccessManager::DeleteOperation;
}
if (networkOp == QNetworkAccessManager::UnknownOperation) {
- m_mainFrame->evaluateJavaScript("console.error('Unknown network operation: " + operation + "');", QString());
+ m_mainFrame->evaluateJavaScript("console.error('Unknown network operation: " + operation + "');");
return;
}
if (address == "about:blank") {
@@ -1314,9 +1314,9 @@
return ret.toString();
}
}
}
- frame->evaluateJavaScript("console.error('Bad header callback given, use phantom.callback);", QString());
+ frame->evaluateJavaScript("console.error('Bad header callback given, use phantom.callback);");
return QString();
}
QString WebPage::header(int page, int numPages)
@@ -1353,9 +1353,9 @@
}
void WebPage::_appendScriptElement(const QString& scriptUrl)
{
- m_currentFrame->evaluateJavaScript(QString(JS_APPEND_SCRIPT_ELEMENT).arg(scriptUrl), scriptUrl);
+ m_currentFrame->evaluateJavaScript(QString(JS_APPEND_SCRIPT_ELEMENT).arg(scriptUrl));
}
QObject* WebPage::_getGenericCallback()
{

View File

@ -0,0 +1,54 @@
Last-Update: 2016-02-15
Forwarded: not-needed
Bug-Upstream: https://github.com/ariya/phantomjs/issues/13727
Author: Ximin Luo <infinity0@debian.org>
Reviewed-By: Dmitry Smirnov <onlyjob@debian.org>
Description: Port to Qt 5.5
- Remove "webSecurity" option since that's not exposed in Qt 5.5
--- a/src/consts.h
+++ b/src/consts.h
@@ -63,9 +63,8 @@
#define PAGE_SETTINGS_USERNAME "userName"
#define PAGE_SETTINGS_PASSWORD "password"
#define PAGE_SETTINGS_MAX_AUTH_ATTEMPTS "maxAuthAttempts"
#define PAGE_SETTINGS_RESOURCE_TIMEOUT "resourceTimeout"
-#define PAGE_SETTINGS_WEB_SECURITY_ENABLED "webSecurityEnabled"
#define PAGE_SETTINGS_JS_CAN_OPEN_WINDOWS "javascriptCanOpenWindows"
#define PAGE_SETTINGS_JS_CAN_CLOSE_WINDOWS "javascriptCanCloseWindows"
#define DEFAULT_WEBDRIVER_CONFIG "127.0.0.1:8910"
--- a/src/phantom.cpp
+++ b/src/phantom.cpp
@@ -135,9 +135,8 @@
m_defaultPageSettings[PAGE_SETTINGS_JS_ENABLED] = QVariant::fromValue(true);
m_defaultPageSettings[PAGE_SETTINGS_XSS_AUDITING] = QVariant::fromValue(false);
m_defaultPageSettings[PAGE_SETTINGS_USER_AGENT] = QVariant::fromValue(m_page->userAgent());
m_defaultPageSettings[PAGE_SETTINGS_LOCAL_ACCESS_REMOTE] = QVariant::fromValue(m_config.localToRemoteUrlAccessEnabled());
- m_defaultPageSettings[PAGE_SETTINGS_WEB_SECURITY_ENABLED] = QVariant::fromValue(m_config.webSecurityEnabled());
m_defaultPageSettings[PAGE_SETTINGS_JS_CAN_OPEN_WINDOWS] = QVariant::fromValue(m_config.javascriptCanOpenWindows());
m_defaultPageSettings[PAGE_SETTINGS_JS_CAN_CLOSE_WINDOWS] = QVariant::fromValue(m_config.javascriptCanCloseWindows());
m_page->applySettings(m_defaultPageSettings);
--- a/src/webpage.cpp
+++ b/src/webpage.cpp
@@ -367,9 +367,8 @@
// attribute "WebSecurityEnabled" must be applied during the initializing
// security context for Document instance. Setting up it later will not cause any effect
// see <qt\src\3rdparty\webkit\Source\WebCore\dom\Document.cpp:4468>
QWebSettings* settings = m_customWebPage->settings();
- settings->setAttribute(QWebSettings::WebSecurityEnabled, phantomCfg->webSecurityEnabled());
m_mainFrame = m_customWebPage->mainFrame();
m_currentFrame = m_mainFrame;
m_mainFrame->setHtml(BLANK_HTML, baseUrl);
@@ -621,9 +620,8 @@
opt->setAttribute(QWebSettings::AutoLoadImages, def[PAGE_SETTINGS_LOAD_IMAGES].toBool());
opt->setAttribute(QWebSettings::JavascriptEnabled, def[PAGE_SETTINGS_JS_ENABLED].toBool());
opt->setAttribute(QWebSettings::XSSAuditingEnabled, def[PAGE_SETTINGS_XSS_AUDITING].toBool());
opt->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, def[PAGE_SETTINGS_LOCAL_ACCESS_REMOTE].toBool());
- opt->setAttribute(QWebSettings::WebSecurityEnabled, def[PAGE_SETTINGS_WEB_SECURITY_ENABLED].toBool());
opt->setAttribute(QWebSettings::JavascriptCanOpenWindows, def[PAGE_SETTINGS_JS_CAN_OPEN_WINDOWS].toBool());
opt->setAttribute(QWebSettings::JavascriptCanCloseWindows, def[PAGE_SETTINGS_JS_CAN_CLOSE_WINDOWS].toBool());
if (def.contains(PAGE_SETTINGS_USER_AGENT)) {

34
debian/patches/build-qt55-print.patch vendored Normal file
View File

@ -0,0 +1,34 @@
Last-Update: 2016-02-15
Forwarded: no
Bug-Upstream: https://github.com/ariya/phantomjs/issues/13727
Author: Ximin Luo <infinity0@debian.org>
Reviewed-By: Dmitry Smirnov <onlyjob@debian.org>
Description: Port to Qt 5.5
- In webpage, don't inherit QWebFrame::PrintCallback since it's not public
--- a/src/webpage.cpp
+++ b/src/webpage.cpp
@@ -1254,9 +1254,9 @@
}
printer.setPageMargins(marginLeft, marginTop, marginRight, marginBottom, QPrinter::Point);
- m_mainFrame->print(&printer, this);
+ m_mainFrame->print(&printer);
return true;
}
void WebPage::setZoomFactor(qreal zoom)
--- a/src/webpage.h
+++ b/src/webpage.h
@@ -44,9 +44,9 @@
class NetworkAccessManager;
class QWebInspector;
class Phantom;
-class WebPage : public QObject, public QWebFrame::PrintCallback
+class WebPage : public QObject
{
Q_OBJECT
Q_PROPERTY(QString title READ title)
Q_PROPERTY(QString frameTitle READ frameTitle)

18
debian/patches/build-qtpath.patch vendored Normal file
View File

@ -0,0 +1,18 @@
Last-Update: 2016-02-15
Forwarded: not-needed
Author: Dmitry Smirnov <onlyjob@debian.org>
Description: build system correction(s).
--- a/build.py
+++ b/build.py
@@ -80,9 +80,9 @@
def qmakePath():
exe = "qmake"
if platform.system() == "Windows":
exe += ".exe"
- return os.path.abspath("src/qt/qtbase/bin/" + exe)
+ return os.path.abspath("/usr/bin/" + exe)
# returns paths for 3rd party libraries (Windows only)
def findThirdPartyDeps():
include_dirs = []

View File

@ -0,0 +1,21 @@
Last-Update: 2015-11-11
Author: Ximin Luo <infinity0@debian.org>
Description: fix .gitignore in a better way
- rm debian-specific things from upstream .gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -10,13 +10,8 @@
*.o
*.swp
*.pyc
*.a
-/debian/*.debhelper
-/debian/files
-/debian/*.log
-/debian/*.substvars
-/debian/*/
/deploy/qt-*.tar.gz
/deploy/Qt-*
/symbols
/src/qt/qtc-debugging-helper

9
debian/patches/series vendored Normal file
View File

@ -0,0 +1,9 @@
build-hardening.patch
build-no-ghostdriver.patch
build-qt-components.patch
build-qt55-evaluateJavaScript.patch
build-qt55-no-websecurity.patch
build-qt55-print.patch
build-qtpath.patch
ignore-upstream-debian-vcs-ignores.patch
unlock-qt.patch

20
debian/patches/unlock-qt.patch vendored Normal file
View File

@ -0,0 +1,20 @@
Last-Update: 2016-06-16
Forwarded: not-needed
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=827421
Author: Dmitry Smirnov <onlyjob@debian.org>
Description: allow build with QT-5.5+
--- a/src/phantomjs.pro
+++ b/src/phantomjs.pro
@@ -2,11 +2,8 @@
QMAKE_CFLAGS *= $$(CFLAGS) $$(CPPFLAGS)
QMAKE_CXXFLAGS *= $$(CXXFLAGS) $$(CPPFLAGS)
QMAKE_LFLAGS *= $$(LDFLAGS)
-if(!equals(QT_MAJOR_VERSION, 5)|!equals(QT_MINOR_VERSION, 5)) {
- error("This program can only be compiled with Qt 5.5.x.")
-}
TEMPLATE = app
TARGET = phantomjs
QT += network webkitwidgets printsupport

125
debian/phantomjs.1 vendored Normal file
View File

@ -0,0 +1,125 @@
.\" This file was pre-generated by help2man 1.47.3.
.TH PHANTOMJS "1" "February 2016" "phantomjs 2.1.1" "User Commands"
.SH NAME
phantomjs \- headless WebKit scriptable with a JavaScript API
.SH DESCRIPTION
PhantomJS is a headless WebKit scriptable with a JavaScript API. It has
fast and native support for various web standards: DOM handling, CSS
selector, JSON, Canvas, and SVG.
.SH SYNOPSIS
phantomjs [switchs] [options] [script] [argument [argument [...]]]
.SH OPTIONS
.TP
\fB\-\-cookies\-file=\fR<val>
Sets the file name to store the persistent cookies
.TP
\fB\-\-config=\fR<val>
Specifies JSON\-formatted configuration file
.TP
\fB\-\-debug=\fR<val>
Prints additional warning and debug message: 'true' or 'false' (default)
.TP
\fB\-\-disk\-cache=\fR<val>
Enables disk cache: 'true' or 'false' (default)
.TP
\fB\-\-disk\-cache\-path=\fR<val>
Specifies the location for the disk cache
.TP
\fB\-\-ignore\-ssl\-errors=\fR<val>
Ignores SSL errors (expired/self\-signed certificate errors): 'true' or 'false' (default)
.TP
\fB\-\-load\-images=\fR<val>
Loads all inlined images: 'true' (default) or 'false'
.TP
\fB\-\-local\-url\-access=\fR<val>
Allows use of 'file:///' URLs: 'true' (default) or 'false'
.TP
\fB\-\-local\-storage\-path=\fR<val>
Specifies the location for local storage
.TP
\fB\-\-local\-storage\-quota=\fR<val>
Sets the maximum size of the local storage (in KB)
.TP
\fB\-\-offline\-storage\-path=\fR<val>
Specifies the location for offline storage
.TP
\fB\-\-offline\-storage\-quota=\fR<val>
Sets the maximum size of the offline storage (in KB)
.TP
\fB\-\-local\-to\-remote\-url\-access=\fR<val>
Allows local content to access remote URL: 'true' or 'false' (default)
.TP
\fB\-\-max\-disk\-cache\-size=\fR<val>
Limits the size of the disk cache (in KB)
.TP
\fB\-\-output\-encoding=\fR<val>
Sets the encoding for the terminal output, default is 'utf8'
.TP
\fB\-\-remote\-debugger\-port=\fR<val>
Starts the script in a debug harness and listens on the specified port
.TP
\fB\-\-remote\-debugger\-autorun=\fR<val>
Runs the script in the debugger immediately: 'true' or 'false' (default)
.TP
\fB\-\-proxy=\fR<val>
Sets the proxy server, e.g. '\-\-proxy=http://proxy.company.com:8080'
.TP
\fB\-\-proxy\-auth=\fR<val>
Provides authentication information for the proxy, e.g. ''\-proxy\-auth=username:password'
.TP
\fB\-\-proxy\-type=\fR<val>
Specifies the proxy type, 'http' (default), 'none' (disable completely), or 'socks5'
.TP
\fB\-\-script\-encoding=\fR<val>
Sets the encoding used for the starting script, default is 'utf8'
.TP
\fB\-\-script\-language=\fR<val>
Sets the script language instead of detecting it: 'javascript'
.TP
\fB\-\-web\-security=\fR<val>
Enables web security, 'true' (default) or 'false'
.TP
\fB\-\-ssl\-protocol=\fR<val>
Selects a specific SSL protocol version to offer. Values (case insensitive): TLSv1.2, TLSv1.1, TLSv1.0, TLSv1 (same as v1.0), SSLv3, or ANY. Default is to offer all that Qt thinks are secure (SSLv3 and up). Not all values may be supported, depending on the system OpenSSL library.
.TP
\fB\-\-ssl\-ciphers=\fR<val>
Sets supported TLS/SSL ciphers. Argument is a colon\-separated list of OpenSSL cipher names (macros like ALL, kRSA, etc. may not be used). Default matches modern browsers.
.TP
\fB\-\-ssl\-certificates\-path=\fR<val>
Sets the location for custom CA certificates (if none set, uses environment variable SSL_CERT_DIR. If none set too, uses system default)
.TP
\fB\-\-ssl\-client\-certificate\-file=\fR<val>
Sets the location of a client certificate
.TP
\fB\-\-ssl\-client\-key\-file=\fR<val>
Sets the location of a clients' private key
.TP
\fB\-\-ssl\-client\-key\-passphrase=\fR<val>
Sets the passphrase for the clients' private key
.TP
\fB\-\-webdriver=\fR<val>
Starts in 'Remote WebDriver mode' (embedded GhostDriver): '[[<IP>:]<PORT>]' (default '127.0.0.1:8910')
.TP
\fB\-\-webdriver\-logfile=\fR<val>
File where to write the WebDriver's Log (default 'none') (NOTE: needs '\-\-webdriver')
.TP
\fB\-\-webdriver\-loglevel=\fR<val>
WebDriver Logging Level: (supported: 'ERROR', 'WARN', 'INFO', 'DEBUG') (default 'INFO') (NOTE: needs '\-\-webdriver')
.TP
\fB\-\-webdriver\-selenium\-grid\-hub=\fR<val>
URL to the Selenium Grid HUB: 'URL_TO_HUB' (default 'none') (NOTE: needs '\-\-webdriver')
.TP
\fB\-w\fR,\-\-wd
Equivalent to '\-\-webdriver' option above
.TP
\fB\-h\fR,\-\-help
Shows this message and quits
.TP
\fB\-v\fR,\-\-version
Prints out PhantomJS version
.PP
Any of the options that accept boolean values ('true'/'false') can also accept 'yes'/'no'.
.PP
Without any argument, PhantomJS will launch in interactive mode (REPL).
.PP
Documentation can be found at the web site, http://phantomjs.org.

28
debian/rules vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
export DEB_BUILD_MAINT_OPTIONS=hardening=+all
#QT_CFG=-no-rpath -system-zlib -system-libjpeg -system-libpng \
# -system-sqlite -plugin-sql-sqlite
export QT_SELECT = qt5
%:
dh $@
override_dh_auto_build:
python build.py --skip-git --skip-qtbase --skip-qtwebkit --confirm --release
override_dh_shlibdeps:
dh_shlibdeps -l/usr/lib/phantomjs
override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
## xvfb-run is affected by #814607.
## By default Xvfb starts at 8bpp (GLX doesn't work at 8bpp).
## https://bugzilla.redhat.com/show_bug.cgi?id=904851
-dbus-launch xvfb-run --server-args="-screen 0 640x480x16" test/run-tests.py
endif

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

3
debian/source/lintian-overrides vendored Normal file
View File

@ -0,0 +1,3 @@
## Long line(s):
source-contains-prebuilt-javascript-object test/module/webpage/mousedoubleclick-event.js line length *
source-is-missing test/module/webpage/mousedoubleclick-event.js line length *

7
debian/watch vendored Normal file
View File

@ -0,0 +1,7 @@
version=3
opts=\
repacksuffix=+dfsg,\
dversionmangle=s/\+dfsg\d*//,\
https://github.com/ariya/phantomjs/releases \
.*/archive/v?\.?(\d[\d\.]+)\.tar\.gz

2
deploy/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vagrant
/brandelf

44
deploy/README.md Normal file
View File

@ -0,0 +1,44 @@
Packaging PhantomJS
===================
This directory contains various scripts to assist with making PhantomJS
packages.
Packaging for Linux
-------------------
Linux building/packaging is best done in a container to ensure
isolation. We use [Docker](https://www.docker.com/) to automate the
process. Please see the [Docker documentation](https://docs.docker.com/)
for instructions on installing Docker. For OS X or Windows host,
please use [Docker Toolbox](https://www.docker.com/docker-toolbox).
Once you have Docker installed, run these commands from the top level
of the PhantomJS source repository:
```bash
$ git clean -xfd .
$ docker run -v $PWD:/src debian:wheezy /src/deploy/docker-build.sh
```
For the 32-bit version:
```bash
$ git clean -xfd .
$ docker run -v $PWD:/src tubia/debian:wheezy /src/deploy/docker-build.sh
```
The built binary will be extracted out of the container and copied to
the current directory.
Packaging for OS X
------------------
Run `deploy/build-and-package.sh`. That's it.
However, if you have previously built the sources in release mode, you
should clean your tree to make sure all the debugging symbols gets
compiled:
$ make clean && cd src/qt && make clean && cd ../..

212
deploy/brandelf.c Normal file
View File

@ -0,0 +1,212 @@
/*-
* Copyright (c) 2000, 2001 David O'Brien
* Copyright (c) 1996 Søren Schmidt
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer
* in this position and unchanged.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
//NOTE: commented out to make it compile on linux
// __FBSDID("$FreeBSD: src/usr.bin/brandelf/brandelf.c,v 1.25.22.2 2012/03/16 03:22:37 eadler Exp $");
#include <sys/types.h>
//NOTE: changed path to make it compile on linux
#include <elf.h>
#include <sys/errno.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int elftype(const char *);
static const char *iselftype(int);
static void printelftypes(void);
static void usage(void);
struct ELFtypes {
const char *str;
int value;
};
/* XXX - any more types? */
static struct ELFtypes elftypes[] = {
{ "FreeBSD", ELFOSABI_FREEBSD },
{ "Linux", ELFOSABI_LINUX },
{ "Solaris", ELFOSABI_SOLARIS },
{ "SVR4", ELFOSABI_SYSV }
};
int
main(int argc, char **argv)
{
const char *strtype = "FreeBSD";
int type = ELFOSABI_FREEBSD;
int retval = 0;
int ch, change = 0, force = 0, listed = 0;
while ((ch = getopt(argc, argv, "f:lt:v")) != -1)
switch (ch) {
case 'f':
if (change)
errx(1, "f option incompatible with t option");
force = 1;
type = atoi(optarg);
if (errno == ERANGE || type < 0 || type > 255) {
warnx("invalid argument to option f: %s",
optarg);
usage();
}
break;
case 'l':
printelftypes();
listed = 1;
break;
case 'v':
/* does nothing */
break;
case 't':
if (force)
errx(1, "t option incompatible with f option");
change = 1;
strtype = optarg;
break;
default:
usage();
}
argc -= optind;
argv += optind;
if (!argc) {
if (listed)
exit(0);
else {
warnx("no file(s) specified");
usage();
}
}
if (!force && (type = elftype(strtype)) == -1) {
warnx("invalid ELF type '%s'", strtype);
printelftypes();
usage();
}
while (argc) {
int fd;
char buffer[EI_NIDENT];
if ((fd = open(argv[0], change || force ? O_RDWR : O_RDONLY, 0)) < 0) {
warn("error opening file %s", argv[0]);
retval = 1;
goto fail;
}
if (read(fd, buffer, EI_NIDENT) < EI_NIDENT) {
warnx("file '%s' too short", argv[0]);
retval = 1;
goto fail;
}
if (buffer[0] != ELFMAG0 || buffer[1] != ELFMAG1 ||
buffer[2] != ELFMAG2 || buffer[3] != ELFMAG3) {
warnx("file '%s' is not ELF format", argv[0]);
retval = 1;
goto fail;
}
if (!change && !force) {
fprintf(stdout,
"File '%s' is of brand '%s' (%u).\n",
argv[0], iselftype(buffer[EI_OSABI]),
buffer[EI_OSABI]);
if (!iselftype(type)) {
warnx("ELF ABI Brand '%u' is unknown",
type);
printelftypes();
}
}
else {
buffer[EI_OSABI] = type;
lseek(fd, 0, SEEK_SET);
if (write(fd, buffer, EI_NIDENT) != EI_NIDENT) {
warn("error writing %s %d", argv[0], fd);
retval = 1;
goto fail;
}
}
fail:
close(fd);
argc--;
argv++;
}
return retval;
}
static void
usage(void)
{
(void)fprintf(stderr,
"usage: brandelf [-lv] [-f ELF_ABI_number] [-t string] file ...\n");
exit(1);
}
static const char *
iselftype(int etype)
{
size_t elfwalk;
for (elfwalk = 0;
elfwalk < sizeof(elftypes)/sizeof(elftypes[0]);
elfwalk++)
if (etype == elftypes[elfwalk].value)
return elftypes[elfwalk].str;
return 0;
}
static int
elftype(const char *elfstrtype)
{
size_t elfwalk;
for (elfwalk = 0;
elfwalk < sizeof(elftypes)/sizeof(elftypes[0]);
elfwalk++)
if (strcasecmp(elfstrtype, elftypes[elfwalk].str) == 0)
return elftypes[elfwalk].value;
return -1;
}
static void
printelftypes(void)
{
size_t elfwalk;
fprintf(stderr, "known ELF types are: ");
for (elfwalk = 0;
elfwalk < sizeof(elftypes)/sizeof(elftypes[0]);
elfwalk++)
fprintf(stderr, "%s(%u) ", elftypes[elfwalk].str,
elftypes[elfwalk].value);
fprintf(stderr, "\n");
}

6
deploy/build-and-package.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
cd `dirname $0`/..
./build.py --confirm --release --git-clean-qtbase --git-clean-qtwebkit "$@" || exit 1

62
deploy/docker-build.sh Executable file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -e
SOURCE_PATH=/src
BUILD_PATH=$HOME/build
# In case the old package URL is still being used
sed -i 's/http\.debian\.net/httpredir\.debian\.org/g' /etc/apt/sources.list
echo "Installing packages for development tools..." && sleep 1
apt-get -y update
apt-get install -y build-essential git flex bison gperf python ruby git libfontconfig1-dev
echo
echo "Preparing to download Debian source package..."
echo "deb-src http://httpredir.debian.org/debian wheezy main" >> /etc/apt/sources.list
apt-get -y update
echo
OPENSSL_TARGET='linux-x86_64'
if [ `getconf LONG_BIT` -eq 32 ]; then
OPENSSL_TARGET='linux-generic32'
fi
echo "Recompiling OpenSSL for ${OPENSSL_TARGET}..." && sleep 1
apt-get source openssl
cd openssl-1.0.1e
OPENSSL_FLAGS='no-idea no-mdc2 no-rc5 no-zlib enable-tlsext no-ssl2 no-ssl3 no-ssl3-method enable-rfc3779 enable-cms'
./Configure --prefix=/usr --openssldir=/etc/ssl --libdir=lib ${OPENSSL_FLAGS} ${OPENSSL_TARGET}
make depend && make && make install
cd ..
echo
echo "Building the static version of ICU library..." && sleep 1
apt-get source icu
cd icu-4.8.1.1/source
./configure --prefix=/usr --enable-static --disable-shared
make && make install
cd ..
echo
echo "Recreating the build directory $BUILD_PATH..."
rm -rf $BUILD_PATH && mkdir -p $BUILD_PATH
echo
echo "Transferring the source: $SOURCE_PATH -> $BUILD_PATH. Please wait..."
cd $BUILD_PATH && cp -rp $SOURCE_PATH . && cd src
echo
echo "Compiling PhantomJS..." && sleep 1
python build.py --confirm --release --qt-config="-no-pkg-config" --git-clean-qtbase --git-clean-qtwebkit
echo
echo "Stripping the executable..." && sleep 1
ls -l bin/phantomjs
strip bin/phantomjs
echo "Copying the executable..." && sleep 1
ls -l bin/phantomjs
cp bin/phantomjs $SOURCE_PATH
echo
echo "Finished."

120
deploy/package.sh Executable file
View File

@ -0,0 +1,120 @@
#!/usr/bin/env bash
#
# usage: just run this script (after having run build.sh)
# and deploy the created tarball to your target machine.
#
# It creates a phantomjs-$version folder and copies the binary,
# example, license etc. together with all shared library dependencies
# to that folder. Furthermore brandelf is used to make the lib
# and binary compatible with older unix/linux machines that don't
# know the new Linux ELF ABI.
#
cd $(dirname $0)
if [[ ! -f ../bin/phantomjs ]]; then
echo "phantomjs was not built yet, please run build.sh first"
exit 1
fi
if [[ "$1" = "--bundle-libs" ]]; then
bundle_libs=1
else
bundle_libs=0
fi
version=$(../bin/phantomjs --version | sed 's/ /-/' | sed 's/[()]//g')
src=..
echo "packaging phantomjs $version"
if [[ $OSTYPE = darwin* ]]; then
dest="phantomjs-$version-macosx"
else
dest="phantomjs-$version-linux-$(uname -m)"
fi
rm -Rf $dest{.tar.bz2,} &> /dev/null
mkdir -p $dest/bin
echo
echo -n "copying files..."
cp $src/bin/phantomjs $dest/bin
cp -r $src/{ChangeLog,examples,LICENSE.BSD,third-party.txt,README.md} $dest/
echo "done"
echo
phantomjs=$dest/bin/phantomjs
if [[ "$bundle_libs" = "1" ]]; then
mkdir -p $dest/lib
if [[ ! -f brandelf ]]; then
echo
echo "brandelf executable not found in current dir"
echo -n "compiling it now..."
g++ brandelf.c -o brandelf || exit 1
echo "done"
fi
libs=$(ldd $phantomjs | egrep -o "/[^ ]+ ")
echo -n "copying shared libs..."
libld=
for l in $libs; do
ll=$(basename $l)
cp $l $dest/lib/$ll
if [[ "$bundle_libs" = "1" ]]; then
# ensure OS ABI compatibility
./brandelf -t SVR4 $dest/lib/$ll
if [[ "$l" == *"ld-linux"* ]]; then
libld=$ll
fi
fi
done
echo "done"
echo
echo -n "writing run script..."
mv $phantomjs $phantomjs.bin
phantomjs=$phantomjs.bin
run=$dest/bin/phantomjs
echo '#!/bin/sh' >> $run
echo 'path=$(dirname $(dirname $(readlink -f $0)))' >> $run
echo 'export LD_LIBRARY_PATH=$path/lib' >> $run
echo 'exec $path/lib/'$libld' $phantomjs $@' >> $run
chmod +x $run
echo "done"
echo
fi
echo -n "stripping binary and libs..."
if [[ $OSTYPE = darwin* ]]; then
strip -x $phantomjs
else
strip -s $phantomjs
[[ -d $dest/lib ]] && strip -s $dest/lib/*
fi
echo "done"
echo
echo -n "compressing binary..."
if type upx >/dev/null 2>&1; then
upx -qqq -9 $phantomjs
echo "done"
else
echo "upx not found"
fi
echo
echo -n "creating archive..."
if [[ $OSTYPE = darwin* ]]; then
zip -r $dest.zip $dest
else
tar -cjf $dest{.tar.bz2,}
fi
echo "done"
echo

10
examples/arguments.js Normal file
View File

@ -0,0 +1,10 @@
"use strict";
var system = require('system');
if (system.args.length === 1) {
console.log('Try to pass some args when invoking this script!');
} else {
system.args.forEach(function (arg, i) {
console.log(i + ': ' + arg);
});
}
phantom.exit();

View File

@ -0,0 +1,28 @@
"use strict";
var spawn = require("child_process").spawn
var execFile = require("child_process").execFile
var child = spawn("ls", ["-lF", "/rooot"])
child.stdout.on("data", function (data) {
console.log("spawnSTDOUT:", JSON.stringify(data))
})
child.stderr.on("data", function (data) {
console.log("spawnSTDERR:", JSON.stringify(data))
})
child.on("exit", function (code) {
console.log("spawnEXIT:", code)
})
//child.kill("SIGKILL")
execFile("ls", ["-lF", "/usr"], null, function (err, stdout, stderr) {
console.log("execFileSTDOUT:", JSON.stringify(stdout))
console.log("execFileSTDERR:", JSON.stringify(stderr))
})
setTimeout(function () {
phantom.exit(0)
}, 2000)

52
examples/colorwheel.js Normal file
View File

@ -0,0 +1,52 @@
"use strict";
var page = require('webpage').create();
page.viewportSize = { width: 400, height : 400 };
page.content = '<html><body><canvas id="surface"></canvas></body></html>';
page.evaluate(function() {
var el = document.getElementById('surface'),
context = el.getContext('2d'),
width = window.innerWidth,
height = window.innerHeight,
cx = width / 2,
cy = height / 2,
radius = width / 2.3,
imageData,
pixels,
hue, sat, value,
i = 0, x, y, rx, ry, d,
f, g, p, u, v, w, rgb;
el.width = width;
el.height = height;
imageData = context.createImageData(width, height);
pixels = imageData.data;
for (y = 0; y < height; y = y + 1) {
for (x = 0; x < width; x = x + 1, i = i + 4) {
rx = x - cx;
ry = y - cy;
d = rx * rx + ry * ry;
if (d < radius * radius) {
hue = 6 * (Math.atan2(ry, rx) + Math.PI) / (2 * Math.PI);
sat = Math.sqrt(d) / radius;
g = Math.floor(hue);
f = hue - g;
u = 255 * (1 - sat);
v = 255 * (1 - sat * f);
w = 255 * (1 - sat * (1 - f));
pixels[i] = [255, v, u, u, w, 255, 255][g];
pixels[i + 1] = [w, 255, 255, v, u, u, w][g];
pixels[i + 2] = [u, u, w, 255, 255, v, u][g];
pixels[i + 3] = 255;
}
}
}
context.putImageData(imageData, 0, 0);
document.body.style.backgroundColor = 'white';
document.body.style.margin = '0px';
});
page.render('colorwheel.png');
phantom.exit();

10
examples/countdown.js Normal file
View File

@ -0,0 +1,10 @@
"use strict";
var t = 10,
interval = setInterval(function(){
if ( t > 0 ) {
console.log(t--);
} else {
console.log("BLAST OFF!");
phantom.exit();
}
}, 1000);

60
examples/detectsniff.js Normal file
View File

@ -0,0 +1,60 @@
// Detect if a web page sniffs the user agent or not.
"use strict";
var page = require('webpage').create(),
system = require('system'),
sniffed,
address;
page.onInitialized = function () {
page.evaluate(function () {
(function () {
var userAgent = window.navigator.userAgent,
platform = window.navigator.platform;
window.navigator = {
appCodeName: 'Mozilla',
appName: 'Netscape',
cookieEnabled: false,
sniffed: false
};
window.navigator.__defineGetter__('userAgent', function () {
window.navigator.sniffed = true;
return userAgent;
});
window.navigator.__defineGetter__('platform', function () {
window.navigator.sniffed = true;
return platform;
});
})();
});
};
if (system.args.length === 1) {
console.log('Usage: detectsniff.js <some URL>');
phantom.exit(1);
} else {
address = system.args[1];
console.log('Checking ' + address + '...');
page.open(address, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
phantom.exit();
} else {
window.setTimeout(function () {
sniffed = page.evaluate(function () {
return navigator.sniffed;
});
if (sniffed) {
console.log('The page tried to sniff the user agent.');
} else {
console.log('The page did not try to sniff the user agent.');
}
phantom.exit();
}, 1500);
}
});
}

24
examples/echoToFile.js Normal file
View File

@ -0,0 +1,24 @@
// echoToFile.js - Write in a given file all the parameters passed on the CLI
"use strict";
var fs = require('fs'),
system = require('system');
if (system.args.length < 3) {
console.log("Usage: echoToFile.js DESTINATION_FILE <arguments to echo...>");
phantom.exit(1);
} else {
var content = '',
f = null,
i;
for ( i= 2; i < system.args.length; ++i ) {
content += system.args[i] + (i === system.args.length-1 ? '' : ' ');
}
try {
fs.write(system.args[1], content, 'w');
} catch(e) {
console.log(e);
}
phantom.exit();
}

30
examples/features.js Normal file
View File

@ -0,0 +1,30 @@
"use strict";
var feature, supported = [], unsupported = [];
phantom.injectJs('modernizr.js');
console.log('Detected features (using Modernizr ' + Modernizr._version + '):');
for (feature in Modernizr) {
if (Modernizr.hasOwnProperty(feature)) {
if (feature[0] !== '_' && typeof Modernizr[feature] !== 'function' &&
feature !== 'input' && feature !== 'inputtypes') {
if (Modernizr[feature]) {
supported.push(feature);
} else {
unsupported.push(feature);
}
}
}
}
console.log('');
console.log('Supported:');
supported.forEach(function (e) {
console.log(' ' + e);
});
console.log('');
console.log('Not supported:');
unsupported.forEach(function (e) {
console.log(' ' + e);
});
phantom.exit();

10
examples/fibo.js Normal file
View File

@ -0,0 +1,10 @@
"use strict";
var fibs = [0, 1];
var ticker = window.setInterval(function () {
console.log(fibs[fibs.length - 1]);
fibs.push(fibs[fibs.length - 1] + fibs[fibs.length - 2]);
if (fibs.length > 10) {
window.clearInterval(ticker);
phantom.exit();
}
}, 300);

3
examples/hello.js Normal file
View File

@ -0,0 +1,3 @@
"use strict";
console.log('Hello, world!');
phantom.exit();

26
examples/injectme.js Normal file
View File

@ -0,0 +1,26 @@
// Use 'page.injectJs()' to load the script itself in the Page context
"use strict";
if ( typeof(phantom) !== "undefined" ) {
var page = require('webpage').create();
// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.onAlert = function(msg) {
console.log(msg);
};
console.log("* Script running in the Phantom context.");
console.log("* Script will 'inject' itself in a page...");
page.open("about:blank", function(status) {
if ( status === "success" ) {
console.log(page.injectJs("injectme.js") ? "... done injecting itself!" : "... fail! Check the $PWD?!");
}
phantom.exit();
});
} else {
alert("* Script running in the Page context.");
}

24
examples/loadspeed.js Normal file
View File

@ -0,0 +1,24 @@
"use strict";
var page = require('webpage').create(),
system = require('system'),
t, address;
if (system.args.length === 1) {
console.log('Usage: loadspeed.js <some URL>');
phantom.exit(1);
} else {
t = Date.now();
address = system.args[1];
page.open(address, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
} else {
t = Date.now() - t;
console.log('Page title is ' + page.evaluate(function () {
return document.title;
}));
console.log('Loading time ' + t + ' msec');
}
phantom.exit();
});
}

View File

@ -0,0 +1,26 @@
"use strict";
var page = require('webpage').create(),
system = require('system');
if (system.args.length < 2) {
console.log('Usage: loadurlwithoutcss.js URL');
phantom.exit();
}
var address = system.args[1];
page.onResourceRequested = function(requestData, request) {
if ((/http:\/\/.+?\.css/gi).test(requestData['url']) || requestData.headers['Content-Type'] == 'text/css') {
console.log('The url of the request is matching. Aborting: ' + requestData['url']);
request.abort();
}
};
page.open(address, function(status) {
if (status === 'success') {
phantom.exit();
} else {
console.log('Unable to load the address!');
phantom.exit();
}
});

1406
examples/modernizr.js Normal file

File diff suppressed because it is too large Load Diff

5
examples/module.js Normal file
View File

@ -0,0 +1,5 @@
"use strict";
var universe = require('./universe');
universe.start();
console.log('The answer is ' + universe.answer);
phantom.exit();

26
examples/netlog.js Normal file
View File

@ -0,0 +1,26 @@
"use strict";
var page = require('webpage').create(),
system = require('system'),
address;
if (system.args.length === 1) {
console.log('Usage: netlog.js <some URL>');
phantom.exit(1);
} else {
address = system.args[1];
page.onResourceRequested = function (req) {
console.log('requested: ' + JSON.stringify(req, undefined, 4));
};
page.onResourceReceived = function (res) {
console.log('received: ' + JSON.stringify(res, undefined, 4));
};
page.open(address, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
}
phantom.exit();
});
}

144
examples/netsniff.js Normal file
View File

@ -0,0 +1,144 @@
"use strict";
if (!Date.prototype.toISOString) {
Date.prototype.toISOString = function () {
function pad(n) { return n < 10 ? '0' + n : n; }
function ms(n) { return n < 10 ? '00'+ n : n < 100 ? '0' + n : n }
return this.getFullYear() + '-' +
pad(this.getMonth() + 1) + '-' +
pad(this.getDate()) + 'T' +
pad(this.getHours()) + ':' +
pad(this.getMinutes()) + ':' +
pad(this.getSeconds()) + '.' +
ms(this.getMilliseconds()) + 'Z';
}
}
function createHAR(address, title, startTime, resources)
{
var entries = [];
resources.forEach(function (resource) {
var request = resource.request,
startReply = resource.startReply,
endReply = resource.endReply;
if (!request || !startReply || !endReply) {
return;
}
// Exclude Data URI from HAR file because
// they aren't included in specification
if (request.url.match(/(^data:image\/.*)/i)) {
return;
}
entries.push({
startedDateTime: request.time.toISOString(),
time: endReply.time - request.time,
request: {
method: request.method,
url: request.url,
httpVersion: "HTTP/1.1",
cookies: [],
headers: request.headers,
queryString: [],
headersSize: -1,
bodySize: -1
},
response: {
status: endReply.status,
statusText: endReply.statusText,
httpVersion: "HTTP/1.1",
cookies: [],
headers: endReply.headers,
redirectURL: "",
headersSize: -1,
bodySize: startReply.bodySize,
content: {
size: startReply.bodySize,
mimeType: endReply.contentType
}
},
cache: {},
timings: {
blocked: 0,
dns: -1,
connect: -1,
send: 0,
wait: startReply.time - request.time,
receive: endReply.time - startReply.time,
ssl: -1
},
pageref: address
});
});
return {
log: {
version: '1.2',
creator: {
name: "PhantomJS",
version: phantom.version.major + '.' + phantom.version.minor +
'.' + phantom.version.patch
},
pages: [{
startedDateTime: startTime.toISOString(),
id: address,
title: title,
pageTimings: {
onLoad: page.endTime - page.startTime
}
}],
entries: entries
}
};
}
var page = require('webpage').create(),
system = require('system');
if (system.args.length === 1) {
console.log('Usage: netsniff.js <some URL>');
phantom.exit(1);
} else {
page.address = system.args[1];
page.resources = [];
page.onLoadStarted = function () {
page.startTime = new Date();
};
page.onResourceRequested = function (req) {
page.resources[req.id] = {
request: req,
startReply: null,
endReply: null
};
};
page.onResourceReceived = function (res) {
if (res.stage === 'start') {
page.resources[res.id].startReply = res;
}
if (res.stage === 'end') {
page.resources[res.id].endReply = res;
}
};
page.open(page.address, function (status) {
var har;
if (status !== 'success') {
console.log('FAIL to load the address');
phantom.exit(1);
} else {
page.endTime = new Date();
page.title = page.evaluate(function () {
return document.title;
});
har = createHAR(page.address, page.title, page.startTime, page.resources);
console.log(JSON.stringify(har, undefined, 4));
phantom.exit();
}
});
}

View File

@ -0,0 +1,25 @@
"use strict";
var page = require('webpage').create(),
system = require('system'),
host, port, address;
if (system.args.length < 4) {
console.log('Usage: openurlwithproxy.js <proxyHost> <proxyPort> <URL>');
phantom.exit(1);
} else {
host = system.args[1];
port = system.args[2];
address = system.args[3];
phantom.setProxy(host, port, 'manual', '', '');
page.open(address, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address "' +
address + '" using proxy "' + host + ':' + port + '"');
} else {
console.log('Page title is ' + page.evaluate(function () {
return document.title;
}));
}
phantom.exit();
});
}

View File

@ -0,0 +1,17 @@
"use strict";
function helloWorld() {
console.log(phantom.outputEncoding + ": こんにちは、世界!");
}
console.log("Using default encoding...");
helloWorld();
console.log("\nUsing other encodings...");
var encodings = ["euc-jp", "sjis", "utf8", "System"];
for (var i = 0; i < encodings.length; i++) {
phantom.outputEncoding = encodings[i];
helloWorld();
}
phantom.exit()

147
examples/page_events.js Normal file
View File

@ -0,0 +1,147 @@
// The purpose of this is to show how and when events fire, considering 5 steps
// happening as follows:
//
// 1. Load URL
// 2. Load same URL, but adding an internal FRAGMENT to it
// 3. Click on an internal Link, that points to another internal FRAGMENT
// 4. Click on an external Link, that will send the page somewhere else
// 5. Close page
//
// Take particular care when going through the output, to understand when
// things happen (and in which order). Particularly, notice what DOESN'T
// happen during step 3.
//
// If invoked with "-v" it will print out the Page Resources as they are
// Requested and Received.
//
// NOTE.1: The "onConsoleMessage/onAlert/onPrompt/onConfirm" events are
// registered but not used here. This is left for you to have fun with.
// NOTE.2: This script is not here to teach you ANY JavaScript. It's aweful!
// NOTE.3: Main audience for this are people new to PhantomJS.
"use strict";
var sys = require("system"),
page = require("webpage").create(),
logResources = false,
step1url = "http://en.wikipedia.org/wiki/DOM_events",
step2url = "http://en.wikipedia.org/wiki/DOM_events#Event_flow";
if (sys.args.length > 1 && sys.args[1] === "-v") {
logResources = true;
}
function printArgs() {
var i, ilen;
for (i = 0, ilen = arguments.length; i < ilen; ++i) {
console.log(" arguments[" + i + "] = " + JSON.stringify(arguments[i]));
}
console.log("");
}
////////////////////////////////////////////////////////////////////////////////
page.onInitialized = function() {
console.log("page.onInitialized");
printArgs.apply(this, arguments);
};
page.onLoadStarted = function() {
console.log("page.onLoadStarted");
printArgs.apply(this, arguments);
};
page.onLoadFinished = function() {
console.log("page.onLoadFinished");
printArgs.apply(this, arguments);
};
page.onUrlChanged = function() {
console.log("page.onUrlChanged");
printArgs.apply(this, arguments);
};
page.onNavigationRequested = function() {
console.log("page.onNavigationRequested");
printArgs.apply(this, arguments);
};
page.onRepaintRequested = function() {
console.log("page.onRepaintRequested");
printArgs.apply(this, arguments);
};
if (logResources === true) {
page.onResourceRequested = function() {
console.log("page.onResourceRequested");
printArgs.apply(this, arguments);
};
page.onResourceReceived = function() {
console.log("page.onResourceReceived");
printArgs.apply(this, arguments);
};
}
page.onClosing = function() {
console.log("page.onClosing");
printArgs.apply(this, arguments);
};
// window.console.log(msg);
page.onConsoleMessage = function() {
console.log("page.onConsoleMessage");
printArgs.apply(this, arguments);
};
// window.alert(msg);
page.onAlert = function() {
console.log("page.onAlert");
printArgs.apply(this, arguments);
};
// var confirmed = window.confirm(msg);
page.onConfirm = function() {
console.log("page.onConfirm");
printArgs.apply(this, arguments);
};
// var user_value = window.prompt(msg, default_value);
page.onPrompt = function() {
console.log("page.onPrompt");
printArgs.apply(this, arguments);
};
////////////////////////////////////////////////////////////////////////////////
setTimeout(function() {
console.log("");
console.log("### STEP 1: Load '" + step1url + "'");
page.open(step1url);
}, 0);
setTimeout(function() {
console.log("");
console.log("### STEP 2: Load '" + step2url + "' (load same URL plus FRAGMENT)");
page.open(step2url);
}, 5000);
setTimeout(function() {
console.log("");
console.log("### STEP 3: Click on page internal link (aka FRAGMENT)");
page.evaluate(function() {
var ev = document.createEvent("MouseEvents");
ev.initEvent("click", true, true);
document.querySelector("a[href='#Event_object']").dispatchEvent(ev);
});
}, 10000);
setTimeout(function() {
console.log("");
console.log("### STEP 4: Click on page external link");
page.evaluate(function() {
var ev = document.createEvent("MouseEvents");
ev.initEvent("click", true, true);
document.querySelector("a[title='JavaScript']").dispatchEvent(ev);
});
}, 15000);
setTimeout(function() {
console.log("");
console.log("### STEP 5: Close page and shutdown (with a delay)");
page.close();
setTimeout(function(){
phantom.exit();
}, 100);
}, 20000);

18
examples/pagecallback.js Normal file
View File

@ -0,0 +1,18 @@
"use strict";
var p = require("webpage").create();
p.onConsoleMessage = function(msg) { console.log(msg); };
// Calls to "callPhantom" within the page 'p' arrive here
p.onCallback = function(msg) {
console.log("Received by the 'phantom' main context: "+msg);
return "Hello there, I'm coming to you from the 'phantom' context instead";
};
p.evaluate(function() {
// Return-value of the "onCallback" handler arrive here
var callbackResponse = window.callPhantom("Hello, I'm coming to you from the 'page' context");
console.log("Received by the 'page' context: "+callbackResponse);
});
phantom.exit();

View File

@ -0,0 +1,21 @@
// Read the Phantom webpage '#intro' element text using jQuery and "includeJs"
"use strict";
var page = require('webpage').create();
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.open("http://phantomjs.org/", function(status) {
if (status === "success") {
page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {
page.evaluate(function() {
console.log("$(\".explanation\").text() -> " + $(".explanation").text());
});
phantom.exit(0);
});
} else {
phantom.exit(1);
}
});

15
examples/post.js Normal file
View File

@ -0,0 +1,15 @@
// Example using HTTP POST operation
"use strict";
var page = require('webpage').create(),
server = 'http://posttestserver.com/post.php?dump',
data = 'universe=expanding&answer=42';
page.open(server, 'post', data, function (status) {
if (status !== 'success') {
console.log('Unable to post!');
} else {
console.log(page.content);
}
phantom.exit();
});

19
examples/postjson.js Normal file
View File

@ -0,0 +1,19 @@
// Example using HTTP POST operation
"use strict";
var page = require('webpage').create(),
server = 'http://posttestserver.com/post.php?dump',
data = '{"universe": "expanding", "answer": 42}';
var headers = {
"Content-Type": "application/json"
}
page.open(server, 'post', data, headers, function (status) {
if (status !== 'success') {
console.log('Unable to post!');
} else {
console.log(page.content);
}
phantom.exit();
});

35
examples/postserver.js Normal file
View File

@ -0,0 +1,35 @@
// Example using HTTP POST operation
"use strict";
var page = require('webpage').create(),
server = require('webserver').create(),
system = require('system'),
data = 'universe=expanding&answer=42';
if (system.args.length !== 2) {
console.log('Usage: postserver.js <portnumber>');
phantom.exit(1);
}
var port = system.args[1];
service = server.listen(port, function (request, response) {
console.log('Request received at ' + new Date());
response.statusCode = 200;
response.headers = {
'Cache': 'no-cache',
'Content-Type': 'text/plain;charset=utf-8'
};
response.write(JSON.stringify(request, null, 4));
response.close();
});
page.open('http://localhost:' + port + '/', 'post', data, function (status) {
if (status !== 'success') {
console.log('Unable to post!');
} else {
console.log(page.plainText);
}
phantom.exit();
});

10
examples/printenv.js Normal file
View File

@ -0,0 +1,10 @@
var system = require('system'),
env = system.env,
key;
for (key in env) {
if (env.hasOwnProperty(key)) {
console.log(key + '=' + env[key]);
}
}
phantom.exit();

View File

@ -0,0 +1,90 @@
"use strict";
var page = require('webpage').create(),
system = require('system');
function someCallback(pageNum, numPages) {
return "<h1> someCallback: " + pageNum + " / " + numPages + "</h1>";
}
if (system.args.length < 3) {
console.log('Usage: printheaderfooter.js URL filename');
phantom.exit(1);
} else {
var address = system.args[1];
var output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
page.paperSize = {
format: 'A4',
margin: "1cm",
/* default header/footer for pages that don't have custom overwrites (see below) */
header: {
height: "1cm",
contents: phantom.callback(function(pageNum, numPages) {
if (pageNum == 1) {
return "";
}
return "<h1>Header <span style='float:right'>" + pageNum + " / " + numPages + "</span></h1>";
})
},
footer: {
height: "1cm",
contents: phantom.callback(function(pageNum, numPages) {
if (pageNum == numPages) {
return "";
}
return "<h1>Footer <span style='float:right'>" + pageNum + " / " + numPages + "</span></h1>";
})
}
};
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
} else {
/* check whether the loaded page overwrites the header/footer setting,
i.e. whether a PhantomJSPriting object exists. Use that then instead
of our defaults above.
example:
<html>
<head>
<script type="text/javascript">
var PhantomJSPrinting = {
header: {
height: "1cm",
contents: function(pageNum, numPages) { return pageNum + "/" + numPages; }
},
footer: {
height: "1cm",
contents: function(pageNum, numPages) { return pageNum + "/" + numPages; }
}
};
</script>
</head>
<body><h1>asdfadsf</h1><p>asdfadsfycvx</p></body>
</html>
*/
if (page.evaluate(function(){return typeof PhantomJSPrinting == "object";})) {
paperSize = page.paperSize;
paperSize.header.height = page.evaluate(function() {
return PhantomJSPrinting.header.height;
});
paperSize.header.contents = phantom.callback(function(pageNum, numPages) {
return page.evaluate(function(pageNum, numPages){return PhantomJSPrinting.header.contents(pageNum, numPages);}, pageNum, numPages);
});
paperSize.footer.height = page.evaluate(function() {
return PhantomJSPrinting.footer.height;
});
paperSize.footer.contents = phantom.callback(function(pageNum, numPages) {
return page.evaluate(function(pageNum, numPages){return PhantomJSPrinting.footer.contents(pageNum, numPages);}, pageNum, numPages);
});
page.paperSize = paperSize;
console.log(page.paperSize.header.height);
console.log(page.paperSize.footer.height);
}
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}

36
examples/printmargins.js Normal file
View File

@ -0,0 +1,36 @@
"use strict";
var page = require('webpage').create(),
system = require('system');
if (system.args.length < 7) {
console.log('Usage: printmargins.js URL filename LEFT TOP RIGHT BOTTOM');
console.log(' margin examples: "1cm", "10px", "7mm", "5in"');
phantom.exit(1);
} else {
var address = system.args[1];
var output = system.args[2];
var marginLeft = system.args[3];
var marginTop = system.args[4];
var marginRight = system.args[5];
var marginBottom = system.args[6];
page.viewportSize = { width: 600, height: 600 };
page.paperSize = {
format: 'A4',
margin: {
left: marginLeft,
top: marginTop,
right: marginRight,
bottom: marginBottom
}
};
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}

49
examples/rasterize.js Normal file
View File

@ -0,0 +1,49 @@
"use strict";
var page = require('webpage').create(),
system = require('system'),
address, output, size;
if (system.args.length < 3 || system.args.length > 5) {
console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
console.log(' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
console.log(' image (png/jpg output) examples: "1920px" entire page, window width 1920px');
console.log(' "800px*600px" window, clipped to 800x600');
phantom.exit(1);
} else {
address = system.args[1];
output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
size = system.args[3].split('*');
page.paperSize = size.length === 2 ? { width: size[0], height: size[1], margin: '0px' }
: { format: system.args[3], orientation: 'portrait', margin: '1cm' };
} else if (system.args.length > 3 && system.args[3].substr(-2) === "px") {
size = system.args[3].split('*');
if (size.length === 2) {
pageWidth = parseInt(size[0], 10);
pageHeight = parseInt(size[1], 10);
page.viewportSize = { width: pageWidth, height: pageHeight };
page.clipRect = { top: 0, left: 0, width: pageWidth, height: pageHeight };
} else {
console.log("size:", system.args[3]);
pageWidth = parseInt(system.args[3], 10);
pageHeight = parseInt(pageWidth * 3/4, 10); // it's as good an assumption as any
console.log ("pageHeight:",pageHeight);
page.viewportSize = { width: pageWidth, height: pageHeight };
}
}
if (system.args.length > 4) {
page.zoomFactor = system.args[4];
}
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}

View File

@ -0,0 +1,74 @@
// Render Multiple URLs to file
"use strict";
var RenderUrlsToFile, arrayOfUrls, system;
system = require("system");
/*
Render given urls
@param array of URLs to render
@param callbackPerUrl Function called after finishing each URL, including the last URL
@param callbackFinal Function called after finishing everything
*/
RenderUrlsToFile = function(urls, callbackPerUrl, callbackFinal) {
var getFilename, next, page, retrieve, urlIndex, webpage;
urlIndex = 0;
webpage = require("webpage");
page = null;
getFilename = function() {
return "rendermulti-" + urlIndex + ".png";
};
next = function(status, url, file) {
page.close();
callbackPerUrl(status, url, file);
return retrieve();
};
retrieve = function() {
var url;
if (urls.length > 0) {
url = urls.shift();
urlIndex++;
page = webpage.create();
page.viewportSize = {
width: 800,
height: 600
};
page.settings.userAgent = "Phantom.js bot";
return page.open("http://" + url, function(status) {
var file;
file = getFilename();
if (status === "success") {
return window.setTimeout((function() {
page.render(file);
return next(status, url, file);
}), 200);
} else {
return next(status, url, file);
}
});
} else {
return callbackFinal();
}
};
return retrieve();
};
arrayOfUrls = null;
if (system.args.length > 1) {
arrayOfUrls = Array.prototype.slice.call(system.args, 1);
} else {
console.log("Usage: phantomjs render_multi_url.js [domain.name1, domain.name2, ...]");
arrayOfUrls = ["www.google.com", "www.bbc.co.uk", "phantomjs.org"];
}
RenderUrlsToFile(arrayOfUrls, (function(status, url, file) {
if (status !== "success") {
return console.log("Unable to render '" + url + "'");
} else {
return console.log("Rendered '" + url + "' at '" + file + "'");
}
}), function() {
return phantom.exit();
});

View File

@ -0,0 +1,181 @@
/**
* Captures the full height document even if it's not showing on the screen or captures with the provided range of screen sizes.
*
* A basic example for taking a screen shot using phantomjs which is sampled for https://nodejs-dersleri.github.io/
*
* usage : phantomjs responsive-screenshot.js {url} [output format] [doClipping]
*
* examples >
* phantomjs responsive-screenshot.js https://nodejs-dersleri.github.io/
* phantomjs responsive-screenshot.js https://nodejs-dersleri.github.io/ pdf
* phantomjs responsive-screenshot.js https://nodejs-dersleri.github.io/ true
* phantomjs responsive-screenshot.js https://nodejs-dersleri.github.io/ png true
*
* @author Salih sagdilek <salihsagdilek@gmail.com>
*/
/**
* http://phantomjs.org/api/system/property/args.html
*
* Queries and returns a list of the command-line arguments.
* The first one is always the script name, which is then followed by the subsequent arguments.
*/
var args = require('system').args;
/**
* http://phantomjs.org/api/fs/
*
* file system api
*/
var fs = require('fs');
/**
* http://phantomjs.org/api/webpage/
*
* Web page api
*/
var page = new WebPage();
/**
* if url address does not exist, exit phantom
*/
if ( 1 === args.length ) {
console.log('Url address is required');
phantom.exit();
}
/**
* setup url address (second argument);
*/
var urlAddress = args[1].toLowerCase();
/**
* set output extension format
* @type {*}
*/
var ext = getFileExtension();
/**
* set if clipping ?
* @type {boolean}
*/
var clipping = getClipping();
/**
* setup viewports
*/
var viewports = [
{
width : 1200,
height : 800
},
{
width : 1024,
height : 768
},
{
width : 768,
height : 1024
},
{
width : 480,
height : 640
},
{
width : 320,
height : 480
}
];
page.open(urlAddress, function (status) {
if ( 'success' !== status ) {
console.log('Unable to load the url address!');
} else {
var folder = urlToDir(urlAddress);
var output, key;
function render(n) {
if ( !!n ) {
key = n - 1;
page.viewportSize = viewports[key];
if ( clipping ) {
page.clipRect = viewports[key];
}
output = folder + "/" + getFileName(viewports[key]);
console.log('Saving ' + output);
page.render(output);
render(key);
}
}
render(viewports.length);
}
phantom.exit();
});
/**
* filename generator helper
* @param viewport
* @returns {string}
*/
function getFileName(viewport) {
var d = new Date();
var date = [
d.getUTCFullYear(),
d.getUTCMonth() + 1,
d.getUTCDate()
];
var time = [
d.getHours() <= 9 ? '0' + d.getHours() : d.getHours(),
d.getMinutes() <= 9 ? '0' + d.getMinutes() : d.getMinutes(),
d.getSeconds() <= 9 ? '0' + d.getSeconds() : d.getSeconds(),
d.getMilliseconds()
];
var resolution = viewport.width + (clipping ? "x" + viewport.height : '');
return date.join('-') + '_' + time.join('-') + "_" + resolution + ext;
}
/**
* output extension format helper
*
* @returns {*}
*/
function getFileExtension() {
if ( 'true' != args[2] && !!args[2] ) {
return '.' + args[2];
}
return '.png';
}
/**
* check if clipping
*
* @returns {boolean}
*/
function getClipping() {
if ( 'true' == args[3] ) {
return !!args[3];
} else if ( 'true' == args[2] ) {
return !!args[2];
}
return false;
}
/**
* url to directory helper
*
* @param url
* @returns {string}
*/
function urlToDir(url) {
var dir = url
.replace(/^(http|https):\/\//, '')
.replace(/\/$/, '');
if ( !fs.makeTree(dir) ) {
console.log('"' + dir + '" is NOT created.');
phantom.exit();
}
return dir;
}

92
examples/run-jasmine.js Normal file
View File

@ -0,0 +1,92 @@
"use strict";
var system = require('system');
/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timeout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
};
if (system.args.length !== 2) {
console.log('Usage: run-jasmine.js URL');
phantom.exit(1);
}
var page = require('webpage').create();
// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.open(system.args[1], function(status){
if (status !== "success") {
console.log("Unable to open " + system.args[1]);
phantom.exit(1);
} else {
waitFor(function(){
return page.evaluate(function(){
return document.body.querySelector('.symbolSummary .pending') === null
});
}, function(){
var exitCode = page.evaluate(function(){
try {
console.log('');
console.log(document.body.querySelector('.description').innerText);
var list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
if (list && list.length > 0) {
console.log('');
console.log(list.length + ' test(s) FAILED:');
for (i = 0; i < list.length; ++i) {
var el = list[i],
desc = el.querySelector('.description'),
msg = el.querySelector('.resultMessage.fail');
console.log('');
console.log(desc.innerText);
console.log(msg.innerText);
console.log('');
}
return 1;
} else {
console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText);
return 0;
}
} catch (ex) {
console.log(ex);
return 1;
}
});
phantom.exit(exitCode);
});
}
});

94
examples/run-jasmine2.js Normal file
View File

@ -0,0 +1,94 @@
"use strict";
var system = require('system');
/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timeout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
};
if (system.args.length !== 2) {
console.log('Usage: run-jasmine2.js URL');
phantom.exit(1);
}
var page = require('webpage').create();
// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.open(system.args[1], function(status){
if (status !== "success") {
console.log("Unable to access network");
phantom.exit();
} else {
waitFor(function(){
return page.evaluate(function(){
return (document.body.querySelector('.symbolSummary .pending') === null &&
document.body.querySelector('.duration') !== null);
});
}, function(){
var exitCode = page.evaluate(function(){
console.log('');
var title = 'Jasmine';
var version = document.body.querySelector('.version').innerText;
var duration = document.body.querySelector('.duration').innerText;
var banner = title + ' ' + version + ' ' + duration;
console.log(banner);
var list = document.body.querySelectorAll('.results > .failures > .spec-detail.failed');
if (list && list.length > 0) {
console.log('');
console.log(list.length + ' test(s) FAILED:');
for (i = 0; i < list.length; ++i) {
var el = list[i],
desc = el.querySelector('.description'),
msg = el.querySelector('.messages > .result-message');
console.log('');
console.log(desc.innerText);
console.log(msg.innerText);
console.log('');
}
return 1;
} else {
console.log(document.body.querySelector('.alert > .bar.passed,.alert > .bar.skipped').innerText);
return 0;
}
});
phantom.exit(exitCode);
});
}
});

77
examples/run-qunit.js Normal file
View File

@ -0,0 +1,77 @@
"use strict";
var system = require('system');
/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 250ms
};
if (system.args.length !== 2) {
console.log('Usage: run-qunit.js URL');
phantom.exit(1);
}
var page = require('webpage').create();
// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.open(system.args[1], function(status){
if (status !== "success") {
console.log("Unable to access network");
phantom.exit(1);
} else {
waitFor(function(){
return page.evaluate(function(){
var el = document.getElementById('qunit-testresult');
if (el && el.innerText.match('completed')) {
return true;
}
return false;
});
}, function(){
var failedNum = page.evaluate(function(){
var el = document.getElementById('qunit-testresult');
console.log(el.innerText);
try {
return el.getElementsByClassName('failed')[0].innerHTML;
} catch (e) { }
return 10000;
});
phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0);
});
}
});

24
examples/scandir.js Normal file
View File

@ -0,0 +1,24 @@
// List all the files in a Tree of Directories
"use strict";
var system = require('system');
if (system.args.length !== 2) {
console.log("Usage: phantomjs scandir.js DIRECTORY_TO_SCAN");
phantom.exit(1);
}
var scanDirectory = function (path) {
var fs = require('fs');
if (fs.exists(path) && fs.isFile(path)) {
console.log(path);
} else if (fs.isDirectory(path)) {
fs.list(path).forEach(function (e) {
if ( e !== "." && e !== ".." ) { //< Avoid loops
scanDirectory(path + '/' + e);
}
});
}
};
scanDirectory(system.args[1]);
phantom.exit();

44
examples/server.js Normal file
View File

@ -0,0 +1,44 @@
"use strict";
var page = require('webpage').create();
var server = require('webserver').create();
var system = require('system');
var host, port;
if (system.args.length !== 2) {
console.log('Usage: server.js <some port>');
phantom.exit(1);
} else {
port = system.args[1];
var listening = server.listen(port, function (request, response) {
console.log("GOT HTTP REQUEST");
console.log(JSON.stringify(request, null, 4));
// we set the headers here
response.statusCode = 200;
response.headers = {"Cache": "no-cache", "Content-Type": "text/html"};
// this is also possible:
response.setHeader("foo", "bar");
// now we write the body
// note: the headers above will now be sent implictly
response.write("<html><head><title>YES!</title></head>");
// note: writeBody can be called multiple times
response.write("<body><p>pretty cool :)</body></html>");
response.close();
});
if (!listening) {
console.log("could not create web server listening on port " + port);
phantom.exit();
}
var url = "http://localhost:" + port + "/foo/bar.php?asdf=true";
console.log("SENDING REQUEST TO:");
console.log(url);
page.open(url, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
} else {
console.log("GOT REPLY FROM SERVER:");
console.log(page.content);
}
phantom.exit();
});
}

View File

@ -0,0 +1,35 @@
"use strict";
var port, server, service,
system = require('system');
if (system.args.length !== 2) {
console.log('Usage: serverkeepalive.js <portnumber>');
phantom.exit(1);
} else {
port = system.args[1];
server = require('webserver').create();
service = server.listen(port, { keepAlive: true }, function (request, response) {
console.log('Request at ' + new Date());
console.log(JSON.stringify(request, null, 4));
var body = JSON.stringify(request, null, 4);
response.statusCode = 200;
response.headers = {
'Cache': 'no-cache',
'Content-Type': 'text/plain',
'Connection': 'Keep-Alive',
'Keep-Alive': 'timeout=5, max=100',
'Content-Length': body.length
};
response.write(body);
response.close();
});
if (service) {
console.log('Web server running on port ' + port);
} else {
console.log('Error: Could not create web server listening on port ' + port);
phantom.exit();
}
}

43
examples/simpleserver.js Normal file
View File

@ -0,0 +1,43 @@
"use strict";
var port, server, service,
system = require('system');
if (system.args.length !== 2) {
console.log('Usage: simpleserver.js <portnumber>');
phantom.exit(1);
} else {
port = system.args[1];
server = require('webserver').create();
service = server.listen(port, function (request, response) {
console.log('Request at ' + new Date());
console.log(JSON.stringify(request, null, 4));
response.statusCode = 200;
response.headers = {
'Cache': 'no-cache',
'Content-Type': 'text/html'
};
response.write('<html>');
response.write('<head>');
response.write('<title>Hello, world!</title>');
response.write('</head>');
response.write('<body>');
response.write('<p>This is from PhantomJS web server.</p>');
response.write('<p>Request data:</p>');
response.write('<pre>');
response.write(JSON.stringify(request, null, 4));
response.write('</pre>');
response.write('</body>');
response.write('</html>');
response.close();
});
if (service) {
console.log('Web server running on port ' + port);
} else {
console.log('Error: Could not create web server listening on port ' + port);
phantom.exit();
}
}

27
examples/sleepsort.js Normal file
View File

@ -0,0 +1,27 @@
// sleepsort.js - Sort integers from the commandline in a very ridiculous way: leveraging timeouts :P
"use strict";
var system = require('system');
function sleepSort(array, callback) {
var sortedCount = 0,
i, len;
for ( i = 0, len = array.length; i < len; ++i ) {
setTimeout((function(j){
return function() {
console.log(array[j]);
++sortedCount;
(len === sortedCount) && callback();
};
}(i)), array[i]);
}
}
if ( system.args.length < 2 ) {
console.log("Usage: phantomjs sleepsort.js PUT YOUR INTEGERS HERE SEPARATED BY SPACES");
phantom.exit(1);
} else {
sleepSort(system.args.slice(1), function() {
phantom.exit();
});
}

View File

@ -0,0 +1,19 @@
"use strict";
var system = require('system');
system.stdout.write('Hello, system.stdout.write!');
system.stdout.writeLine('\nHello, system.stdout.writeLine!');
system.stderr.write('Hello, system.stderr.write!');
system.stderr.writeLine('\nHello, system.stderr.writeLine!');
system.stdout.writeLine('system.stdin.readLine(): ');
var line = system.stdin.readLine();
system.stdout.writeLine(JSON.stringify(line));
// This is essentially a `readAll`
system.stdout.writeLine('system.stdin.read(5): (ctrl+D to end)');
var input = system.stdin.read(5);
system.stdout.writeLine(JSON.stringify(input));
phantom.exit(0);

10
examples/universe.js Normal file
View File

@ -0,0 +1,10 @@
// This is to be used by "module.js" (and "module.coffee") example(s).
// There should NOT be a "universe.coffee" as only 1 of the 2 would
// ever be loaded unless the file extension was specified.
"use strict";
exports.answer = 42;
exports.start = function () {
console.log('Starting the universe....');
}

25
examples/unrandomize.js Normal file
View File

@ -0,0 +1,25 @@
// Modify global object at the page initialization.
// In this example, effectively Math.random() always returns 0.42.
"use strict";
var page = require('webpage').create();
page.onInitialized = function () {
page.evaluate(function () {
Math.random = function() {
return 42 / 100;
};
});
};
page.open('http://ariya.github.com/js/random/', function (status) {
var result;
if (status !== 'success') {
console.log('Network error.');
} else {
console.log(page.evaluate(function () {
return document.getElementById('numbers').textContent;
}));
}
phantom.exit();
});

15
examples/useragent.js Normal file
View File

@ -0,0 +1,15 @@
"use strict";
var page = require('webpage').create();
console.log('The default user agent is ' + page.settings.userAgent);
page.settings.userAgent = 'SpecialAgent';
page.open('http://www.httpuseragent.org', function (status) {
if (status !== 'success') {
console.log('Unable to access network');
} else {
var ua = page.evaluate(function () {
return document.getElementById('myagent').innerText;
});
console.log(ua);
}
phantom.exit();
});

6
examples/version.js Normal file
View File

@ -0,0 +1,6 @@
"use strict";
console.log('using PhantomJS version ' +
phantom.version.major + '.' +
phantom.version.minor + '.' +
phantom.version.patch);
phantom.exit();

58
examples/waitfor.js Normal file
View File

@ -0,0 +1,58 @@
/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
"use strict";
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 250); //< repeat check every 250ms
};
var page = require('webpage').create();
// Open Twitter on 'sencha' profile and, onPageLoad, do...
page.open("http://twitter.com/#!/sencha", function (status) {
// Check for page load success
if (status !== "success") {
console.log("Unable to access network");
} else {
// Wait for 'signin-dropdown' to be visible
waitFor(function() {
// Check in the page if a specific element is now visible
return page.evaluate(function() {
return $("#signin-dropdown").is(":visible");
});
}, function() {
console.log("The sign-in dialog should be visible now.");
phantom.exit();
});
}
});

View File

@ -0,0 +1,73 @@
"use strict";
var p = require("webpage").create();
function pageTitle(page) {
return page.evaluate(function(){
return window.document.title;
});
}
function setPageTitle(page, newTitle) {
page.evaluate(function(newTitle){
window.document.title = newTitle;
}, newTitle);
}
p.open("../test/webpage-spec-frames/index.html", function(status) {
console.log("pageTitle(): " + pageTitle(p));
console.log("currentFrameName(): "+p.currentFrameName());
console.log("childFramesCount(): "+p.childFramesCount());
console.log("childFramesName(): "+p.childFramesName());
console.log("setPageTitle(CURRENT TITLE+'-visited')"); setPageTitle(p, pageTitle(p) + "-visited");
console.log("");
console.log("p.switchToChildFrame(\"frame1\"): "+p.switchToChildFrame("frame1"));
console.log("pageTitle(): " + pageTitle(p));
console.log("currentFrameName(): "+p.currentFrameName());
console.log("childFramesCount(): "+p.childFramesCount());
console.log("childFramesName(): "+p.childFramesName());
console.log("setPageTitle(CURRENT TITLE+'-visited')"); setPageTitle(p, pageTitle(p) + "-visited");
console.log("");
console.log("p.switchToChildFrame(\"frame1-2\"): "+p.switchToChildFrame("frame1-2"));
console.log("pageTitle(): " + pageTitle(p));
console.log("currentFrameName(): "+p.currentFrameName());
console.log("childFramesCount(): "+p.childFramesCount());
console.log("childFramesName(): "+p.childFramesName());
console.log("setPageTitle(CURRENT TITLE+'-visited')"); setPageTitle(p, pageTitle(p) + "-visited");
console.log("");
console.log("p.switchToParentFrame(): "+p.switchToParentFrame());
console.log("pageTitle(): " + pageTitle(p));
console.log("currentFrameName(): "+p.currentFrameName());
console.log("childFramesCount(): "+p.childFramesCount());
console.log("childFramesName(): "+p.childFramesName());
console.log("setPageTitle(CURRENT TITLE+'-visited')"); setPageTitle(p, pageTitle(p) + "-visited");
console.log("");
console.log("p.switchToChildFrame(0): "+p.switchToChildFrame(0));
console.log("pageTitle(): " + pageTitle(p));
console.log("currentFrameName(): "+p.currentFrameName());
console.log("childFramesCount(): "+p.childFramesCount());
console.log("childFramesName(): "+p.childFramesName());
console.log("setPageTitle(CURRENT TITLE+'-visited')"); setPageTitle(p, pageTitle(p) + "-visited");
console.log("");
console.log("p.switchToMainFrame()"); p.switchToMainFrame();
console.log("pageTitle(): " + pageTitle(p));
console.log("currentFrameName(): "+p.currentFrameName());
console.log("childFramesCount(): "+p.childFramesCount());
console.log("childFramesName(): "+p.childFramesName());
console.log("setPageTitle(CURRENT TITLE+'-visited')"); setPageTitle(p, pageTitle(p) + "-visited");
console.log("");
console.log("p.switchToChildFrame(\"frame2\"): "+p.switchToChildFrame("frame2"));
console.log("pageTitle(): " + pageTitle(p));
console.log("currentFrameName(): "+p.currentFrameName());
console.log("childFramesCount(): "+p.childFramesCount());
console.log("childFramesName(): "+p.childFramesName());
console.log("setPageTitle(CURRENT TITLE+'-visited')"); setPageTitle(p, pageTitle(p) + "-visited");
console.log("");
phantom.exit();
});

3
phantomjs.pro Normal file
View File

@ -0,0 +1,3 @@
TEMPLATE = subdirs
CONFIG += ordered
SUBDIRS += src/phantomjs.pro

18
src/.gitignore-breakpad Normal file
View File

@ -0,0 +1,18 @@
/src/client/linux/linux_dumper_unittest_helper
/src/processor/minidump_dump
/src/processor/minidump_stackwalk
/src/tools/linux/core2md/core2md
/src/tools/linux/dump_syms/dump_syms
/src/tools/linux/md2core/minidump-2-core
/src/tools/linux/symupload/minidump_upload
/src/tools/linux/symupload/sym_upload
/src/config.h
/src/stamp-h1
/config.log
/config.status
/autom4te.cache
!Makefile.am
!Makefile.in
.dirstamp
.deps

12
src/Info.plist Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
<key>CFBundleExecutable</key>
<string>phantomjs</string>
<key>CFBundleIdentifier</key>
<string>org.phantomjs</string>
<key>LSUIElement</key>
<string>1</string>
</dict>
</plist>

327
src/bootstrap.js vendored Normal file
View File

@ -0,0 +1,327 @@
/*jslint sloppy: true, nomen: true */
/*global window:true,phantom:true */
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.com>
Copyright (C) 2011 James Roe <roejames12@hotmail.com>
Copyright (C) 2011 execjosh, http://execjosh.blogspot.com
Copyright (C) 2012 James M. Greene <james.m.greene@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
phantom.__defineErrorSignalHandler__ = function(obj, page, handlers) {
var handlerName = 'onError';
Object.defineProperty(obj, handlerName, {
set: function (f) {
// Disconnect previous handler (if any)
var handlerObj = handlers[handlerName];
if (!!handlerObj && typeof handlerObj.callback === "function" && typeof handlerObj.connector === "function") {
try { page.javaScriptErrorSent.disconnect(handlerObj.connector); }
catch (e) { }
}
// Delete the previous handler
delete handlers[handlerName];
if (typeof f === 'function') {
var connector = function (message, lineNumber, source, stack) {
var revisedStack = JSON.parse(stack).map(function (item) {
return { file: item.url, line: item.lineNumber, function: item.functionName };
});
if (revisedStack.length == 0)
revisedStack = [{ file: source, line: lineNumber }];
f(message, revisedStack);
};
// Store the new handler for reference
handlers[handlerName] = {
callback: f,
connector: connector
};
page.javaScriptErrorSent.connect(connector);
}
},
get: function () {
var handlerObj = handlers[handlerName];
return (!!handlerObj && typeof handlerObj.callback === "function" && typeof handlerObj.connector === "function") ?
handlers[handlerName].callback :
undefined;
}
});
};
(function() {
var handlers = {};
phantom.__defineErrorSignalHandler__(phantom, phantom.page, handlers);
})();
// TODO: Make this output to STDERR
phantom.defaultErrorHandler = function(message, stack) {
console.log(message + "\n");
stack.forEach(function(item) {
var message = item.file + ":" + item.line;
if (item["function"])
message += " in " + item["function"];
console.log(" " + message);
});
};
phantom.onError = phantom.defaultErrorHandler;
phantom.callback = function(callback) {
var ret = phantom.createCallback();
ret.called.connect(function(args) {
var retVal = callback.apply(this, args);
ret.returnValue = retVal;
});
return ret;
};
(function() {
// CommonJS module implementation follows
window.global = window;
// fs is loaded at the end, when everything is ready
var fs;
var cache = {};
var paths = [];
// use getters to initialize lazily
// (for future, now both fs and system are loaded anyway)
var nativeExports = {
get fs() { return phantom.createFilesystem(); },
get child_process() { return phantom._createChildProcess(); },
get system() { return phantom.createSystem(); }
};
var extensions = {
'.js': function(module, filename) {
var code = fs.read(filename);
module._compile(code);
},
'.json': function(module, filename) {
module.exports = JSON.parse(fs.read(filename));
}
};
function loadFs() {
var file, code, module, filename = ':/modules/fs.js';
module = new Module(filename);
cache[filename] = module;
module.exports = nativeExports.fs;
file = module.exports._open(filename, { mode: 'r' })
code = file.read();
file.close();
module._compile(code);
return module.exports;
}
function dirname(path) {
var replaced = path.replace(/\/[^\/]*\/?$/, '');
if (replaced == path) {
replaced = '';
}
return replaced;
}
function basename(path) {
return path.replace(/.*\//, '');
}
function joinPath() {
// It should be okay to hard-code a slash here.
// The FileSystem module returns a platform-specific
// separator, but the JavaScript engine only expects
// the slash.
var args = Array.prototype.slice.call(arguments);
return args.join('/');
}
function tryFile(path) {
if (fs.isFile(path)) return path;
return null;
}
function tryExtensions(path) {
var filename, exts = Object.keys(extensions);
for (var i=0; i<exts.length; ++i) {
filename = tryFile(path + exts[i]);
if (filename) return filename;
}
return null;
}
function tryPackage(path) {
var filename, package, packageFile = joinPath(path, 'package.json');
if (fs.isFile(packageFile)) {
package = JSON.parse(fs.read(packageFile));
if (!package || !package.main) return null;
filename = fs.absolute(joinPath(path, package.main));
return tryFile(filename) || tryExtensions(filename) ||
tryExtensions(joinPath(filename, 'index'));
}
return null;
}
function Module(filename, stubs) {
if (filename) this._setFilename(filename);
this.exports = {};
this.stubs = {};
for (var name in stubs) {
this.stubs[name] = stubs[name];
}
}
Module.prototype._setFilename = function(filename) {
this.id = this.filename = filename;
this.dirname = dirname(filename);
};
Module.prototype._isNative = function() {
return this.filename && this.filename[0] === ':';
};
Module.prototype._getPaths = function(request) {
var _paths = [], dir;
if (request[0] === '.') {
_paths.push(fs.absolute(joinPath(phantom.webdriverMode ? ":/ghostdriver" : this.dirname, request)));
} else if (fs.isAbsolute(request)) {
_paths.push(fs.absolute(request));
} else {
// first look in PhantomJS modules
_paths.push(joinPath(':/modules', request));
// then look in node_modules directories
if (!this._isNative()) {
dir = this.dirname;
while (dir) {
_paths.push(joinPath(dir, 'node_modules', request));
dir = dirname(dir);
}
}
}
for (var i=0; i<paths.length; ++i) {
if(fs.isAbsolute(paths[i])) {
_paths.push(fs.absolute(joinPath(paths[i], request)));
} else {
_paths.push(fs.absolute(joinPath(this.dirname, paths[i], request)));
}
}
return _paths;
};
Module.prototype._getFilename = function(request) {
var path, filename = null, _paths = this._getPaths(request);
for (var i=0; i<_paths.length && !filename; ++i) {
path = _paths[i];
filename = tryFile(path) || tryExtensions(path) || tryPackage(path) ||
tryExtensions(joinPath(path, 'index'));
}
return filename;
};
Module.prototype._getRequire = function() {
var self = this;
function require(request) {
return self.require(request);
}
require.cache = cache;
require.extensions = extensions;
require.paths = paths;
require.stub = function(request, exports) {
self.stubs[request] = { exports: exports };
};
return require;
};
Module.prototype._load = function() {
var ext = this.filename.match(/\.[^.]+$/)[0];
if (!ext) ext = '.js';
extensions[ext](this, this.filename);
};
Module.prototype._compile = function(code) {
phantom.loadModule(code, this.filename);
};
Module.prototype.require = function(request) {
var filename, module;
// first see if there are any stubs for the request
if (this.stubs.hasOwnProperty(request)) {
if (this.stubs[request].exports instanceof Function) {
this.stubs[request].exports = this.stubs[request].exports();
}
return this.stubs[request].exports;
}
// else look for a file
filename = this._getFilename(request);
if (!filename) {
throw new Error("Cannot find module '" + request + "'");
}
if (cache.hasOwnProperty(filename)) {
return cache[filename].exports;
}
module = new Module(filename, this.stubs);
if (module._isNative()) {
module.exports = nativeExports[request] || {};
}
cache[filename] = module;
module._load();
return module.exports;
};
(function() {
var cwd, mainFilename, mainModule = new Module();
window.require = mainModule._getRequire();
fs = loadFs();
cwd = fs.absolute(phantom.libraryPath);
mainFilename = joinPath(cwd, basename(require('system').args[0]) || 'repl');
mainModule._setFilename(mainFilename);
}());
}());
// Legacy way to use WebPage
window.WebPage = require('webpage').create;

55
src/callback.cpp Normal file
View File

@ -0,0 +1,55 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 Milian Wolff, KDAB <milian.wolff@kdab.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "callback.h"
#include <QDebug>
Callback::Callback(QObject* parent)
: QObject(parent)
{
}
QVariant Callback::call(const QVariantList& arguments)
{
emit called(arguments);
qDebug() << "Callback - call result:" << m_returnValue;
return m_returnValue;
}
QVariant Callback::returnValue() const
{
return m_returnValue;
}
void Callback::setReturnValue(const QVariant& returnValue)
{
m_returnValue = returnValue;
}

57
src/callback.h Normal file
View File

@ -0,0 +1,57 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 Milian Wolff, KDAB <milian.wolff@kdab.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CALLBACK_H
#define CALLBACK_H
#include <QObject>
#include <QVariant>
class Callback : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariant returnValue READ returnValue WRITE setReturnValue)
public:
Callback(QObject* parent);
QVariant call(const QVariantList& arguments);
QVariant returnValue() const;
void setReturnValue(const QVariant& returnValue);
signals:
void called(const QVariantList& arguments);
private:
QVariant m_returnValue;
};
#endif // CALLBACK_H

136
src/childprocess.cpp Normal file
View File

@ -0,0 +1,136 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 execjosh, http://execjosh.blogspot.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "childprocess.h"
//
// ChildProcessContext
//
ChildProcessContext::ChildProcessContext(QObject* parent)
: QObject(parent)
, m_proc(this)
{
connect(&m_proc, SIGNAL(readyReadStandardOutput()), this, SLOT(_readyReadStandardOutput()));
connect(&m_proc, SIGNAL(readyReadStandardError()), this, SLOT(_readyReadStandardError()));
connect(&m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(_finished(int, QProcess::ExitStatus)));
connect(&m_proc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(_error(QProcess::ProcessError)));
}
ChildProcessContext::~ChildProcessContext()
{
}
// public:
qint64 ChildProcessContext::pid() const
{
Q_PID pid = m_proc.pid();
#if !defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
return pid;
#else
return pid->dwProcessId;
#endif
}
void ChildProcessContext::kill(const QString& signal)
{
// TODO: it would be nice to be able to handle more signals
if ("SIGKILL" == signal) {
m_proc.kill();
} else {
// Default to "SIGTERM"
m_proc.terminate();
}
}
void ChildProcessContext::_setEncoding(const QString& encoding)
{
m_encoding.setEncoding(encoding);
}
// This is affected by [QTBUG-5990](https://bugreports.qt-project.org/browse/QTBUG-5990).
// `QProcess` doesn't properly handle the situations of `cmd` not existing or
// failing to start...
bool ChildProcessContext::_start(const QString& cmd, const QStringList& args)
{
m_proc.start(cmd, args);
// TODO: Is there a better way to do this???
return m_proc.waitForStarted(1000);
}
// private slots:
void ChildProcessContext::_readyReadStandardOutput()
{
QByteArray bytes = m_proc.readAllStandardOutput();
emit stdoutData(m_encoding.decode(bytes));
}
void ChildProcessContext::_readyReadStandardError()
{
QByteArray bytes = m_proc.readAllStandardError();
emit stderrData(m_encoding.decode(bytes));
}
void ChildProcessContext::_finished(const int exitCode, const QProcess::ExitStatus exitStatus)
{
Q_UNUSED(exitStatus)
emit exit(exitCode);
}
void ChildProcessContext::_error(const QProcess::ProcessError error)
{
Q_UNUSED(error)
emit exit(m_proc.exitCode());
}
//
// ChildProcess
//
ChildProcess::ChildProcess(QObject* parent)
: QObject(parent)
{
}
ChildProcess::~ChildProcess()
{
}
// public:
QObject* ChildProcess::_createChildProcessContext()
{
return new ChildProcessContext(this);
}

97
src/childprocess.h Normal file
View File

@ -0,0 +1,97 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 execjosh, http://execjosh.blogspot.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CHILDPROCESS_H
#define CHILDPROCESS_H
#include <QObject>
#include <QProcess>
#ifdef Q_OS_WIN
#include <QtCore/qt_windows.h>
#endif
#include "encoding.h"
/**
* This class wraps a QProcess and facilitates emulation of node.js's ChildProcess
*/
class ChildProcessContext : public QObject
{
Q_OBJECT
Q_PROPERTY(qint64 pid READ pid)
public:
explicit ChildProcessContext(QObject* parent = 0);
virtual ~ChildProcessContext();
qint64 pid() const;
Q_INVOKABLE void kill(const QString& signal = "SIGTERM");
Q_INVOKABLE void _setEncoding(const QString& encoding);
Q_INVOKABLE bool _start(const QString& cmd, const QStringList& args);
signals:
void exit(const int code) const;
/**
* For emulating `child.stdout.on("data", function (data) {})`
*/
void stdoutData(const QString& data) const;
/**
* For emulating `child.stderr.on("data", function (data) {})`
*/
void stderrData(const QString& data) const;
private slots:
void _readyReadStandardOutput();
void _readyReadStandardError();
void _error(const QProcess::ProcessError error);
void _finished(const int exitCode, const QProcess::ExitStatus exitStatus);
private:
QProcess m_proc;
Encoding m_encoding;
};
/**
* Helper class for child_process module
*/
class ChildProcess : public QObject
{
Q_OBJECT
public:
explicit ChildProcess(QObject* parent = 0);
virtual ~ChildProcess();
Q_INVOKABLE QObject* _createChildProcessContext();
};
#endif // CHILDPROCESS_H

923
src/config.cpp Normal file
View File

@ -0,0 +1,923 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 execjosh, http://execjosh.blogspot.com
Copyright (C) 2013 James M. Greene <james.m.greene@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <QDir>
#include <QFileInfo>
#include <QtWebKitWidgets/QWebPage>
#include <QtWebKitWidgets/QWebFrame>
#include <QNetworkProxy>
#include "terminal.h"
#include "qcommandline.h"
#include "utils.h"
#include "consts.h"
#include <iostream>
static const struct QCommandLineConfigEntry flags[] = {
{ QCommandLine::Option, '\0', "cookies-file", "Sets the file name to store the persistent cookies", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "config", "Specifies JSON-formatted configuration file", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "debug", "Prints additional warning and debug message: 'true' or 'false' (default)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "disk-cache", "Enables disk cache: 'true' or 'false' (default)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "disk-cache-path", "Specifies the location for the disk cache", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ignore-ssl-errors", "Ignores SSL errors (expired/self-signed certificate errors): 'true' or 'false' (default)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "load-images", "Loads all inlined images: 'true' (default) or 'false'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-url-access", "Allows use of 'file:///' URLs: 'true' (default) or 'false'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-storage-path", "Specifies the location for local storage", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-storage-quota", "Sets the maximum size of the local storage (in KB)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "offline-storage-path", "Specifies the location for offline storage", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "offline-storage-quota", "Sets the maximum size of the offline storage (in KB)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-to-remote-url-access", "Allows local content to access remote URL: 'true' or 'false' (default)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "max-disk-cache-size", "Limits the size of the disk cache (in KB)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "output-encoding", "Sets the encoding for the terminal output, default is 'utf8'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "remote-debugger-port", "Starts the script in a debug harness and listens on the specified port", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "remote-debugger-autorun", "Runs the script in the debugger immediately: 'true' or 'false' (default)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "proxy", "Sets the proxy server, e.g. '--proxy=http://proxy.company.com:8080'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "proxy-auth", "Provides authentication information for the proxy, e.g. ''-proxy-auth=username:password'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "proxy-type", "Specifies the proxy type, 'http' (default), 'none' (disable completely), or 'socks5'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "script-encoding", "Sets the encoding used for the starting script, default is 'utf8'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "script-language", "Sets the script language instead of detecting it: 'javascript'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "web-security", "Enables web security, 'true' (default) or 'false'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-protocol", "Selects a specific SSL protocol version to offer. Values (case insensitive): TLSv1.2, TLSv1.1, TLSv1.0, TLSv1 (same as v1.0), SSLv3, or ANY. Default is to offer all that Qt thinks are secure (SSLv3 and up). Not all values may be supported, depending on the system OpenSSL library.", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-ciphers", "Sets supported TLS/SSL ciphers. Argument is a colon-separated list of OpenSSL cipher names (macros like ALL, kRSA, etc. may not be used). Default matches modern browsers.", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-certificates-path", "Sets the location for custom CA certificates (if none set, uses environment variable SSL_CERT_DIR. If none set too, uses system default)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-client-certificate-file", "Sets the location of a client certificate", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-client-key-file", "Sets the location of a clients' private key", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-client-key-passphrase", "Sets the passphrase for the clients' private key", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "webdriver", "Starts in 'Remote WebDriver mode' (embedded GhostDriver): '[[<IP>:]<PORT>]' (default '127.0.0.1:8910') ", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "webdriver-logfile", "File where to write the WebDriver's Log (default 'none') (NOTE: needs '--webdriver') ", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "webdriver-loglevel", "WebDriver Logging Level: (supported: 'ERROR', 'WARN', 'INFO', 'DEBUG') (default 'INFO') (NOTE: needs '--webdriver') ", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "webdriver-selenium-grid-hub", "URL to the Selenium Grid HUB: 'URL_TO_HUB' (default 'none') (NOTE: needs '--webdriver') ", QCommandLine::Optional },
{ QCommandLine::Param, '\0', "script", "Script", QCommandLine::Flags(QCommandLine::Optional | QCommandLine::ParameterFence)},
{ QCommandLine::Param, '\0', "argument", "Script argument", QCommandLine::OptionalMultiple },
{ QCommandLine::Switch, 'w', "wd", "Equivalent to '--webdriver' option above", QCommandLine::Optional },
{ QCommandLine::Switch, 'h', "help", "Shows this message and quits", QCommandLine::Optional },
{ QCommandLine::Switch, 'v', "version", "Prints out PhantomJS version", QCommandLine::Optional },
QCOMMANDLINE_CONFIG_ENTRY_END
};
Config::Config(QObject* parent)
: QObject(parent)
{
m_cmdLine = new QCommandLine(this);
// We will handle --help and --version ourselves in phantom.cpp
m_cmdLine->enableHelp(false);
m_cmdLine->enableVersion(false);
resetToDefaults();
}
void Config::init(const QStringList* const args)
{
resetToDefaults();
QByteArray envSslCertDir = qgetenv("SSL_CERT_DIR");
if (!envSslCertDir.isEmpty()) {
setSslCertificatesPath(envSslCertDir);
}
processArgs(*args);
}
void Config::processArgs(const QStringList& args)
{
connect(m_cmdLine, SIGNAL(switchFound(const QString&)), this, SLOT(handleSwitch(const QString&)));
connect(m_cmdLine, SIGNAL(optionFound(const QString&, const QVariant&)), this, SLOT(handleOption(const QString&, const QVariant&)));
connect(m_cmdLine, SIGNAL(paramFound(const QString&, const QVariant&)), this, SLOT(handleParam(const QString&, const QVariant&)));
connect(m_cmdLine, SIGNAL(parseError(const QString&)), this, SLOT(handleError(const QString&)));
m_cmdLine->setArguments(args);
m_cmdLine->setConfig(flags);
m_cmdLine->parse();
// Inject command line parameters to be picked up by GhostDriver
if (isWebdriverMode()) {
QStringList argsForGhostDriver;
m_scriptFile = "main.js"; //< launch script
argsForGhostDriver << QString("--ip=%1").arg(m_webdriverIp); //< "--ip=IP"
argsForGhostDriver << QString("--port=%1").arg(m_webdriverPort); //< "--port=PORT"
if (!m_webdriverSeleniumGridHub.isEmpty()) {
argsForGhostDriver << QString("--hub=%1").arg(m_webdriverSeleniumGridHub); //< "--hub=SELENIUM_GRID_HUB_URL"
}
if (!m_webdriverLogFile.isEmpty()) {
argsForGhostDriver << QString("--logFile=%1").arg(m_webdriverLogFile); //< "--logFile=LOG_FILE"
argsForGhostDriver << "--logColor=false"; //< Force no-color-output in Log File
}
argsForGhostDriver << QString("--logLevel=%1").arg(m_webdriverLogLevel); //< "--logLevel=LOG_LEVEL"
// Clear current args and override with those
setScriptArgs(argsForGhostDriver);
}
}
void Config::loadJsonFile(const QString& filePath)
{
QString jsonConfig;
QFile f(filePath);
// Check file exists and is readable
if (!f.exists() || !f.open(QFile::ReadOnly | QFile::Text)) {
Terminal::instance()->cerr("Unable to open config: \"" + filePath + "\"");
return;
}
// Read content
jsonConfig = QString::fromUtf8(f.readAll().trimmed());
f.close();
// Check it's a valid JSON format
if (jsonConfig.isEmpty() || !jsonConfig.startsWith('{') || !jsonConfig.endsWith('}')) {
Terminal::instance()->cerr("Config file MUST be in JSON format!");
return;
}
// Load configurator
QString configurator = Utils::readResourceFileUtf8(":/configurator.js");
// Use a temporary QWebPage to load the JSON configuration in this Object using the 'configurator' above
QWebPage webPage;
// Add this object to the global scope
webPage.mainFrame()->addToJavaScriptWindowObject("config", this);
// Apply the JSON config settings to this very object
webPage.mainFrame()->evaluateJavaScript(configurator.arg(jsonConfig));
}
QString Config::helpText() const
{
return m_cmdLine->help();
}
bool Config::autoLoadImages() const
{
return m_autoLoadImages;
}
void Config::setAutoLoadImages(const bool value)
{
m_autoLoadImages = value;
}
QString Config::cookiesFile() const
{
return m_cookiesFile;
}
void Config::setCookiesFile(const QString& value)
{
m_cookiesFile = value;
}
QString Config::offlineStoragePath() const
{
return m_offlineStoragePath;
}
void Config::setOfflineStoragePath(const QString& value)
{
QDir dir(value);
m_offlineStoragePath = dir.absolutePath();
}
int Config::offlineStorageDefaultQuota() const
{
return m_offlineStorageDefaultQuota;
}
void Config::setOfflineStorageDefaultQuota(int offlineStorageDefaultQuota)
{
m_offlineStorageDefaultQuota = offlineStorageDefaultQuota * 1024;
}
QString Config::localStoragePath() const
{
return m_localStoragePath;
}
void Config::setLocalStoragePath(const QString& value)
{
QDir dir(value);
m_localStoragePath = dir.absolutePath();
}
int Config::localStorageDefaultQuota() const
{
return m_localStorageDefaultQuota;
}
void Config::setLocalStorageDefaultQuota(int localStorageDefaultQuota)
{
m_localStorageDefaultQuota = localStorageDefaultQuota * 1024;
}
bool Config::diskCacheEnabled() const
{
return m_diskCacheEnabled;
}
void Config::setDiskCacheEnabled(const bool value)
{
m_diskCacheEnabled = value;
}
int Config::maxDiskCacheSize() const
{
return m_maxDiskCacheSize;
}
void Config::setMaxDiskCacheSize(int maxDiskCacheSize)
{
m_maxDiskCacheSize = maxDiskCacheSize;
}
QString Config::diskCachePath() const
{
return m_diskCachePath;
}
void Config::setDiskCachePath(const QString& value)
{
QDir dir(value);
m_diskCachePath = dir.absolutePath();
}
bool Config::ignoreSslErrors() const
{
return m_ignoreSslErrors;
}
void Config::setIgnoreSslErrors(const bool value)
{
m_ignoreSslErrors = value;
}
bool Config::localUrlAccessEnabled() const
{
return m_localUrlAccessEnabled;
}
void Config::setLocalUrlAccessEnabled(const bool value)
{
m_localUrlAccessEnabled = value;
}
bool Config::localToRemoteUrlAccessEnabled() const
{
return m_localToRemoteUrlAccessEnabled;
}
void Config::setLocalToRemoteUrlAccessEnabled(const bool value)
{
m_localToRemoteUrlAccessEnabled = value;
}
QString Config::outputEncoding() const
{
return m_outputEncoding;
}
void Config::setOutputEncoding(const QString& value)
{
if (value.isEmpty()) {
return;
}
m_outputEncoding = value;
}
QString Config::proxyType() const
{
return m_proxyType;
}
void Config::setProxyType(const QString& value)
{
m_proxyType = value;
}
QString Config::proxy() const
{
return m_proxyHost + ":" + QString::number(m_proxyPort);
}
void Config::setProxy(const QString& value)
{
QUrl proxyUrl = QUrl::fromUserInput(value);
if (proxyUrl.isValid()) {
setProxyHost(proxyUrl.host());
setProxyPort(proxyUrl.port(1080));
}
}
void Config::setProxyAuth(const QString& value)
{
QString proxyUser = value;
QString proxyPass = "";
if (proxyUser.lastIndexOf(':') > 0) {
proxyPass = proxyUser.mid(proxyUser.lastIndexOf(':') + 1).trimmed();
proxyUser = proxyUser.left(proxyUser.lastIndexOf(':')).trimmed();
setProxyAuthUser(proxyUser);
setProxyAuthPass(proxyPass);
}
}
QString Config::proxyAuth() const
{
return proxyAuthUser() + ":" + proxyAuthPass();
}
QString Config::proxyAuthUser() const
{
return m_proxyAuthUser;
}
QString Config::proxyAuthPass() const
{
return m_proxyAuthPass;
}
QString Config::proxyHost() const
{
return m_proxyHost;
}
int Config::proxyPort() const
{
return m_proxyPort;
}
QStringList Config::scriptArgs() const
{
return m_scriptArgs;
}
void Config::setScriptArgs(const QStringList& value)
{
m_scriptArgs.clear();
QStringListIterator it(value);
while (it.hasNext()) {
m_scriptArgs.append(it.next());
}
}
QString Config::scriptEncoding() const
{
return m_scriptEncoding;
}
void Config::setScriptEncoding(const QString& value)
{
if (value.isEmpty()) {
return;
}
m_scriptEncoding = value;
}
QString Config::scriptLanguage() const
{
return m_scriptLanguage;
}
void Config::setScriptLanguage(const QString& value)
{
if (value.isEmpty()) {
return;
}
m_scriptLanguage = value;
}
QString Config::scriptFile() const
{
return m_scriptFile;
}
void Config::setScriptFile(const QString& value)
{
m_scriptFile = value;
}
QString Config::unknownOption() const
{
return m_unknownOption;
}
void Config::setUnknownOption(const QString& value)
{
m_unknownOption = value;
}
bool Config::versionFlag() const
{
return m_versionFlag;
}
void Config::setVersionFlag(const bool value)
{
m_versionFlag = value;
}
bool Config::debug() const
{
return m_debug;
}
void Config::setDebug(const bool value)
{
m_debug = value;
}
int Config::remoteDebugPort() const
{
return m_remoteDebugPort;
}
void Config::setRemoteDebugPort(const int port)
{
m_remoteDebugPort = port;
}
bool Config::remoteDebugAutorun() const
{
return m_remoteDebugAutorun;
}
void Config::setRemoteDebugAutorun(const bool value)
{
m_remoteDebugAutorun = value;
}
bool Config::webSecurityEnabled() const
{
return m_webSecurityEnabled;
}
void Config::setWebSecurityEnabled(const bool value)
{
m_webSecurityEnabled = value;
}
void Config::setJavascriptCanOpenWindows(const bool value)
{
m_javascriptCanOpenWindows = value;
}
bool Config::javascriptCanOpenWindows() const
{
return m_javascriptCanOpenWindows;
}
void Config::setJavascriptCanCloseWindows(const bool value)
{
m_javascriptCanCloseWindows = value;
}
bool Config::javascriptCanCloseWindows() const
{
return m_javascriptCanCloseWindows;
}
void Config::setWebdriver(const QString& webdriverConfig)
{
// Parse and validate the configuration
bool isValidPort;
QStringList wdCfg = webdriverConfig.split(':');
if (wdCfg.length() == 1 && wdCfg[0].toInt(&isValidPort) && isValidPort) {
// Only a PORT was provided
m_webdriverPort = wdCfg[0];
} else if (wdCfg.length() == 2 && !wdCfg[0].isEmpty() && wdCfg[1].toInt(&isValidPort) && isValidPort) {
// Both IP and PORT provided
m_webdriverIp = wdCfg[0];
m_webdriverPort = wdCfg[1];
}
}
QString Config::webdriver() const
{
return QString("%1:%2").arg(m_webdriverIp).arg(m_webdriverPort);
}
bool Config::isWebdriverMode() const
{
return !m_webdriverPort.isEmpty();
}
void Config::setWebdriverLogFile(const QString& webdriverLogFile)
{
m_webdriverLogFile = webdriverLogFile;
}
QString Config::webdriverLogFile() const
{
return m_webdriverLogFile;
}
void Config::setWebdriverLogLevel(const QString& webdriverLogLevel)
{
m_webdriverLogLevel = webdriverLogLevel;
}
QString Config::webdriverLogLevel() const
{
return m_webdriverLogLevel;
}
void Config::setWebdriverSeleniumGridHub(const QString& hubUrl)
{
m_webdriverSeleniumGridHub = hubUrl;
}
QString Config::webdriverSeleniumGridHub() const
{
return m_webdriverSeleniumGridHub;
}
// private:
void Config::resetToDefaults()
{
m_autoLoadImages = true;
m_cookiesFile = QString();
m_offlineStoragePath = QString();
m_offlineStorageDefaultQuota = -1;
m_localStoragePath = QString();
m_localStorageDefaultQuota = -1;
m_diskCacheEnabled = false;
m_maxDiskCacheSize = -1;
m_diskCachePath = QString();
m_ignoreSslErrors = false;
m_localUrlAccessEnabled = true;
m_localToRemoteUrlAccessEnabled = false;
m_outputEncoding = "UTF-8";
m_proxyType = "http";
m_proxyHost.clear();
m_proxyPort = 1080;
m_proxyAuthUser.clear();
m_proxyAuthPass.clear();
m_scriptArgs.clear();
m_scriptEncoding = "UTF-8";
m_scriptLanguage.clear();
m_scriptFile.clear();
m_unknownOption.clear();
m_versionFlag = false;
m_debug = false;
m_remoteDebugPort = -1;
m_remoteDebugAutorun = false;
m_webSecurityEnabled = true;
m_javascriptCanOpenWindows = true;
m_javascriptCanCloseWindows = true;
m_helpFlag = false;
m_printDebugMessages = false;
m_sslProtocol = "default";
// Default taken from Chromium 35.0.1916.153
m_sslCiphers = ("ECDHE-ECDSA-AES128-GCM-SHA256"
":ECDHE-RSA-AES128-GCM-SHA256"
":DHE-RSA-AES128-GCM-SHA256"
":ECDHE-ECDSA-AES256-SHA"
":ECDHE-ECDSA-AES128-SHA"
":ECDHE-RSA-AES128-SHA"
":ECDHE-RSA-AES256-SHA"
":ECDHE-ECDSA-RC4-SHA"
":ECDHE-RSA-RC4-SHA"
":DHE-RSA-AES128-SHA"
":DHE-DSS-AES128-SHA"
":DHE-RSA-AES256-SHA"
":AES128-GCM-SHA256"
":AES128-SHA"
":AES256-SHA"
":DES-CBC3-SHA"
":RC4-SHA"
":RC4-MD5");
m_sslCertificatesPath.clear();
m_sslClientCertificateFile.clear();
m_sslClientKeyFile.clear();
m_sslClientKeyPassphrase.clear();
m_webdriverIp = QString();
m_webdriverPort = QString();
m_webdriverLogFile = QString();
m_webdriverLogLevel = "INFO";
m_webdriverSeleniumGridHub = QString();
}
void Config::setProxyAuthPass(const QString& value)
{
m_proxyAuthPass = value;
}
void Config::setProxyAuthUser(const QString& value)
{
m_proxyAuthUser = value;
}
void Config::setProxyHost(const QString& value)
{
m_proxyHost = value;
}
void Config::setProxyPort(const int value)
{
m_proxyPort = value;
}
bool Config::helpFlag() const
{
return m_helpFlag;
}
void Config::setHelpFlag(const bool value)
{
m_helpFlag = value;
}
bool Config::printDebugMessages() const
{
return m_printDebugMessages;
}
void Config::setPrintDebugMessages(const bool value)
{
m_printDebugMessages = value;
}
void Config::handleSwitch(const QString& sw)
{
setHelpFlag(sw == "help");
setVersionFlag(sw == "version");
if (sw == "wd") {
setWebdriver(DEFAULT_WEBDRIVER_CONFIG);
}
}
void Config::handleOption(const QString& option, const QVariant& value)
{
bool boolValue = false;
QStringList booleanFlags;
booleanFlags << "debug";
booleanFlags << "disk-cache";
booleanFlags << "ignore-ssl-errors";
booleanFlags << "load-images";
booleanFlags << "local-url-access";
booleanFlags << "local-to-remote-url-access";
booleanFlags << "remote-debugger-autorun";
booleanFlags << "web-security";
if (booleanFlags.contains(option)) {
if ((value != "true") && (value != "yes") && (value != "false") && (value != "no")) {
setUnknownOption(QString("Invalid values for '%1' option.").arg(option));
return;
}
boolValue = (value == "true") || (value == "yes");
}
if (option == "cookies-file") {
setCookiesFile(value.toString());
}
if (option == "config") {
loadJsonFile(value.toString());
}
if (option == "debug") {
setPrintDebugMessages(boolValue);
}
if (option == "disk-cache") {
setDiskCacheEnabled(boolValue);
}
if (option == "disk-cache-path") {
setDiskCachePath(value.toString());
}
if (option == "ignore-ssl-errors") {
setIgnoreSslErrors(boolValue);
}
if (option == "load-images") {
setAutoLoadImages(boolValue);
}
if (option == "local-storage-path") {
setLocalStoragePath(value.toString());
}
if (option == "local-storage-quota") {
setLocalStorageDefaultQuota(value.toInt());
}
if (option == "offline-storage-path") {
setOfflineStoragePath(value.toString());
}
if (option == "offline-storage-quota") {
setOfflineStorageDefaultQuota(value.toInt());
}
if (option == "local-url-access") {
setLocalUrlAccessEnabled(boolValue);
}
if (option == "local-to-remote-url-access") {
setLocalToRemoteUrlAccessEnabled(boolValue);
}
if (option == "max-disk-cache-size") {
setMaxDiskCacheSize(value.toInt());
}
if (option == "output-encoding") {
setOutputEncoding(value.toString());
}
if (option == "remote-debugger-autorun") {
setRemoteDebugAutorun(boolValue);
}
if (option == "remote-debugger-port") {
setDebug(true);
setRemoteDebugPort(value.toInt());
}
if (option == "proxy") {
setProxy(value.toString());
}
if (option == "proxy-type") {
setProxyType(value.toString());
}
if (option == "proxy-auth") {
setProxyAuth(value.toString());
}
if (option == "script-encoding") {
setScriptEncoding(value.toString());
}
if (option == "script-language") {
setScriptLanguage(value.toString());
}
if (option == "web-security") {
setWebSecurityEnabled(boolValue);
}
if (option == "ssl-protocol") {
setSslProtocol(value.toString());
}
if (option == "ssl-ciphers") {
setSslCiphers(value.toString());
}
if (option == "ssl-certificates-path") {
setSslCertificatesPath(value.toString());
}
if (option == "ssl-client-certificate-file") {
setSslClientCertificateFile(value.toString());
}
if (option == "ssl-client-key-file") {
setSslClientKeyFile(value.toString());
}
if (option == "ssl-client-key-passphrase") {
setSslClientKeyPassphrase(value.toByteArray());
}
if (option == "webdriver") {
setWebdriver(value.toString().length() > 0 ? value.toString() : DEFAULT_WEBDRIVER_CONFIG);
}
if (option == "webdriver-logfile") {
setWebdriverLogFile(value.toString());
}
if (option == "webdriver-loglevel") {
setWebdriverLogLevel(value.toString());
}
if (option == "webdriver-selenium-grid-hub") {
setWebdriverSeleniumGridHub(value.toString());
}
}
void Config::handleParam(const QString& param, const QVariant& value)
{
Q_UNUSED(param);
if (m_scriptFile.isEmpty()) {
m_scriptFile = value.toString();
} else {
m_scriptArgs += value.toString();
}
}
void Config::handleError(const QString& error)
{
setUnknownOption(QString("Error: %1").arg(error));
}
QString Config::sslProtocol() const
{
return m_sslProtocol;
}
void Config::setSslProtocol(const QString& sslProtocolName)
{
m_sslProtocol = sslProtocolName.toLower();
}
QString Config::sslCiphers() const
{
return m_sslCiphers;
}
void Config::setSslCiphers(const QString& sslCiphersName)
{
// OpenSSL cipher strings are case sensitive.
m_sslCiphers = sslCiphersName;
}
QString Config::sslCertificatesPath() const
{
return m_sslCertificatesPath;
}
void Config::setSslCertificatesPath(const QString& sslCertificatesPath)
{
QFileInfo sslPathInfo = QFileInfo(sslCertificatesPath);
if (sslPathInfo.isDir()) {
if (sslCertificatesPath.endsWith('/')) {
m_sslCertificatesPath = sslCertificatesPath + "*";
} else {
m_sslCertificatesPath = sslCertificatesPath + "/*";
}
} else {
m_sslCertificatesPath = sslCertificatesPath;
}
}
QString Config::sslClientCertificateFile() const
{
return m_sslClientCertificateFile;
}
void Config::setSslClientCertificateFile(const QString& sslClientCertificateFile)
{
m_sslClientCertificateFile = sslClientCertificateFile;
}
QString Config::sslClientKeyFile() const
{
return m_sslClientKeyFile;
}
void Config::setSslClientKeyFile(const QString& sslClientKeyFile)
{
m_sslClientKeyFile = sslClientKeyFile;
}
QByteArray Config::sslClientKeyPassphrase() const
{
return m_sslClientKeyPassphrase;
}
void Config::setSslClientKeyPassphrase(const QByteArray& sslClientKeyPassphrase)
{
m_sslClientKeyPassphrase = sslClientKeyPassphrase;
}

272
src/config.h Normal file
View File

@ -0,0 +1,272 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 execjosh, http://execjosh.blogspot.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <QString>
#include <QStringList>
#include <QNetworkProxy>
#include <QVariant>
class QCommandLine;
class Config: public QObject
{
Q_OBJECT
Q_PROPERTY(QString cookiesFile READ cookiesFile WRITE setCookiesFile)
Q_PROPERTY(bool diskCacheEnabled READ diskCacheEnabled WRITE setDiskCacheEnabled)
Q_PROPERTY(int maxDiskCacheSize READ maxDiskCacheSize WRITE setMaxDiskCacheSize)
Q_PROPERTY(QString diskCachePath READ diskCachePath WRITE setDiskCachePath)
Q_PROPERTY(bool ignoreSslErrors READ ignoreSslErrors WRITE setIgnoreSslErrors)
Q_PROPERTY(bool localUrlAccessEnabled READ localUrlAccessEnabled WRITE setLocalUrlAccessEnabled)
Q_PROPERTY(bool localToRemoteUrlAccessEnabled READ localToRemoteUrlAccessEnabled WRITE setLocalToRemoteUrlAccessEnabled)
Q_PROPERTY(QString outputEncoding READ outputEncoding WRITE setOutputEncoding)
Q_PROPERTY(QString proxyType READ proxyType WRITE setProxyType)
Q_PROPERTY(QString proxy READ proxy WRITE setProxy)
Q_PROPERTY(QString proxyAuth READ proxyAuth WRITE setProxyAuth)
Q_PROPERTY(QString scriptEncoding READ scriptEncoding WRITE setScriptEncoding)
Q_PROPERTY(bool webSecurityEnabled READ webSecurityEnabled WRITE setWebSecurityEnabled)
Q_PROPERTY(QString offlineStoragePath READ offlineStoragePath WRITE setOfflineStoragePath)
Q_PROPERTY(QString localStoragePath READ localStoragePath WRITE setLocalStoragePath)
Q_PROPERTY(int localStorageDefaultQuota READ localStorageDefaultQuota WRITE setLocalStorageDefaultQuota)
Q_PROPERTY(int offlineStorageDefaultQuota READ offlineStorageDefaultQuota WRITE setOfflineStorageDefaultQuota)
Q_PROPERTY(bool printDebugMessages READ printDebugMessages WRITE setPrintDebugMessages)
Q_PROPERTY(bool javascriptCanOpenWindows READ javascriptCanOpenWindows WRITE setJavascriptCanOpenWindows)
Q_PROPERTY(bool javascriptCanCloseWindows READ javascriptCanCloseWindows WRITE setJavascriptCanCloseWindows)
Q_PROPERTY(QString sslProtocol READ sslProtocol WRITE setSslProtocol)
Q_PROPERTY(QString sslCiphers READ sslCiphers WRITE setSslCiphers)
Q_PROPERTY(QString sslCertificatesPath READ sslCertificatesPath WRITE setSslCertificatesPath)
Q_PROPERTY(QString sslClientCertificateFile READ sslClientCertificateFile WRITE setSslClientCertificateFile)
Q_PROPERTY(QString sslClientKeyFile READ sslClientKeyFile WRITE setSslClientKeyFile)
Q_PROPERTY(QByteArray sslClientKeyPassphrase READ sslClientKeyPassphrase WRITE setSslClientKeyPassphrase)
Q_PROPERTY(QString webdriver READ webdriver WRITE setWebdriver)
Q_PROPERTY(QString webdriverLogFile READ webdriverLogFile WRITE setWebdriverLogFile)
Q_PROPERTY(QString webdriverLogLevel READ webdriverLogLevel WRITE setWebdriverLogLevel)
Q_PROPERTY(QString webdriverSeleniumGridHub READ webdriverSeleniumGridHub WRITE setWebdriverSeleniumGridHub)
public:
Config(QObject* parent = 0);
void init(const QStringList* const args);
void processArgs(const QStringList& args);
void loadJsonFile(const QString& filePath);
QString helpText() const;
bool autoLoadImages() const;
void setAutoLoadImages(const bool value);
QString cookiesFile() const;
void setCookiesFile(const QString& cookiesFile);
QString offlineStoragePath() const;
void setOfflineStoragePath(const QString& value);
int offlineStorageDefaultQuota() const;
void setOfflineStorageDefaultQuota(int offlineStorageDefaultQuota);
QString localStoragePath() const;
void setLocalStoragePath(const QString& value);
int localStorageDefaultQuota() const;
void setLocalStorageDefaultQuota(int localStorageDefaultQuota);
bool diskCacheEnabled() const;
void setDiskCacheEnabled(const bool value);
int maxDiskCacheSize() const;
void setMaxDiskCacheSize(int maxDiskCacheSize);
QString diskCachePath() const;
void setDiskCachePath(const QString& value);
bool ignoreSslErrors() const;
void setIgnoreSslErrors(const bool value);
bool localUrlAccessEnabled() const;
void setLocalUrlAccessEnabled(const bool value);
bool localToRemoteUrlAccessEnabled() const;
void setLocalToRemoteUrlAccessEnabled(const bool value);
QString outputEncoding() const;
void setOutputEncoding(const QString& value);
QString proxyType() const;
void setProxyType(const QString& value);
QString proxy() const;
void setProxy(const QString& value);
QString proxyHost() const;
int proxyPort() const;
QString proxyAuth() const;
void setProxyAuth(const QString& value);
QString proxyAuthUser() const;
QString proxyAuthPass() const;
void setProxyAuthUser(const QString& value);
void setProxyAuthPass(const QString& value);
QStringList scriptArgs() const;
void setScriptArgs(const QStringList& value);
QString scriptEncoding() const;
void setScriptEncoding(const QString& value);
QString scriptLanguage() const;
void setScriptLanguage(const QString& value);
QString scriptFile() const;
void setScriptFile(const QString& value);
QString unknownOption() const;
void setUnknownOption(const QString& value);
bool versionFlag() const;
void setVersionFlag(const bool value);
void setDebug(const bool value);
bool debug() const;
void setRemoteDebugPort(const int port);
int remoteDebugPort() const;
void setRemoteDebugAutorun(const bool value);
bool remoteDebugAutorun() const;
bool webSecurityEnabled() const;
void setWebSecurityEnabled(const bool value);
bool helpFlag() const;
void setHelpFlag(const bool value);
void setPrintDebugMessages(const bool value);
bool printDebugMessages() const;
void setJavascriptCanOpenWindows(const bool value);
bool javascriptCanOpenWindows() const;
void setJavascriptCanCloseWindows(const bool value);
bool javascriptCanCloseWindows() const;
void setSslProtocol(const QString& sslProtocolName);
QString sslProtocol() const;
void setSslCiphers(const QString& sslCiphersName);
QString sslCiphers() const;
void setSslCertificatesPath(const QString& sslCertificatesPath);
QString sslCertificatesPath() const;
void setSslClientCertificateFile(const QString& sslClientCertificateFile);
QString sslClientCertificateFile() const;
void setSslClientKeyFile(const QString& sslClientKeyFile);
QString sslClientKeyFile() const;
void setSslClientKeyPassphrase(const QByteArray& sslClientKeyPassphrase);
QByteArray sslClientKeyPassphrase() const;
void setWebdriver(const QString& webdriverConfig);
QString webdriver() const;
bool isWebdriverMode() const;
void setWebdriverLogFile(const QString& webdriverLogFile);
QString webdriverLogFile() const;
void setWebdriverLogLevel(const QString& webdriverLogLevel);
QString webdriverLogLevel() const;
void setWebdriverSeleniumGridHub(const QString& hubUrl);
QString webdriverSeleniumGridHub() const;
public slots:
void handleSwitch(const QString& sw);
void handleOption(const QString& option, const QVariant& value);
void handleParam(const QString& param, const QVariant& value);
void handleError(const QString& error);
private:
void resetToDefaults();
void setProxyHost(const QString& value);
void setProxyPort(const int value);
void setAuthUser(const QString& value);
void setAuthPass(const QString& value);
QCommandLine* m_cmdLine;
bool m_autoLoadImages;
QString m_cookiesFile;
QString m_offlineStoragePath;
int m_offlineStorageDefaultQuota;
QString m_localStoragePath;
int m_localStorageDefaultQuota;
bool m_diskCacheEnabled;
int m_maxDiskCacheSize;
QString m_diskCachePath;
bool m_ignoreSslErrors;
bool m_localUrlAccessEnabled;
bool m_localToRemoteUrlAccessEnabled;
QString m_outputEncoding;
QString m_proxyType;
QString m_proxyHost;
int m_proxyPort;
QString m_proxyAuthUser;
QString m_proxyAuthPass;
QStringList m_scriptArgs;
QString m_scriptEncoding;
QString m_scriptLanguage;
QString m_scriptFile;
QString m_unknownOption;
bool m_versionFlag;
QString m_authUser;
QString m_authPass;
bool m_debug;
int m_remoteDebugPort;
bool m_remoteDebugAutorun;
bool m_webSecurityEnabled;
bool m_helpFlag;
bool m_printDebugMessages;
bool m_javascriptCanOpenWindows;
bool m_javascriptCanCloseWindows;
QString m_sslProtocol;
QString m_sslCiphers;
QString m_sslCertificatesPath;
QString m_sslClientCertificateFile;
QString m_sslClientKeyFile;
QByteArray m_sslClientKeyPassphrase;
QString m_webdriverIp;
QString m_webdriverPort;
QString m_webdriverLogFile;
QString m_webdriverLogLevel;
QString m_webdriverSeleniumGridHub;
};
#endif // CONFIG_H

17
src/configurator.js Normal file
View File

@ -0,0 +1,17 @@
(function (opts) {
var i;
opts = opts || {};
if (typeof opts !== 'object') {
return;
}
for (i in opts) {
if (opts.hasOwnProperty(i) && typeof opts[i] !== 'undefined') {
config[i] = opts[i];
}
}
return null;
})((%1));

72
src/consts.h Normal file
View File

@ -0,0 +1,72 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.com>
Copyright (C) 2011 James Roe <roejames12@hotmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CONSTS_H
#define CONSTS_H
#define PHANTOMJS_VERSION_MAJOR 2
#define PHANTOMJS_VERSION_MINOR 1
#define PHANTOMJS_VERSION_PATCH 1
#define PHANTOMJS_VERSION_STRING "2.1.1"
#define HTTP_HEADER_CONTENT_LENGTH "content-length"
#define HTTP_HEADER_CONTENT_TYPE "content-type"
#define JAVASCRIPT_SOURCE_PLATFORM_URL "phantomjs://platform/%1"
#define JAVASCRIPT_SOURCE_CODE_URL "phantomjs://code/%1"
#define JS_ELEMENT_CLICK "(function (el) { " \
"var ev = document.createEvent('MouseEvents');" \
"ev.initEvent(\"click\", true, true);" \
"el.dispatchEvent(ev);" \
"})(this);"
#define JS_APPEND_SCRIPT_ELEMENT "var el = document.createElement('script');" \
"el.onload = function() { alert('%1'); };" \
"el.src = '%1';" \
"document.body.appendChild(el);"
#define PAGE_SETTINGS_LOAD_IMAGES "loadImages"
#define PAGE_SETTINGS_JS_ENABLED "javascriptEnabled"
#define PAGE_SETTINGS_XSS_AUDITING "XSSAuditingEnabled"
#define PAGE_SETTINGS_USER_AGENT "userAgent"
#define PAGE_SETTINGS_PROXY "proxy"
#define PAGE_SETTINGS_LOCAL_ACCESS_REMOTE "localToRemoteUrlAccessEnabled"
#define PAGE_SETTINGS_USERNAME "userName"
#define PAGE_SETTINGS_PASSWORD "password"
#define PAGE_SETTINGS_MAX_AUTH_ATTEMPTS "maxAuthAttempts"
#define PAGE_SETTINGS_RESOURCE_TIMEOUT "resourceTimeout"
#define PAGE_SETTINGS_JS_CAN_OPEN_WINDOWS "javascriptCanOpenWindows"
#define PAGE_SETTINGS_JS_CAN_CLOSE_WINDOWS "javascriptCanCloseWindows"
#define DEFAULT_WEBDRIVER_CONFIG "127.0.0.1:8910"
#endif // CONSTS_H

505
src/cookiejar.cpp Normal file
View File

@ -0,0 +1,505 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2012 Ivan De Marino <ivan.de.marino@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "phantom.h"
#include "config.h"
#include "cookiejar.h"
#include <QDateTime>
#include <QDataStream>
#include <QSettings>
#include <QTimer>
#define COOKIE_JAR_VERSION 1
// Operators needed for Cookie Serialization
QT_BEGIN_NAMESPACE
QDataStream& operator<<(QDataStream& stream, const QList<QNetworkCookie>& list)
{
stream << COOKIE_JAR_VERSION;
stream << quint32(list.size());
for (int i = 0; i < list.size(); ++i) {
stream << list.at(i).toRawForm();
}
return stream;
}
QDataStream& operator>>(QDataStream& stream, QList<QNetworkCookie>& list)
{
list.clear();
quint32 version;
stream >> version;
if (version != COOKIE_JAR_VERSION) {
return stream;
}
quint32 count;
stream >> count;
for (quint32 i = 0; i < count; ++i) {
QByteArray value;
stream >> value;
QList<QNetworkCookie> newCookies = QNetworkCookie::parseCookies(value);
if (newCookies.count() == 0 && value.length() != 0) {
qWarning() << "CookieJar: Unable to parse saved cookie:" << value;
}
for (int j = 0; j < newCookies.count(); ++j) {
list.append(newCookies.at(j));
}
if (stream.atEnd()) {
break;
}
}
return stream;
}
QT_END_NAMESPACE
// public:
CookieJar::CookieJar(QString cookiesFile, QObject* parent)
: QNetworkCookieJar(parent)
, m_enabled(true)
{
if (cookiesFile == "") {
m_cookieStorage = 0;
qDebug() << "CookieJar - Created but will not store cookies (use option '--cookies-file=<filename>' to enable persistent cookie storage)";
} else {
m_cookieStorage = new QSettings(cookiesFile, QSettings::IniFormat, this);
load();
qDebug() << "CookieJar - Created and will store cookies in:" << cookiesFile;
}
}
// private:
CookieJar::~CookieJar()
{
// On destruction, before saving, clear all the session cookies
purgeSessionCookies();
save();
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie>& cookieList, const QUrl& url)
{
// Update cookies in memory
if (isEnabled()) {
QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
save();
}
// No changes occurred
return false;
}
QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl& url) const
{
if (isEnabled()) {
return QNetworkCookieJar::cookiesForUrl(url);
}
// The CookieJar is disabled: don't return any cookie
return QList<QNetworkCookie>();
}
bool CookieJar::addCookie(const QNetworkCookie& cookie, const QString& url)
{
if (isEnabled() && (!url.isEmpty() || !cookie.domain().isEmpty())) {
// Save a single cookie
setCookiesFromUrl(
QList<QNetworkCookie>() << cookie, //< unfortunately, "setCookiesFromUrl" requires a list
!url.isEmpty() ?
url : //< use given URL
QString( //< mock-up a URL
(cookie.isSecure() ? "https://" : "http://") + //< URL protocol
QString(cookie.domain().startsWith('.') ? "www" : "") + cookie.domain() + //< URL domain
(cookie.path().isEmpty() ? "/" : cookie.path()))); //< URL path
// Return "true" if the cookie was really set
if (contains(cookie)) {
return true;
}
qDebug() << "CookieJar - Rejected Cookie" << cookie.toRawForm();
return false;
}
return false;
}
void CookieJar::addCookie(const QVariantMap& cookie)
{
addCookieFromMap(cookie);
}
bool CookieJar::addCookieFromMap(const QVariantMap& cookie, const QString& url)
{
QNetworkCookie newCookie;
// The cookie must have a non-empty "name" and a "value"
if (!cookie["name"].isNull() && !cookie["name"].toString().isEmpty() && !cookie["value"].isNull()) {
// Name & Value
newCookie.setName(cookie["name"].toByteArray());
newCookie.setValue(cookie["value"].toByteArray());
// Domain, if provided
if (!cookie["domain"].isNull() && !cookie["domain"].toString().isEmpty()) {
newCookie.setDomain(cookie["domain"].toString());
}
// Path, if provided
if (!cookie["path"].isNull() || !cookie["path"].toString().isEmpty()) {
newCookie.setPath(cookie["path"].toString());
}
// HttpOnly, false by default
newCookie.setHttpOnly(cookie["httponly"].isNull() ? false : cookie["httponly"].toBool());
// Secure, false by default
newCookie.setSecure(cookie["secure"].isNull() ? false : cookie["secure"].toBool());
// Expiration Date, if provided, giving priority to "expires" over "expiry"
QVariant expiresVar;
if (!cookie["expires"].isNull()) {
expiresVar = cookie["expires"];
} else if (!cookie["expiry"].isNull()) {
expiresVar = cookie["expiry"];
}
if (expiresVar.isValid()) {
QDateTime expirationDate;
if (expiresVar.type() == QVariant::String) {
// Set cookie expire date via "classic" string format
QString datetime = expiresVar.toString().replace(" GMT", "");
expirationDate = QDateTime::fromString(datetime, "ddd, dd MMM yyyy hh:mm:ss");
} else if (expiresVar.type() == QVariant::Double) {
// Set cookie expire date via "number of seconds since epoch"
// NOTE: Every JS number is a Double.
// @see http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
expirationDate = QDateTime::fromMSecsSinceEpoch(expiresVar.toLongLong());
}
if (expirationDate.isValid()) {
newCookie.setExpirationDate(expirationDate);
}
}
return addCookie(newCookie, url);
}
return false;
}
bool CookieJar::addCookies(const QList<QNetworkCookie>& cookiesList, const QString& url)
{
bool added = false;
for (int i = cookiesList.length() - 1; i >= 0; --i) {
if (addCookie(cookiesList.at(i), url)) {
// change it to "true" if at least 1 cookie was set
added = true;
}
}
return added;
}
bool CookieJar::addCookiesFromMap(const QVariantList& cookiesList, const QString& url)
{
bool added = false;
for (int i = cookiesList.length() - 1; i >= 0; --i) {
if (addCookieFromMap(cookiesList.at(i).toMap(), url)) {
// change it to "true" if at least 1 cookie was set
added = true;
}
}
return added;
}
QList<QNetworkCookie> CookieJar::cookies(const QString& url) const
{
if (url.isEmpty()) {
// No url provided: return all the cookies in this CookieJar
return allCookies();
} else {
// Return ONLY the cookies that match this URL
return cookiesForUrl(url);
}
}
QVariantList CookieJar::cookiesToMap(const QString& url) const
{
QVariantList result;
QNetworkCookie c;
QVariantMap cookie;
QList<QNetworkCookie> cookiesList = cookies(url);
for (int i = cookiesList.length() - 1; i >= 0; --i) {
c = cookiesList.at(i);
cookie.clear();
cookie["domain"] = QVariant(c.domain());
cookie["name"] = QVariant(QString(c.name()));
cookie["value"] = QVariant(QString(c.value()));
cookie["path"] = (c.path().isNull() || c.path().isEmpty()) ? QVariant("/") : QVariant(c.path());
cookie["httponly"] = QVariant(c.isHttpOnly());
cookie["secure"] = QVariant(c.isSecure());
if (c.expirationDate().isValid()) {
cookie["expires"] = QVariant(QString(c.expirationDate().toString("ddd, dd MMM yyyy hh:mm:ss")).append(" GMT"));
cookie["expiry"] = QVariant(c.expirationDate().toMSecsSinceEpoch() / 1000);
}
result.append(cookie);
}
return result;
}
QNetworkCookie CookieJar::cookie(const QString& name, const QString& url) const
{
QList<QNetworkCookie> cookiesList = cookies(url);
for (int i = cookiesList.length() - 1; i >= 0; --i) {
if (cookiesList.at(i).name() == name) {
return cookiesList.at(i);
}
}
return QNetworkCookie();
}
QVariantMap CookieJar::cookieToMap(const QString& name, const QString& url) const
{
QVariantMap cookie;
QVariantList cookiesList = cookiesToMap(url);
for (int i = cookiesList.length() - 1; i >= 0; --i) {
cookie = cookiesList.at(i).toMap();
if (cookie["name"].toString() == name) {
return cookie;
}
}
return QVariantMap();
}
bool CookieJar::deleteCookie(const QString& name, const QString& url)
{
bool deleted = false;
if (isEnabled()) {
// NOTE: This code has been written in an "extended form" to make it
// easy to understand. Surely this could be "shrinked", but it
// would probably look uglier.
QList<QNetworkCookie> cookiesListAll;
if (url.isEmpty()) {
if (name.isEmpty()) { //< Neither "name" or "url" provided
// This method has been used wrong:
// "redirecting" to the right method for the job
clearCookies();
} else { //< Only "name" provided
// Delete all cookies with the given name from the CookieJar
cookiesListAll = allCookies();
for (int i = cookiesListAll.length() - 1; i >= 0; --i) {
if (cookiesListAll.at(i).name() == name) {
// Remove this cookie
qDebug() << "CookieJar - Deleted" << cookiesListAll.at(i).toRawForm();
cookiesListAll.removeAt(i);
deleted = true;
}
}
}
} else {
// Delete cookie(s) from the ones visible to the given "url".
// Use the "name" to delete only the right one, otherwise all of them.
QList<QNetworkCookie> cookiesListUrl = cookies(url);
cookiesListAll = allCookies();
for (int i = cookiesListAll.length() - 1; i >= 0; --i) {
if (cookiesListUrl.contains(cookiesListAll.at(i)) && //< if it part of the set of cookies visible at URL
(cookiesListAll.at(i).name() == name || name.isEmpty())) { //< and if the name matches, or no name provided
// Remove this cookie
qDebug() << "CookieJar - Deleted" << cookiesListAll.at(i).toRawForm();
cookiesListAll.removeAt(i);
deleted = true;
if (!name.isEmpty()) {
// Only one cookie was supposed to be deleted: we are done here!
break;
}
}
}
}
// Put back the remaining cookies
setAllCookies(cookiesListAll);
}
return deleted;
}
bool CookieJar::deleteCookies(const QString& url)
{
if (isEnabled()) {
if (url.isEmpty()) {
// No URL provided: delete ALL the cookies in the CookieJar
clearCookies();
return true;
}
// No cookie name provided: delete all the cookies visible by this URL
qDebug() << "Delete all cookies for URL:" << url;
return deleteCookie("", url);
}
return false;
}
void CookieJar::clearCookies()
{
if (isEnabled()) {
setAllCookies(QList<QNetworkCookie>());
}
}
void CookieJar::enable()
{
m_enabled = true;
}
void CookieJar::disable()
{
m_enabled = false;
}
bool CookieJar::isEnabled() const
{
return m_enabled;
}
void CookieJar::close()
{
deleteLater();
}
// private:
bool CookieJar::purgeExpiredCookies()
{
QList<QNetworkCookie> cookiesList = allCookies();
// If empty, there is nothing to purge
if (cookiesList.isEmpty()) {
return false;
}
// Check if any cookie has expired
int prePurgeCookiesCount = cookiesList.count();
QDateTime now = QDateTime::currentDateTime();
for (int i = cookiesList.count() - 1; i >= 0; --i) {
if (!cookiesList.at(i).isSessionCookie() && cookiesList.at(i).expirationDate() < now) {
qDebug() << "CookieJar - Purged (expired)" << cookiesList.at(i).toRawForm();
cookiesList.removeAt(i);
}
}
// Set cookies and returns "true" if at least 1 cookie expired and has been removed
if (prePurgeCookiesCount != cookiesList.count()) {
setAllCookies(cookiesList);
return true;
}
return false;
}
bool CookieJar::purgeSessionCookies()
{
QList<QNetworkCookie> cookiesList = allCookies();
// If empty, there is nothing to purge
if (cookiesList.isEmpty()) {
return false;
}
// Check if any cookie has expired
int prePurgeCookiesCount = cookiesList.count();
for (int i = cookiesList.count() - 1; i >= 0; --i) {
if (cookiesList.at(i).isSessionCookie()) {
qDebug() << "CookieJar - Purged (session)" << cookiesList.at(i).toRawForm();
cookiesList.removeAt(i);
}
}
// Set cookies and returns "true" if at least 1 session cookie was found and removed
if (prePurgeCookiesCount != cookiesList.count()) {
setAllCookies(cookiesList);
return true;
}
return false;
}
void CookieJar::save()
{
if (isEnabled()) {
// Get rid of all the Cookies that have expired
purgeExpiredCookies();
#ifndef QT_NO_DEBUG_OUTPUT
foreach(QNetworkCookie cookie, allCookies()) {
qDebug() << "CookieJar - Saved" << cookie.toRawForm();
}
#endif
// Store cookies
if (m_cookieStorage) {
m_cookieStorage->setValue(QLatin1String("cookies"), QVariant::fromValue<QList<QNetworkCookie> >(allCookies()));
}
}
}
void CookieJar::load()
{
if (isEnabled()) {
// Register a "StreamOperator" for this Meta Type, so we can easily serialize/deserialize the cookies
qRegisterMetaTypeStreamOperators<QList<QNetworkCookie> >("QList<QNetworkCookie>");
// Load all the cookies
if (m_cookieStorage) {
setAllCookies(qvariant_cast<QList<QNetworkCookie> >(m_cookieStorage->value(QLatin1String("cookies"))));
}
// If any cookie has expired since last execution, purge and save before going any further
if (purgeExpiredCookies()) {
save();
}
#ifndef QT_NO_DEBUG_OUTPUT
foreach(QNetworkCookie cookie, allCookies()) {
qDebug() << "CookieJar - Loaded" << cookie.toRawForm();
}
#endif
}
}
bool CookieJar::contains(const QNetworkCookie& cookieToFind) const
{
QList<QNetworkCookie> cookiesList = allCookies();
foreach(QNetworkCookie cookie, cookiesList) {
if (cookieToFind == cookie) {
return true;
}
}
return false;
}

91
src/cookiejar.h Normal file
View File

@ -0,0 +1,91 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2012 Ivan De Marino <ivan.de.marino@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef COOKIEJAR_H
#define COOKIEJAR_H
#include <QSettings>
#include <QNetworkCookie>
#include <QNetworkCookieJar>
#include <QVariantList>
#include <QVariantMap>
class CookieJar: public QNetworkCookieJar
{
Q_OBJECT
Q_PROPERTY(QVariantList cookies READ cookiesToMap WRITE addCookiesFromMap)
public:
CookieJar(QString cookiesFile, QObject* parent = NULL);
virtual ~CookieJar();
bool setCookiesFromUrl(const QList<QNetworkCookie>& cookieList, const QUrl& url);
QList<QNetworkCookie> cookiesForUrl(const QUrl& url) const;
bool addCookie(const QNetworkCookie& cookie, const QString& url = QString());
bool addCookies(const QList<QNetworkCookie>& cookiesList, const QString& url = QString());
QList<QNetworkCookie> cookies(const QString& url = QString()) const;
QNetworkCookie cookie(const QString& name, const QString& url = QString()) const;
using QNetworkCookieJar::deleteCookie;
bool deleteCookies(const QString& url = QString());
void enable();
void disable();
bool isEnabled() const;
public slots:
void addCookie(const QVariantMap& cookie);
bool addCookieFromMap(const QVariantMap& cookie, const QString& url = QString());
bool addCookiesFromMap(const QVariantList& cookiesList, const QString& url = QString());
QVariantList cookiesToMap(const QString& url = QString()) const;
QVariantMap cookieToMap(const QString& name, const QString& url = QString()) const;
bool deleteCookie(const QString& name, const QString& url = QString());
void clearCookies();
void close();
private slots:
bool purgeExpiredCookies();
bool purgeSessionCookies();
void save();
void load();
private:
bool contains(const QNetworkCookie& cookie) const;
private:
QSettings* m_cookieStorage;
bool m_enabled;
};
#endif // COOKIEJAR_H

Some files were not shown because too many files have changed in this diff Show More