diff --git a/examples/walk_through_frames.coffee b/examples/walk_through_frames.coffee new file mode 100644 index 00000000..1838fa27 --- /dev/null +++ b/examples/walk_through_frames.coffee @@ -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() diff --git a/examples/walk_through_frames.js b/examples/walk_through_frames.js new file mode 100644 index 00000000..35c2bb9b --- /dev/null +++ b/examples/walk_through_frames.js @@ -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(); +}); + diff --git a/src/bootstrap.js b/src/bootstrap.js index 4c3f2719..842141a6 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -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) diff --git a/src/utils.cpp b/src/utils.cpp index e2805821..0fa65bfc 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -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", diff --git a/src/webpage.cpp b/src/webpage.cpp index 06d812c3..7ffc49fa 100644 --- a/src/webpage.cpp +++ b/src/webpage.cpp @@ -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" diff --git a/src/webpage.h b/src/webpage.h index c79fdf2c..b34e108d 100644 --- a/src/webpage.h +++ b/src/webpage.h @@ -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(); diff --git a/test/webpage-spec-frames/frame1-1.html b/test/webpage-spec-frames/frame1-1.html new file mode 100644 index 00000000..c80ec458 --- /dev/null +++ b/test/webpage-spec-frames/frame1-1.html @@ -0,0 +1,8 @@ + + + frame1-1 + + +

index > frame1 > frame1-1

+ + diff --git a/test/webpage-spec-frames/frame1-2.html b/test/webpage-spec-frames/frame1-2.html new file mode 100644 index 00000000..b0c38d2f --- /dev/null +++ b/test/webpage-spec-frames/frame1-2.html @@ -0,0 +1,8 @@ + + + frame1-2 + + +

index > frame1 > frame1-2

+ + diff --git a/test/webpage-spec-frames/frame1.html b/test/webpage-spec-frames/frame1.html new file mode 100644 index 00000000..b23c2741 --- /dev/null +++ b/test/webpage-spec-frames/frame1.html @@ -0,0 +1,9 @@ + + + frame1 + + + + + + diff --git a/test/webpage-spec-frames/frame2-1.html b/test/webpage-spec-frames/frame2-1.html new file mode 100644 index 00000000..2f7c121a --- /dev/null +++ b/test/webpage-spec-frames/frame2-1.html @@ -0,0 +1,8 @@ + + + frame2-1 + + +

index > frame2 > frame2-1

+ + diff --git a/test/webpage-spec-frames/frame2-2.html b/test/webpage-spec-frames/frame2-2.html new file mode 100644 index 00000000..99f603d7 --- /dev/null +++ b/test/webpage-spec-frames/frame2-2.html @@ -0,0 +1,8 @@ + + + frame2-2 + + +

index > frame2 > frame2-2

+ + diff --git a/test/webpage-spec-frames/frame2-3.html b/test/webpage-spec-frames/frame2-3.html new file mode 100644 index 00000000..b6f18089 --- /dev/null +++ b/test/webpage-spec-frames/frame2-3.html @@ -0,0 +1,8 @@ + + + frame2-3 + + +

index > frame2 > frame2-3

+ + diff --git a/test/webpage-spec-frames/frame2.html b/test/webpage-spec-frames/frame2.html new file mode 100644 index 00000000..d3484bd1 --- /dev/null +++ b/test/webpage-spec-frames/frame2.html @@ -0,0 +1,10 @@ + + + frame2 + + + + + + + diff --git a/test/webpage-spec-frames/index.html b/test/webpage-spec-frames/index.html new file mode 100644 index 00000000..dbe01bb5 --- /dev/null +++ b/test/webpage-spec-frames/index.html @@ -0,0 +1,9 @@ + + + index + + + + + + diff --git a/test/webpage-spec.js b/test/webpage-spec.js index fba43e43..b2984743 100644 --- a/test/webpage-spec.js +++ b/test/webpage-spec.js @@ -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"]); + }); +});