Permalink
Browse files

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
  • Loading branch information...
detro authored and ariya committed May 30, 2012
1 parent 40fd210 commit f386f7d48470a442e7ce7a89fc8cb4ca7daea5af
@@ -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()
@@ -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
@@ -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
@@ -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
@@ -131,6 +131,9 @@ public slots:
}
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
@@ -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 @@ public slots:
private slots:
void finish(bool ok);
- void registerCallbacksHolder();
+ void handleJavaScriptWindowObjectCleared();
private:
QImage renderImage();
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>frame1-1</title>
+</head>
+<body>
+ <h1>index > frame1 > frame1-1</h1>
+</body>
+</html>
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>frame1-2</title>
+</head>
+<body>
+ <h1>index > frame1 > frame1-2</h1>
+</body>
+</html>
@@ -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>
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>frame2-1</title>
+</head>
+<body>
+ <h1>index > frame2 > frame2-1</h1>
+</body>
+</html>
Oops, something went wrong.

2 comments on commit f386f7d

Contributor

jonleighton replied Jun 16, 2012

@detro these tests are failing for me on Fedora 16 / x86_64. I presume they pass for you?

Collaborator

detro replied Jun 16, 2012

Yes.
Can you investigate a bit where are they failing? I'm sure you got the skill for that :)

The code itself it's VERY simple: I'm just using QWebFrame interface methods and exposing them to the JS space - nothing fancy

Please sign in to comment.