Adding the ability to switch between frames.

1. Passing JavaScript eval and other related actions to the CurrentFrame, not the MainFrame.
2. Added different methods to navigate between frames
3. With a call to "window.frames[0].focus()", the "currentFrame" changes: commands after that are sent to the new frame under focus.
4. The navigation between frames allows to walk over the "tree of frames" contained in the page.

This commit also adds examples (both in JS and CoffeeScript) and Unit Test.

http://code.google.com/p/phantomjs/issues/detail?id=573
1.6
Ivan De Marino 2012-05-30 13:28:20 +02:00 committed by Ariya Hidayat
parent 40fd210c99
commit f386f7d484
15 changed files with 420 additions and 9 deletions

View File

@ -0,0 +1,66 @@
pageTitle = (page) ->
page.evaluate ->
window.document.title
setPageTitle = (page, newTitle) ->
page.evaluate ((newTitle) ->
window.document.title = newTitle
), newTitle
p = require("webpage").create()
p.open "../test/webpage-spec-frames/index.html", (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()

View File

@ -0,0 +1,73 @@
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();
});

View File

@ -112,7 +112,7 @@ phantom.__defineErrorSetter__(phantom, phantom.page);
phantom.defaultErrorHandler = function(error) {
console.log(error + "\n");
if (error.stack) {
if (error && error.stack) {
error.stack.forEach(function(item) {
var message = item.sourceURL + ":" + item.line;
if (item.function)

View File

@ -74,6 +74,7 @@ void Utils::messageHandler(QtMsgType type, const char *msg)
bool Utils::exceptionHandler(const char* dump_path, const char* minidump_id, void* context, bool succeeded)
{
Q_UNUSED(context);
fprintf(stderr, "PhantomJS has crashed. Please file a bug report at " \
"https://code.google.com/p/phantomjs/issues/entry and " \
"attach the crash dump file: %s/%s.dmp\n",

View File

@ -131,6 +131,9 @@ protected:
}
void javaScriptError(const QString &message, int lineNumber, const QString &sourceID) {
Q_UNUSED(message);
Q_UNUSED(lineNumber);
Q_UNUSED(sourceID);
m_webPage->emitError();
}
@ -250,7 +253,7 @@ WebPage::WebPage(QObject *parent, const Config *config, const QUrl &baseUrl)
m_mainFrame = m_webPage->mainFrame();
m_mainFrame->setHtml(BLANK_HTML, baseUrl);
connect(m_mainFrame, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(registerCallbacksHolder()));
connect(m_mainFrame, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(handleJavaScriptWindowObjectCleared()));
connect(m_mainFrame, SIGNAL(javaScriptWindowObjectCleared()), SIGNAL(initialized()));
connect(m_mainFrame, SIGNAL(urlChanged(QUrl)), SIGNAL(urlChanged(QUrl)));
connect(m_webPage, SIGNAL(loadStarted()), SIGNAL(loadStarted()), Qt::QueuedConnection);
@ -451,7 +454,9 @@ QVariantMap WebPage::paperSize() const
QVariant WebPage::evaluateJavaScript(const QString &code)
{
QString function = "(" + code + ")()";
return m_mainFrame->evaluateJavaScript(function, QString("phantomjs://webpage.evaluate()"));
return m_webPage->currentFrame()->evaluateJavaScript(
function,
QString("phantomjs://webpage.evaluate()"));
}
void WebPage::emitAlert(const QString &msg)
@ -877,7 +882,7 @@ QString WebPage::footer(int page, int numPages)
void WebPage::uploadFile(const QString &selector, const QString &fileName)
{
QWebElement el = m_mainFrame->findFirstElement(selector);
QWebElement el = m_webPage->currentFrame()->findFirstElement(selector);
if (el.isNull())
return;
@ -886,11 +891,11 @@ void WebPage::uploadFile(const QString &selector, const QString &fileName)
}
bool WebPage::injectJs(const QString &jsFilePath) {
return Utils::injectJsInFrame(jsFilePath, m_libraryPath, m_mainFrame);
return Utils::injectJsInFrame(jsFilePath, m_libraryPath, m_webPage->currentFrame());
}
void WebPage::_appendScriptElement(const QString &scriptUrl) {
m_mainFrame->evaluateJavaScript( QString(JS_APPEND_SCRIPT_ELEMENT).arg(scriptUrl), scriptUrl );
m_webPage->currentFrame()->evaluateJavaScript(QString(JS_APPEND_SCRIPT_ELEMENT).arg(scriptUrl), scriptUrl);
}
QObject *WebPage::_getGenericCallback() {
@ -950,6 +955,60 @@ void WebPage::sendEvent(const QString &type, const QVariant &arg1, const QVarian
}
}
int WebPage::childFramesCount()
{
return m_webPage->currentFrame()->childFrames().count();
}
QVariantList WebPage::childFramesName()
{
QVariantList framesName;
foreach(QWebFrame * f, m_webPage->currentFrame()->childFrames()) {
framesName << f->frameName();
}
return framesName;
}
bool WebPage::switchToChildFrame(const QString &frameName)
{
foreach(QWebFrame * f, m_webPage->currentFrame()->childFrames()) {
if (f->frameName() == frameName) {
f->setFocus();
return true;
}
}
return false;
}
bool WebPage::switchToChildFrame(const int framePosition)
{
if (framePosition >= 0 && framePosition < m_webPage->currentFrame()->childFrames().size()) {
m_webPage->currentFrame()->childFrames().at(framePosition)->setFocus();
return true;
}
return false;
}
void WebPage::switchToMainFrame()
{
m_mainFrame->setFocus();
}
bool WebPage::switchToParentFrame()
{
if (m_webPage->currentFrame()->parentFrame() != NULL) {
m_webPage->currentFrame()->parentFrame()->setFocus();
return true;
}
return false;
}
QString WebPage::currentFrameName()
{
return m_webPage->currentFrame()->frameName();
}
void WebPage::initCompletions()
{
// Add completion for the Dynamic Properties of the 'webpage' object
@ -981,13 +1040,25 @@ void WebPage::initCompletions()
addCompletion("onResourceReceived");
}
void WebPage::registerCallbacksHolder()
void WebPage::handleJavaScriptWindowObjectCleared()
{
// Create Callbacks Holder object, if not already present for this page
if (!m_callbacks) {
m_callbacks = new WebpageCallbacks(this);
}
// Reset focus on the Main Frame
m_mainFrame->setFocus();
// Decorate the window object in the Main Frame
m_mainFrame->addToJavaScriptWindowObject(CALLBACKS_OBJECT_NAME, m_callbacks, QScriptEngine::QtOwnership);
m_mainFrame->evaluateJavaScript(CALLBACKS_OBJECT_INJECTION);
// Decorate the window object in the Main Frame's Child Frames
foreach (QWebFrame *childFrame, m_mainFrame->childFrames()) {
childFrame->addToJavaScriptWindowObject(CALLBACKS_OBJECT_NAME, m_callbacks, QScriptEngine::QtOwnership);
childFrame->evaluateJavaScript(CALLBACKS_OBJECT_INJECTION);
}
}
#include "webpage.moc"

View File

@ -123,6 +123,52 @@ public slots:
void uploadFile(const QString &selector, const QString &fileName);
void sendEvent(const QString &type, const QVariant &arg1 = QVariant(), const QVariant &arg2 = QVariant());
/**
* Returns the number of Child Frames inside the Current Frame.
* NOTE: The Current Frame changes when focus moves (via API or JS) to a specific child frame.
* @brief childFramesCount
* @return Number of Frames inside the Current Frame
*/
int childFramesCount();
/**
* Returns a list of Child Frames name.
* NOTE: The Current Frame changes when focus moves (via API or JS) to a specific child frame.
* @brief childFramesName
* @return List (JS Array) containing the names of the Child Frames inside the Current Frame (if any)
*/
QVariantList childFramesName();
/**
* Switches focus from the Current Frame to a Child Frame, identified by it's name.
* @brief switchToChildFrame
* @param frameName Name of the Child frame
* @return "true" if the frame was found, "false" otherwise
*/
bool switchToChildFrame(const QString &frameName);
/**
* Switches focus from the Current Frame to a Child Frame, identified by it positional order.
* @brief switchToChildFrame
* @param framePosition Position of the Frame inside the Child Frames array (i.e. "window.frames[i]")
* @return "true" if the frame was found, "false" otherwise
*/
bool switchToChildFrame(const int framePosition);
/**
* Switches focus to the Main Frame within this Page.
* @brief switchToMainFrame
*/
void switchToMainFrame();
/**
* Switches focus to the Parent Frame of the Current Frame (if it exists).
* @brief switchToParentFrame
* @return "true" if the Current Frame is not a Main Frame, "false" otherwise (i.e. there is no parent frame to switch to)
*/
bool switchToParentFrame();
/**
* Returns the name of the Current Frame (if it has one)
* @brief currentFrameName
* @return Name of the Current Frame
*/
QString currentFrameName();
signals:
void initialized();
void loadStarted();
@ -137,7 +183,7 @@ signals:
private slots:
void finish(bool ok);
void registerCallbacksHolder();
void handleJavaScriptWindowObjectCleared();
private:
QImage renderImage();

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>frame1-1</title>
</head>
<body>
<h1>index > frame1 > frame1-1</h1>
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>frame1-2</title>
</head>
<body>
<h1>index > frame1 > frame1-2</h1>
</body>
</html>

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>frame1</title>
</head>
<frameset rows="50%,50%">
<frame name="frame1-1" src="./frame1-1.html" />
<frame name="frame1-2" src="./frame1-2.html" />
</frameset>
</html>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>frame2-1</title>
</head>
<body>
<h1>index > frame2 > frame2-1</h1>
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>frame2-2</title>
</head>
<body>
<h1>index > frame2 > frame2-2</h1>
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>frame2-3</title>
</head>
<body>
<h1>index > frame2 > frame2-3</h1>
</body>
</html>

View File

@ -0,0 +1,10 @@
<html>
<head>
<title>frame2</title>
</head>
<frameset rows="30%,40%,30%">
<frame name="frame2-1" src="./frame2-1.html" />
<frame name="frame2-2" src="./frame2-2.html" />
<frame name="frame2-3" src="./frame2-3.html" />
</frameset>
</html>

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>index</title>
</head>
<frameset cols="50%,50%">
<frame name="frame1" src="./frame1.html" />
<frame name="frame2" src="./frame2.html" />
</frameset>
</html>

View File

@ -159,8 +159,13 @@ describe("WebPage object", function() {
expectHasFunction(page, 'resourceReceived');
expectHasFunction(page, 'resourceRequested');
expectHasFunction(page, 'uploadFile');
expectHasFunction(page, 'sendEvent');
expectHasFunction(page, 'childFramesCount');
expectHasFunction(page, 'childFramesName');
expectHasFunction(page, 'switchToChildFrame');
expectHasFunction(page, 'switchToMainFrame');
expectHasFunction(page, 'switchToParentFrame');
expectHasFunction(page, 'currentFrameName');
it("should handle mousedown event", function() {
runs(function() {
@ -657,3 +662,84 @@ describe("WebPage construction with options", function () {
});
}
});
describe("WebPage should be able to switch frame of execution", function(){
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);
}
it("should load a page full of frames", function(){
runs(function() {
p.open("../test/webpage-spec-frames/index.html");
});
waits(50);
});
it("should be able to detect frames at level 0", function(){
expect(pageTitle(p)).toEqual("index");
expect(p.currentFrameName()).toEqual("");
expect(p.childFramesCount()).toEqual(2);
expect(p.childFramesName()).toEqual(["frame1", "frame2"]);
setPageTitle(p, pageTitle(p) + "-visited");
});
it("should go down to a child frame at level 1", function(){
expect(p.switchToChildFrame("frame1")).toBeTruthy();
expect(pageTitle(p)).toEqual("frame1");
expect(p.currentFrameName()).toEqual("frame1");
expect(p.childFramesCount()).toEqual(2);
expect(p.childFramesName()).toEqual(["frame1-1", "frame1-2"]);
setPageTitle(p, pageTitle(p) + "-visited");
});
it("should go down to a child frame at level 2", function(){
expect(p.switchToChildFrame("frame1-2")).toBeTruthy();
expect(pageTitle(p)).toEqual("frame1-2");
expect(p.currentFrameName()).toEqual("frame1-2");
expect(p.childFramesCount()).toEqual(0);
expect(p.childFramesName()).toEqual([]);
setPageTitle(p, pageTitle(p) + "-visited");
});
it("should go up to the parent frame at level 1", function(){
expect(p.switchToParentFrame()).toBeTruthy();
expect(pageTitle(p)).toEqual("frame1-visited");
expect(p.currentFrameName()).toEqual("frame1");
expect(p.childFramesCount()).toEqual(2);
expect(p.childFramesName()).toEqual(["frame1-1", "frame1-2"]);
});
it("should go down to a child frame at level 2 (again)", function(){
expect(p.switchToChildFrame(0)).toBeTruthy();
expect(pageTitle(p)).toEqual("frame1-1");
expect(p.currentFrameName()).toEqual("frame1-1");
expect(p.childFramesCount()).toEqual(0);
expect(p.childFramesName()).toEqual([]);
});
it("should go up to the main (top) frame at level 0", function(){
expect(p.switchToMainFrame()).toBeUndefined();
expect(pageTitle(p)).toEqual("index-visited");
expect(p.currentFrameName()).toEqual("");
expect(p.childFramesCount()).toEqual(2);
expect(p.childFramesName()).toEqual(["frame1", "frame2"]);
});
it("should go down to (the other) child frame at level 1", function(){
expect(p.switchToChildFrame("frame2")).toBeTruthy();
expect(pageTitle(p)).toEqual("frame2");
expect(p.currentFrameName()).toEqual("frame2");
expect(p.childFramesCount()).toEqual(3);
expect(p.childFramesName()).toEqual(["frame2-1", "frame2-2", "frame2-3"]);
});
});