Adding property "page.ownsPages".

When set to "true", any page that gets created, is
owned by the "page" that control it's lifetime.
Also, the pages can be found in the "page.pages[]" array.

Default value is "true".

Addresses [Issue #151](http://code.google.com/p/phantomjs/issues/detail?id=151)
1.7
Ivan De Marino 2012-07-27 17:13:43 +01:00
parent 27dc699919
commit 733d21042b
5 changed files with 177 additions and 45 deletions

View File

@ -41,6 +41,7 @@
#include "system.h" #include "system.h"
class WebPage; class WebPage;
class CustomPage;
class WebServer; class WebServer;
class Phantom: public REPLCompletable class Phantom: public REPLCompletable
@ -133,6 +134,8 @@ private:
QList<QPointer<WebServer> > m_servers; QList<QPointer<WebServer> > m_servers;
Config m_config; Config m_config;
QVariantMap m_keyMap; QVariantMap m_keyMap;
friend class CustomPage;
}; };
#endif // PHANTOM_H #endif // PHANTOM_H

View File

@ -181,9 +181,16 @@ protected:
QWebPage *createWindow (WebWindowType type) { QWebPage *createWindow (WebWindowType type) {
Q_UNUSED(type); Q_UNUSED(type);
WebPage *newPage;
// Create a new "raw" WebPage object // Create a new "raw" WebPage object
WebPage *newPage = new WebPage(m_webPage); if (m_webPage->ownsPages()) {
newPage = new WebPage(m_webPage);
} else {
newPage = new WebPage(Phantom::instance());
Phantom::instance()->m_pages.append(newPage);
}
// Apply default settings // Apply default settings
newPage->applySettings(Phantom::instance()->defaultPageSettings()); newPage->applySettings(Phantom::instance()->defaultPageSettings());
@ -264,6 +271,7 @@ WebPage::WebPage(QObject *parent, const QUrl &baseUrl)
: REPLCompletable(parent) : REPLCompletable(parent)
, m_callbacks(NULL) , m_callbacks(NULL)
, m_navigationLocked(false) , m_navigationLocked(false)
, m_ownsPages(true)
{ {
setObjectName("WebPage"); setObjectName("WebPage");
m_customWebPage = new CustomPage(this); m_customWebPage = new CustomPage(this);
@ -415,7 +423,6 @@ bool WebPage::navigationLocked()
return m_navigationLocked; return m_navigationLocked;
} }
void WebPage::setViewportSize(const QVariantMap &size) void WebPage::setViewportSize(const QVariantMap &size)
{ {
int w = size.value("width").toInt(); int w = size.value("width").toInt();
@ -1062,6 +1069,16 @@ QObject *WebPage::getPage(const QString &windowName) const
return NULL; return NULL;
} }
bool WebPage::ownsPages() const
{
return m_ownsPages;
}
void WebPage::setOwnsPages(const bool owns)
{
m_ownsPages = owns;
}
int WebPage::framesCount() const int WebPage::framesCount() const
{ {
return m_customWebPage->currentFrame()->childFrames().count(); return m_customWebPage->currentFrame()->childFrames().count();

View File

@ -64,6 +64,7 @@ class WebPage: public REPLCompletable, public QWebFrame::PrintCallback
Q_PROPERTY(QString windowName READ windowName) Q_PROPERTY(QString windowName READ windowName)
Q_PROPERTY(QObjectList pages READ pages) Q_PROPERTY(QObjectList pages READ pages)
Q_PROPERTY(QStringList pagesWindowName READ pagesWindowName) Q_PROPERTY(QStringList pagesWindowName READ pagesWindowName)
Q_PROPERTY(bool ownsPages READ ownsPages WRITE setOwnsPages)
Q_PROPERTY(QStringList framesName READ framesName) Q_PROPERTY(QStringList framesName READ framesName)
Q_PROPERTY(QString frameName READ frameName) Q_PROPERTY(QString frameName READ frameName)
Q_PROPERTY(int framesCount READ framesCount) Q_PROPERTY(int framesCount READ framesCount)
@ -136,6 +137,8 @@ public:
* *
* NOTE: The ownership of this array is held by the Page: it's not adviced * NOTE: The ownership of this array is held by the Page: it's not adviced
* to have a "long running reference" to this array, as it might change. * to have a "long running reference" to this array, as it might change.
* NOTE: If "ownsPages()" is "false", the page will create pages but not
* hold any ownership to it. Resource management is than left to the user.
* *
* @brief pages * @brief pages
* @return List (JS Array) containing the Pages that this page * @return List (JS Array) containing the Pages that this page
@ -148,6 +151,8 @@ public:
* NOTE: When a page is opened with <code>"window.open"</code>, a window * NOTE: When a page is opened with <code>"window.open"</code>, a window
* <code>"name"</code> might be provided as second parameter. * <code>"name"</code> might be provided as second parameter.
* This provides a useful list of those. * This provides a useful list of those.
* NOTE: If "ownsPages()" is "false", the page will create pages but not
* hold any ownership of it. Resource management is than left to the user.
* *
* @brief pagesWindowName * @brief pagesWindowName
* @return List (JS Array) containing the <code>'window.name'</code>(s) of * @return List (JS Array) containing the <code>'window.name'</code>(s) of
@ -155,6 +160,26 @@ public:
*/ */
QStringList pagesWindowName() const; QStringList pagesWindowName() const;
/**
* Returns "true" if it owns the pages it creates (and keeps them in "pages[]").
* Default value is "true". Can be changed using {@link setOwnsPages()}.
*
* @brief ownsPages()
* @return "true" if it owns the pages it creates in "pages[]", "false" otherwise.
*/
bool ownsPages() const;
/**
* Set if, from now on, it should own the pages it creates in "pages[]".
* Default value is "true".
*
* NOTE: When switching from "false" to "true", only the pages created
* from that point on will be owned. It's NOT retroactive.
*
* @brief setOwnsPages
* @param owns "true" to make it own the pages it creates in "pages[]", "false" otherwise.
*/
void setOwnsPages(const bool owns);
/** /**
* Returns the number of Child Frames inside the Current Frame. * 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. * NOTE: The Current Frame changes when focus moves (via API or JS) to a specific child frame.
@ -338,6 +363,7 @@ private:
WebpageCallbacks *m_callbacks; WebpageCallbacks *m_callbacks;
bool m_navigationLocked; bool m_navigationLocked;
QPoint m_mousePos; QPoint m_mousePos;
bool m_ownsPages;
friend class Phantom; friend class Phantom;
friend class CustomPage; friend class CustomPage;

View File

@ -6,11 +6,11 @@ describe("Basic Files API (read, write, remove, ...)", function() {
FILENAME_ENC = FILENAME + ".enc", FILENAME_ENC = FILENAME + ".enc",
FILENAME_BIN = FILENAME + ".bin", FILENAME_BIN = FILENAME + ".bin",
ABSENT = "absent-01.test"; ABSENT = "absent-01.test";
it("should be able to create and write a file", function() { it("should be able to create and write a file", function() {
try{ try{
var f = fs.open(FILENAME, "w"); var f = fs.open(FILENAME, "w");
f.write("hello"); f.write("hello");
f.writeLine(""); f.writeLine("");
f.writeLine("world"); f.writeLine("world");
@ -25,31 +25,29 @@ describe("Basic Files API (read, write, remove, ...)", function() {
expect(fs.exists(FILENAME_EMPTY)).toBeTruthy(); expect(fs.exists(FILENAME_EMPTY)).toBeTruthy();
expect(fs.size(FILENAME_EMPTY)).toEqual(0); expect(fs.size(FILENAME_EMPTY)).toEqual(0);
}); });
it("should be able to read content from a file", function() { it("should be able to read content from a file", function() {
var content = ""; var content = "";
try{ try{
var f = fs.open(FILENAME, "r"); var f = fs.open(FILENAME, "r");
content = f.read(); content = f.read();
f.close(); f.close();
} catch (e) { } } catch (e) { }
expect(content).toEqual("hello\nworld\n"); expect(content).toEqual("hello\nworld\n");
}); });
it("should be able to read/write/append content from a file", function() { it("should be able to read/write/append content from a file", function() {
var content = ""; var content = "";
try{ try{
var f = fs.open(FILENAME, "rw+"); var f = fs.open(FILENAME, "rw+");
console.log(f.read().length);
f.writeLine("asdf"); f.writeLine("asdf");
content = f.read(); content = f.read();
console.log(content.length);
f.close(); f.close();
} catch (e) { } } catch (e) { }
expect(content).toEqual("hello\nworld\nasdf\n"); expect(content).toEqual("hello\nworld\nasdf\n");
}); });
it("should be able to copy a file", function() { it("should be able to copy a file", function() {
expect(fs.exists(FILENAME_COPY)).toBeFalsy(); expect(fs.exists(FILENAME_COPY)).toBeFalsy();
fs.copy(FILENAME, FILENAME_COPY); fs.copy(FILENAME, FILENAME_COPY);
@ -77,7 +75,7 @@ describe("Basic Files API (read, write, remove, ...)", function() {
fs.remove(FILENAME_COPY); fs.remove(FILENAME_COPY);
expect(fs.exists(FILENAME_COPY)).toBeFalsy(); expect(fs.exists(FILENAME_COPY)).toBeFalsy();
}); });
it("should be able to remove an empty file", function() { it("should be able to remove an empty file", function() {
expect(fs.exists(FILENAME_EMPTY)).toBeTruthy(); expect(fs.exists(FILENAME_EMPTY)).toBeTruthy();
fs.remove(FILENAME_EMPTY); fs.remove(FILENAME_EMPTY);
@ -139,4 +137,4 @@ describe("Basic Files API (read, write, remove, ...)", function() {
} catch (e) { } } catch (e) { }
expect(content).toEqual(output); expect(content).toEqual(output);
}); });
}); });

View File

@ -157,6 +157,7 @@ describe("WebPage object", function() {
expectHasFunction(page, 'loadStarted'); expectHasFunction(page, 'loadStarted');
expectHasFunction(page, 'openUrl'); expectHasFunction(page, 'openUrl');
expectHasFunction(page, 'release'); expectHasFunction(page, 'release');
expectHasFunction(page, 'close');
expectHasFunction(page, 'render'); expectHasFunction(page, 'render');
expectHasFunction(page, 'resourceReceived'); expectHasFunction(page, 'resourceReceived');
expectHasFunction(page, 'resourceRequested'); expectHasFunction(page, 'resourceRequested');
@ -242,7 +243,7 @@ describe("WebPage object", function() {
return page.evaluate(function() { return page.evaluate(function() {
return document.querySelector('input').value; return document.querySelector('input').value;
}); });
} };
page.sendEvent('keypress', phantom.keys.A); page.sendEvent('keypress', phantom.keys.A);
expect(getText()).toEqual("A"); expect(getText()).toEqual("A");
page.sendEvent('keypress', phantom.keys.B); page.sendEvent('keypress', phantom.keys.B);
@ -431,7 +432,7 @@ describe("WebPage object", function() {
runs(function() { runs(function() {
page.evaluate(function() { page.evaluate(function() {
setTimeout(function() { referenceError }, 0); setTimeout(function() { referenceError(); }, 0);
}); });
}); });
@ -440,13 +441,13 @@ describe("WebPage object", function() {
runs(function() { runs(function() {
expect(lastError).toEqual("ReferenceError: Can't find variable: referenceError"); expect(lastError).toEqual("ReferenceError: Can't find variable: referenceError");
page.evaluate(function() { referenceError2 }); page.evaluate(function() { referenceError2(); });
expect(lastError).toEqual("ReferenceError: Can't find variable: referenceError2"); expect(lastError).toEqual("ReferenceError: Can't find variable: referenceError2");
page.evaluate(function() { throw "foo" }); page.evaluate(function() { throw "foo"; });
expect(lastError).toEqual("foo"); expect(lastError).toEqual("foo");
page.evaluate(function() { throw Error("foo") }); page.evaluate(function() { throw Error("foo"); });
expect(lastError).toEqual("Error: foo"); expect(lastError).toEqual("Error: foo");
}); });
}); });
@ -462,32 +463,32 @@ describe("WebPage object", function() {
caughtError = false; caughtError = false;
try { try {
referenceError referenceError();
} catch(e) { } catch(e) {
caughtError = true; caughtError = true;
} }
}); });
expect(hadError).toEqual(false); expect(hadError).toEqual(false);
expect(page.evaluate(function() { return caughtError })).toEqual(true); expect(page.evaluate(function() { return caughtError; })).toEqual(true);
}); });
}) });
it("reports the sourceURL and line of errors", function() { it("reports the sourceURL and line of errors", function() {
runs(function() { runs(function() {
var e1, e2; var e1, e2;
try { try {
referenceError referenceError();
} catch (e) { } catch (e) {
e1 = e e1 = e;
}; }
try { try {
referenceError referenceError();
} catch (e) { } catch (e) {
e2 = e e2 = e;
}; }
expect(e1.sourceURL).toMatch(/webpage-spec.js$/); expect(e1.sourceURL).toMatch(/webpage-spec.js$/);
expect(e1.line).toBeGreaterThan(1); expect(e1.line).toBeGreaterThan(1);
@ -503,15 +504,15 @@ describe("WebPage object", function() {
runs(function() { runs(function() {
function test() { function test() {
ErrorHelper.foo() ErrorHelper.foo();
}; }
var err; var err;
try { try {
test() test();
} catch (e) { } catch (e) {
err = e err = e;
}; }
var lines = err.stack.split("\n"); var lines = err.stack.split("\n");
@ -522,8 +523,8 @@ describe("WebPage object", function() {
page.injectJs(helperFile); page.injectJs(helperFile);
page.onError = function(message, s) { stack = s }; page.onError = function(message, s) { stack = s; };
page.evaluate(function() { setTimeout(function() { ErrorHelper.foo() }, 0) }); page.evaluate(function() { setTimeout(function() { ErrorHelper.foo(); }, 0); });
}); });
waits(0); waits(0);
@ -531,16 +532,16 @@ describe("WebPage object", function() {
runs(function() { runs(function() {
expect(stack[0].file).toEqual("./fixtures/error-helper.js"); expect(stack[0].file).toEqual("./fixtures/error-helper.js");
expect(stack[0].line).toEqual(7); expect(stack[0].line).toEqual(7);
expect(stack[0].function).toEqual("bar"); expect(stack[0]["function"]).toEqual("bar");
}); });
}); });
it("reports errors that occur in the main context", function() { it("reports errors that occur in the main context", function() {
var error; var error;
phantom.onError = function(e) { error = e }; phantom.onError = function(e) { error = e; };
runs(function() { runs(function() {
setTimeout(function() { zomg }, 0); setTimeout(function() { zomg(); }, 0);
}); });
waits(0); waits(0);
@ -881,7 +882,7 @@ describe("WebPage construction with options", function () {
decodedText = page.evaluate(function() { decodedText = page.evaluate(function() {
return document.getElementsByTagName('pre')[0].innerText; return document.getElementsByTagName('pre')[0].innerText;
}); });
page.release(); page.close();
}); });
it("Should support text codec " + text.codec, function() { it("Should support text codec " + text.codec, function() {
expect(decodedText.match("^" + text.reference) == text.reference).toEqual(true); expect(decodedText.match("^" + text.reference) == text.reference).toEqual(true);
@ -1092,7 +1093,7 @@ describe("WebPage opening and closing of windows/child-pages", function(){
var yahoo = p.getPage("yahoo"); var yahoo = p.getPage("yahoo");
expect(yahoo).not.toBe(null); expect(yahoo).not.toBe(null);
yahoo.release(); yahoo.close();
waitsFor(function(){ waitsFor(function(){
return p.pages.length === 1; return p.pages.length === 1;
@ -1101,6 +1102,7 @@ describe("WebPage opening and closing of windows/child-pages", function(){
runs(function(){ runs(function(){
expect(p.pages.length).toEqual(1); expect(p.pages.length).toEqual(1);
expect(p.pagesWindowName).toEqual(["bing"]); expect(p.pagesWindowName).toEqual(["bing"]);
p.close();
}); });
}); });
}); });
@ -1117,9 +1119,15 @@ describe("WebPage closing notification/alerting", function(){
p.close(); p.close();
expect(spy).toHaveBeenCalled(); //< called waitsFor(function() {
expect(spy.calls.length).toEqual(1); //< only once return spy.calls.length === 1;
expect(spy).toHaveBeenCalledWith(p); //< called passing reference to the closing page 'p' }, "after 2sec 'onClosing' had still not been invoked", 2000);
runs(function() {
expect(spy).toHaveBeenCalled(); //< called
expect(spy.calls.length).toEqual(1); //< only once
expect(spy).toHaveBeenCalledWith(p); //< called passing reference to the closing page 'p'
});
}); });
it("should call 'onClosing' when a page closes on it's own", function(){ it("should call 'onClosing' when a page closes on it's own", function(){
@ -1135,8 +1143,88 @@ describe("WebPage closing notification/alerting", function(){
window.close(); window.close();
}); });
expect(spy).toHaveBeenCalled(); //< called waitsFor(function() {
expect(spy.calls.length).toEqual(1); //< only once return spy.calls.length === 1;
expect(spy).toHaveBeenCalledWith(p); //< called passing reference to the closing page 'p' }, "after 2sec 'onClosing' had still not been invoked", 2000);
runs(function() {
expect(spy).toHaveBeenCalled(); //< called
expect(spy.calls.length).toEqual(1); //< only once
expect(spy).toHaveBeenCalledWith(p); //< called passing reference to the closing page 'p'
});
});
});
describe("WebPage closing notification/alerting: closing propagation control", function(){
it("should close all 4 pages if parent page is closed (default value for 'ownsPages')", function(){
var p = require("webpage").create(),
pages,
openPagesCount = 0;
p.onPageCreated = jasmine.createSpy("onPageCreated spy");
expect(p.ownsPages).toBeTruthy();
p.evaluate(function() {
// yeah, I know globals. YIKES!
window.w1 = window.open("http://www.google.com", "google");
window.w2 = window.open("http://www.yahoo.com", "yahoo");
window.w3 = window.open("http://www.bing.com", "bing");
});
pages = p.pages;
openPagesCount = p.pages.length + 1;
expect(p.onPageCreated).toHaveBeenCalled();
expect(p.onPageCreated.calls.length).toEqual(3);
expect(p.pages.length).toEqual(3);
p.onClosing = function() { --openPagesCount; };
pages[0].onClosing = function() { --openPagesCount; };
pages[1].onClosing = function() { --openPagesCount; };
pages[2].onClosing = function() { --openPagesCount; };
p.close();
waitsFor(function() {
return openPagesCount === 0;
}, "after 2sec pages were still open", 2000);
runs(function() {
expect(openPagesCount).toBe(0);
});
});
it("should NOT close all 4 pages if parent page is closed, just parent itself ('ownsPages' set to false)", function(){
var p = require("webpage").create(),
pages,
openPagesCount = 0;
p.ownsPages = false;
p.onPageCreated = jasmine.createSpy("onPageCreated spy");
expect(p.ownsPages).toBeFalsy();
p.evaluate(function() {
// yeah, I know globals. YIKES!
window.w1 = window.open("http://www.google.com", "google");
window.w2 = window.open("http://www.yahoo.com", "yahoo");
window.w3 = window.open("http://www.bing.com", "bing");
});
pages = p.pages;
openPagesCount = 1;
expect(p.onPageCreated).toHaveBeenCalled();
expect(p.onPageCreated.calls.length).toEqual(3);
expect(p.pages.length).toEqual(0);
p.onClosing = function() { --openPagesCount; };
p.close();
waitsFor(function() {
return openPagesCount === 0;
}, "after 2sec pages were still open", 2000);
runs(function() {
expect(openPagesCount).toBe(0);
});
}); });
}); });