Merge m-c to inbound

This commit is contained in:
Ryan VanderMeulen 2012-05-26 18:20:43 -04:00
commit c88da090b2
128 changed files with 8000 additions and 7303 deletions

View File

@ -40,14 +40,14 @@ window.onload = function() {
sessionData.dispatchEvent(event);
initTreeView();
document.getElementById("errorTryAgain").focus();
};
function initTreeView() {
var tabList = document.getElementById("tabList");
var winLabel = tabList.getAttribute("_window_label");
gTreeData = [];
gStateObject.windows.forEach(function(aWinData, aIx) {
var winState = {
@ -73,7 +73,7 @@ function initTreeView() {
for each (var tab in winState.tabs)
gTreeData.push(tab);
}, this);
tabList.view = treeView;
tabList.view.selection.select(0);
}
@ -82,7 +82,7 @@ function initTreeView() {
function restoreSession() {
document.getElementById("errorTryAgain").disabled = true;
// remove all unselected tabs from the state before restoring it
var ix = gStateObject.windows.length - 1;
for (var t = gTreeData.length - 1; t >= 0; t--) {
@ -99,10 +99,10 @@ function restoreSession() {
}
}
var stateString = JSON.stringify(gStateObject);
var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
var top = getBrowserWindow();
// if there's only this page open, reuse the window for restoring the session
if (top.gBrowser.tabs.length == 1) {
ss.setWindowState(top, stateString, true);
@ -114,7 +114,7 @@ function restoreSession() {
newWindow.addEventListener("load", function() {
newWindow.removeEventListener("load", arguments.callee, true);
ss.setWindowState(newWindow, stateString, true);
var tabbrowser = top.gBrowser;
var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
@ -133,7 +133,7 @@ function onListClick(aEvent) {
// don't react to right-clicks
if (aEvent.button == 2)
return;
var row = {}, col = {};
treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY, row, col, {});
if (col.value) {
@ -189,9 +189,9 @@ function toggleRowChecked(aIx) {
var item = gTreeData[aIx];
item.checked = !item.checked;
treeView.treeBox.invalidateRow(aIx);
function isChecked(aItem) aItem.checked;
if (treeView.isContainer(aIx)) {
// (un)check all tabs of this window as well
for each (var tab in item.tabs) {
@ -205,7 +205,7 @@ function toggleRowChecked(aIx) {
item.parent.tabs.some(isChecked) ? 0 : false;
treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
}
document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
}
@ -213,14 +213,14 @@ function restoreSingleTab(aIx, aShifted) {
var tabbrowser = getBrowserWindow().gBrowser;
var newTab = tabbrowser.addTab();
var item = gTreeData[aIx];
var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
var tabState = gStateObject.windows[item.parent.ix]
.tabs[aIx - gTreeData.indexOf(item.parent) - 1];
// ensure tab would be visible on the tabstrip.
tabState.hidden = false;
ss.setTabState(newTab, JSON.stringify(tabState));
// respect the preference as to whether to select the tab (the Shift key inverses)
var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)

View File

@ -29,15 +29,15 @@
<!-- PAGE CONTAINER (for styling purposes only) -->
<div id="errorPageContainer">
<!-- Error Title -->
<div id="errorTitle">
<h1 id="errorTitleText">&restorepage.errorTitle;</h1>
</div>
<!-- LONG CONTENT (the section most likely to require scrolling) -->
<div id="errorLongContent">
<!-- Short Description -->
<div id="errorShortDesc">
<p id="errorShortDescText">&restorepage.problemDesc;</p>

View File

@ -3,5 +3,5 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
browser.jar:
* content/browser/aboutSessionRestore.xhtml (content/aboutSessionRestore.xhtml)
* content/browser/aboutSessionRestore.xhtml (content/aboutSessionRestore.xhtml)
* content/browser/aboutSessionRestore.js (content/aboutSessionRestore.js)

View File

@ -17,7 +17,7 @@ interface nsISessionStartup: nsISupports
readonly attribute jsval state;
/**
* Determine if session should be restored
* Determine if session should be restored
*/
boolean doRestore();

View File

@ -68,7 +68,7 @@ interface nsISessionStore : nsISupports
/**
* @param aWindow is the browser window whose state is to be returned.
*
*
* @returns a JSON string representing a session state with only one window.
*/
AString getWindowState(in nsIDOMWindow aWindow);
@ -82,7 +82,7 @@ interface nsISessionStore : nsISupports
/**
* @param aTab is the tabbrowser tab whose state is to be returned.
*
*
* @returns a JSON string representing the state of the tab
* (note: doesn't contain cookies - if you need them, use getWindowState instead).
*/
@ -160,7 +160,7 @@ interface nsISessionStore : nsISupports
/**
* @param aWindow is the window to get the value for.
* @param aKey is the value's name.
*
*
* @returns A string value or an empty string if none is set.
*/
AString getWindowValue(in nsIDOMWindow aWindow, in AString aKey);
@ -171,7 +171,7 @@ interface nsISessionStore : nsISupports
* @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
*/
void setWindowValue(in nsIDOMWindow aWindow, in AString aKey, in AString aStringValue);
/**
* @param aWindow is the browser window to get the value for.
* @param aKey is the value's name.
@ -181,7 +181,7 @@ interface nsISessionStore : nsISupports
/**
* @param aTab is the tabbrowser tab to get the value for.
* @param aKey is the value's name.
*
*
* @returns A string value or an empty string if none is set.
*/
AString getTabValue(in nsIDOMNode aTab, in AString aKey);

View File

@ -35,7 +35,7 @@ let DocumentUtils = {
let node;
let ret = {id: {}, xpath: {}};
// Limit the number of XPath expressions for performance reasons. See
// bug 477564.
const MAX_TRAVERSED_XPATHS = 100;
@ -121,7 +121,7 @@ let DocumentUtils = {
if ("xpath" in aData) {
for each (let [xpath, value] in Iterator(aData.xpath)) {
let node = XPathGenerator.resolve(aDocument, xpath);
if (node) {
this.restoreFormValue(node, value, aDocument);
}
@ -131,7 +131,7 @@ let DocumentUtils = {
if ("id" in aData) {
for each (let [id, value] in Iterator(aData.id)) {
let node = aDocument.getElementById(id);
if (node) {
this.restoreFormValue(node, value, aDocument);
}
@ -159,7 +159,7 @@ let DocumentUtils = {
aDocument = aDocument || aNode.ownerDocument;
let eventType;
if (typeof aValue == "string" && aNode.type != "file") {
// Don't dispatch an input event if there is no change.
if (aNode.value == aValue) {
@ -173,7 +173,7 @@ let DocumentUtils = {
if (aNode.checked == aValue) {
return;
}
aNode.checked = aValue;
eventType = "change";
} else if (typeof aValue == "number") {
@ -183,11 +183,11 @@ let DocumentUtils = {
if (aNode.selectedIndex == aValue) {
return;
}
if (aValue < aNode.options.length) {
aNode.selectedIndex = aValue;
eventType = "change";
}
}
} else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
// handle select new format
@ -212,7 +212,7 @@ let DocumentUtils = {
Array.forEach(aNode.options, function(opt, index) {
// don't worry about malformed options with same values
opt.selected = aValue.indexOf(opt.value) > -1;
// Only fire the event here if this wasn't selected by default
if (!opt.defaultSelected) {
eventType = "change";

View File

@ -21,4 +21,21 @@ EXTRA_PP_COMPONENTS = \
libs::
$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/sessionstore
SS_EXTRA_PP_JS_MODULES = \
SessionStore.jsm \
$(NULL)
ifdef SS_EXTRA_PP_JS_MODULES
libs:: $(SS_EXTRA_PP_JS_MODULES)
ifndef NO_DIST_INSTALL
$(EXIT_ON_ERROR) \
$(NSINSTALL) -D $(FINAL_TARGET)/modules/sessionstore; \
for i in $^; do \
dest=$(FINAL_TARGET)/modules/sessionstore/`basename $$i`; \
$(RM) -f $$dest; \
$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $$i > $$dest; \
done
endif
endif
include $(topsrcdir)/config/rules.mk

File diff suppressed because it is too large Load Diff

View File

@ -16,18 +16,18 @@ let XPathGenerator = {
// have we reached the document node already?
if (!aNode.parentNode)
return "";
// Access localName, namespaceURI just once per node since it's expensive.
let nNamespaceURI = aNode.namespaceURI;
let nLocalName = aNode.localName;
let prefix = this.namespacePrefixes[nNamespaceURI] || null;
let tag = (prefix ? prefix + ":" : "") + this.escapeName(nLocalName);
// stop once we've found a tag with an ID
if (aNode.id)
return "//" + tag + "[@id=" + this.quoteArgument(aNode.id) + "]";
// count the number of previous sibling nodes of the same tag
// (and possible also the same name)
let count = 0;
@ -36,7 +36,7 @@ let XPathGenerator = {
if (n.localName == nLocalName && n.namespaceURI == nNamespaceURI &&
(!nName || n.name == nName))
count++;
// recurse until hitting either the document node or an ID'd node
return this.generate(aNode.parentNode) + "/" + tag +
(nName ? "[@name=" + this.quoteArgument(nName) + "]" : "") +
@ -90,7 +90,7 @@ let XPathGenerator = {
ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')";
let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" +
"//input[" + ignore + "]|//xhtml:input[" + ignore + "]";
delete this.restorableFormNodes;
return (this.restorableFormNodes = formNodesXPath);
}

View File

@ -1,4 +1,4 @@
/*
/*
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
@ -6,28 +6,28 @@
/**
# * Session Storage and Restoration
# *
# *
# * Overview
# * This service reads user's session file at startup, and makes a determination
# * as to whether the session should be restored. It will restore the session
# * This service reads user's session file at startup, and makes a determination
# * as to whether the session should be restored. It will restore the session
# * under the circumstances described below. If the auto-start Private Browsing
# * mode is active, however, the session is never restored.
# *
# *
# * Crash Detection
# * The session file stores a session.state property, that
# * indicates whether the browser is currently running. When the browser shuts
# * The session file stores a session.state property, that
# * indicates whether the browser is currently running. When the browser shuts
# * down, the field is changed to "stopped". At startup, this field is read, and
# * if its value is "running", then it's assumed that the browser had previously
# * crashed, or at the very least that something bad happened, and that we should
# * restore the session.
# *
# *
# * Forced Restarts
# * In the event that a restart is required due to application update or extension
# * installation, set the browser.sessionstore.resume_session_once pref to true,
# * and the session will be restored the next time the browser starts.
# *
# *
# * Always Resume
# * This service will always resume the session if the integer pref
# * This service will always resume the session if the integer pref
# * browser.startup.page is set to 3.
*/
@ -161,11 +161,11 @@ SessionStartup.prototype = {
*/
observe: function sss_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "app-startup":
case "app-startup":
Services.obs.addObserver(this, "final-ui-startup", true);
Services.obs.addObserver(this, "quit-application", true);
break;
case "final-ui-startup":
case "final-ui-startup":
Services.obs.removeObserver(this, "final-ui-startup");
Services.obs.removeObserver(this, "quit-application");
this.init();
@ -206,7 +206,7 @@ SessionStartup.prototype = {
var wType = aWindow.document.documentElement.getAttribute("windowtype");
if (wType != "navigator:browser")
return;
/**
* Note: this relies on the fact that nsBrowserContentHandler will return
* a different value the first time its getter is called after an update,

File diff suppressed because it is too large Load Diff

View File

@ -132,6 +132,8 @@ _BROWSER_TEST_FILES = \
browser_701377.js \
browser_705597.js \
browser_707862.js \
browser_739531.js \
browser_739531_sample.html \
browser_739805.js \
$(NULL)

View File

@ -15,17 +15,17 @@
<input type="radio" name="group" checked> Radio 3
<h3>Selects</h3>
<select name="any">
<option value="1"> Select 1
<option value="some"> Select 2
<select name="any">
<option value="1"> Select 1
<option value="some"> Select 2
<option>Select 3
</select>
<select multiple="multiple">
<select multiple="multiple">
<option value=1> Multi-select 1
<option value=2> Multi-select 2
<option value=2> Multi-select 2
<option value=3> Multi-select 3
<option value=4> Multi-select 4
</select>
</select>
<h3>Text Areas</h3>
<textarea name="testarea"></textarea>

View File

@ -4,30 +4,30 @@
function test() {
/** Test for Bug 339445 **/
waitForExplicitFinish();
let testURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_339445_sample.html";
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
let doc = tab.linkedBrowser.contentDocument;
is(doc.getElementById("storageTestItem").textContent, "PENDING",
"sessionStorage value has been set");
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
let doc2 = tab2.linkedBrowser.contentDocument;
is(doc2.getElementById("storageTestItem").textContent, "SUCCESS",
"sessionStorage value has been duplicated");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
}, true);
}, true);

View File

@ -4,7 +4,7 @@
function test() {
/** Test for Bug 345898 **/
function test(aLambda) {
try {
aLambda();

View File

@ -17,7 +17,7 @@ function test() {
file.append("346337_test2.file");
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
var filePath2 = file.path;
let fieldList = {
"//input[@name='input']": Date.now().toString(),
"//input[@name='spaced 1']": Math.random().toString(),
@ -35,13 +35,13 @@ function test() {
"//input[@type='file'][1]": [filePath1],
"//input[@type='file'][2]": [filePath1, filePath2]
};
function getElementByXPath(aTab, aQuery) {
let doc = aTab.linkedBrowser.contentDocument;
let xptype = Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
return doc.evaluate(aQuery, doc, null, xptype, null).singleNodeValue;
}
function setFormValue(aTab, aQuery, aValue) {
let node = getElementByXPath(aTab, aQuery);
if (typeof aValue == "string")
@ -56,7 +56,7 @@ function test() {
Array.forEach(node.options, function(aOpt, aIx)
(aOpt.selected = aValue.indexOf(aIx) > -1));
}
function compareFormValue(aTab, aQuery, aValue) {
let node = getElementByXPath(aTab, aQuery);
if (!node)
@ -77,14 +77,14 @@ function test() {
return Array.every(node.options, function(aOpt, aIx)
(aValue.indexOf(aIx) > -1) == aOpt.selected);
}
// test setup
let tabbrowser = gBrowser;
waitForExplicitFinish();
// make sure we don't save form data at all (except for tab duplication)
gPrefService.setIntPref("browser.sessionstore.privacy_level", 2);
let rootDir = getRootDirectory(gTestPath);
let testURL = rootDir + "browser_346337_sample.html";
let tab = tabbrowser.addTab(testURL);
@ -92,18 +92,18 @@ function test() {
this.removeEventListener("load", arguments.callee, true);
for (let xpath in fieldList)
setFormValue(tab, xpath, fieldList[xpath]);
let tab2 = tabbrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
for (let xpath in fieldList)
ok(compareFormValue(tab2, xpath, fieldList[xpath]),
"The value for \"" + xpath + "\" was correctly restored");
// clean up
tabbrowser.removeTab(tab2);
tabbrowser.removeTab(tab);
tab = undoCloseTab();
tab.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
@ -111,7 +111,7 @@ function test() {
if (fieldList[xpath])
ok(!compareFormValue(tab, xpath, fieldList[xpath]),
"The value for \"" + xpath + "\" was correctly discarded");
if (gPrefService.prefHasUserValue("browser.sessionstore.privacy_level"))
gPrefService.clearUserPref("browser.sessionstore.privacy_level");
// undoCloseTab can reuse a single blank tab, so we have to

View File

@ -15,17 +15,17 @@
<input type="radio" name="group" checked> Radio 3
<h3>Selects</h3>
<select name="any">
<option value="1"> Select 1
<option value="some"> Select 2
<select name="any">
<option value="1"> Select 1
<option value="some"> Select 2
<option>Select 3
</select>
<select multiple="multiple">
<select multiple="multiple">
<option value=1> Multi-select 1
<option value=2> Multi-select 2
<option value=2> Multi-select 2
<option value=3> Multi-select 3
<option value=4> Multi-select 4
</select>
</select>
<h3>Text Areas</h3>
<textarea name="testarea"></textarea>

View File

@ -1,6 +1,6 @@
function test() {
/** Test for Bug 350525 **/
function test(aLambda) {
try {
return aLambda() || true;
@ -16,22 +16,22 @@ function test() {
////////////////////////////
let key = "Unique name: " + Date.now();
let value = "Unique value: " + Math.random();
// test adding
ok(test(function() ss.setWindowValue(window, key, value)), "set a window value");
// test retrieving
is(ss.getWindowValue(window, key), value, "stored window value matches original");
// test deleting
// test deleting
ok(test(function() ss.deleteWindowValue(window, key)), "delete the window value");
// value should not exist post-delete
is(ss.getWindowValue(window, key), "", "window value was deleted");
// test deleting a non-existent value
ok(test(function() ss.deleteWindowValue(window, key)), "delete non-existent window value");
/////////////////////////
// setTabValue, et al. //
/////////////////////////
@ -39,35 +39,35 @@ function test() {
value = "Unique value: " + Date.now();
let tab = gBrowser.addTab();
tab.linkedBrowser.stop();
// test adding
ok(test(function() ss.setTabValue(tab, key, value)), "store a tab value");
// test retrieving
is(ss.getTabValue(tab, key), value, "stored tab value match original");
// test deleting
// test deleting
ok(test(function() ss.deleteTabValue(tab, key)), "delete the tab value");
// value should not exist post-delete
is(ss.getTabValue(tab, key), "", "tab value was deleted");
// test deleting a non-existent value
ok(test(function() ss.deleteTabValue(tab, key)), "delete non-existent tab value");
// clean up
gBrowser.removeTab(tab);
/////////////////////////////////////
// getClosedTabCount, undoCloseTab //
/////////////////////////////////////
// get closed tab count
let count = ss.getClosedTabCount(window);
let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo");
ok(0 <= count && count <= max_tabs_undo,
"getClosedTabCount returns zero or at most max_tabs_undo");
// create a new tab
let testURL = "about:";
tab = gBrowser.addTab(testURL);
@ -75,22 +75,22 @@ function test() {
this.removeEventListener("load", arguments.callee, true);
// make sure that the next closed tab will increase getClosedTabCount
gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1);
// remove tab
gBrowser.removeTab(tab);
// getClosedTabCount
var newcount = ss.getClosedTabCount(window);
ok(newcount > count, "after closing a tab, getClosedTabCount has been incremented");
// undoCloseTab
tab = test(function() ss.undoCloseTab(window, 0));
ok(tab, "undoCloseTab doesn't throw")
tab.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
is(this.currentURI.spec, testURL, "correct tab was reopened");
// clean up
if (gPrefService.prefHasUserValue("browser.sessionstore.max_tabs_undo"))
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");

View File

@ -6,29 +6,29 @@ function test() {
/** Test for Bug 367052 **/
waitForExplicitFinish();
// make sure that the next closed tab will increase getClosedTabCount
let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo");
gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1);
let closedTabCount = ss.getClosedTabCount(window);
// restore a blank tab
let tab = gBrowser.addTab("about:");
tab.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
let history = tab.linkedBrowser.webNavigation.sessionHistory;
ok(history.count >= 1, "the new tab does have at least one history entry");
ss.setTabState(tab, JSON.stringify({ entries: [] }));
tab.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
ok(history.count == 0, "the tab was restored without any history whatsoever");
gBrowser.removeTab(tab);
ok(ss.getClosedTabCount(window) == closedTabCount,
"The closed blank tab wasn't added to Recently Closed Tabs");
// clean up
if (gPrefService.prefHasUserValue("browser.sessionstore.max_tabs_undo"))
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");

View File

@ -2,14 +2,14 @@ function test() {
/** Test for Bug 393716 **/
waitForExplicitFinish();
/////////////////
// getTabState //
/////////////////
let key = "Unique key: " + Date.now();
let value = "Unique value: " + Math.random();
let testURL = "about:config";
// create a new tab
let tab = gBrowser.addTab(testURL);
ss.setTabValue(tab, key, value);
@ -18,7 +18,7 @@ function test() {
// get the tab's state
let state = ss.getTabState(tab);
ok(state, "get the tab's state");
// verify the tab state's integrity
state = JSON.parse(state);
ok(state instanceof Object && state.entries instanceof Array && state.entries.length > 0,
@ -27,11 +27,11 @@ function test() {
"Got the expected state object (test URL)");
ok(state.extData && state.extData[key] == value,
"Got the expected state object (test manually set tab value)");
// clean up
gBrowser.removeTab(tab);
}, true);
//////////////////////////////////
// setTabState and duplicateTab //
//////////////////////////////////
@ -39,7 +39,7 @@ function test() {
let value2 = "Value " + Math.random();
let value3 = "Another value: " + Date.now();
let state = { entries: [{ url: testURL }], extData: { key2: value2 } };
// create a new tab
let tab2 = gBrowser.addTab();
// set the tab's state
@ -49,15 +49,15 @@ function test() {
// verify the correctness of the restored tab
ok(ss.getTabValue(tab2, key2) == value2 && this.currentURI.spec == testURL,
"the tab's state was correctly restored");
// add text data
let textbox = this.contentDocument.getElementById("textbox");
textbox.value = value3;
// duplicate the tab
let duplicateTab = ss.duplicateTab(window, tab2);
gBrowser.removeTab(tab2);
duplicateTab.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
// verify the correctness of the duplicated tab
@ -65,7 +65,7 @@ function test() {
"correctly duplicated the tab's state");
let textbox = this.contentDocument.getElementById("textbox");
is(textbox.value, value3, "also duplicated text data");
// clean up
gBrowser.removeTab(duplicateTab);
finish();

View File

@ -74,7 +74,7 @@ function test() {
}
]
};
// set browser to test state
ss.setBrowserState(JSON.stringify(testState));
@ -93,7 +93,7 @@ function test() {
is(win.selected, 1, "Selected tab has changed");
is(win.title, REMEMBER, "The window title was correctly updated");
// Test more complicated case
// Test more complicated case
win = closedWindowData[1];
is(win.tabs.length, 3, "2 tabs were removed");
is(countOpenTabsByTitle(win.tabs, FORGET), 0,

View File

@ -4,14 +4,14 @@
function test() {
/** Test for Bug 408470 **/
waitForExplicitFinish();
let pendingCount = 1;
let rootDir = getRootDirectory(gTestPath);
let testUrl = rootDir + "browser_408470_sample.html";
let tab = gBrowser.addTab(testUrl);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
// enable all stylesheets and verify that they're correctly persisted
@ -26,18 +26,18 @@ function test() {
let states = Array.map(newTab.linkedBrowser.contentDocument.styleSheets,
function(aSS) !aSS.disabled);
let correct = states.indexOf(true) == aIx && states.indexOf(true, aIx + 1) == -1;
if (/^fail_/.test(ssTitle))
ok(!correct, "didn't restore stylesheet " + ssTitle);
else
ok(correct, "restored stylesheet " + ssTitle);
gBrowser.removeTab(newTab);
if (--pendingCount == 0)
finish();
}, true);
});
// disable all styles and verify that this is correctly persisted
tab.linkedBrowser.markupDocumentViewer.authorStyleDisabled = true;
let newTab = gBrowser.duplicateTab(tab);
@ -45,12 +45,12 @@ function test() {
newTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
is(newTab.linkedBrowser.markupDocumentViewer.authorStyleDisabled, true,
"disabled all stylesheets");
gBrowser.removeTab(newTab);
if (--pendingCount == 0)
finish();
}, true);
gBrowser.removeTab(tab);
}, true);
}

View File

@ -4,34 +4,34 @@
function test() {
/** Test for Bug 447951 **/
waitForExplicitFinish();
const baseURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_447951_sample.html#";
let tab = gBrowser.addTab();
tab.linkedBrowser.addEventListener("load", function(aEvent) {
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
let tabState = { entries: [] };
let max_entries = gPrefService.getIntPref("browser.sessionhistory.max_entries");
for (let i = 0; i < max_entries; i++)
tabState.entries.push({ url: baseURL + i });
ss.setTabState(tab, JSON.stringify(tabState));
tab.addEventListener("SSTabRestored", function(aEvent) {
tab.removeEventListener("SSTabRestored", arguments.callee, false);
tabState = JSON.parse(ss.getTabState(tab));
is(tabState.entries.length, max_entries, "session history filled to the limit");
is(tabState.entries[0].url, baseURL + 0, "... but not more");
// visit yet another anchor (appending it to session history)
let doc = tab.linkedBrowser.contentDocument;
let event = doc.createEvent("MouseEvents");
event.initMouseEvent("click", true, true, doc.defaultView, 1,
0, 0, 0, 0, false, false, false, false, 0, null);
doc.querySelector("a").dispatchEvent(event);
executeSoon(function() {
tabState = JSON.parse(ss.getTabState(tab));
is(tab.linkedBrowser.currentURI.spec, baseURL + "end",
@ -40,7 +40,7 @@ function test() {
"... and ignored");
is(tabState.entries[0].url, baseURL + 1,
"... and the first item was removed");
// clean up
gBrowser.removeTab(tab);
finish();

View File

@ -4,17 +4,17 @@
function test() {
/** Test for Bug 454908 **/
waitForExplicitFinish();
let fieldValues = {
username: "User " + Math.random(),
passwd: "pwd" + Date.now()
};
// make sure we do save form data
gPrefService.setIntPref("browser.sessionstore.privacy_level", 0);
let rootDir = getRootDirectory(gTestPath);
let testURL = rootDir + "browser_454908_sample.html";
let tab = gBrowser.addTab(testURL);
@ -23,9 +23,9 @@ function test() {
let doc = tab.linkedBrowser.contentDocument;
for (let id in fieldValues)
doc.getElementById(id).value = fieldValues[id];
gBrowser.removeTab(tab);
tab = undoCloseTab();
tab.linkedBrowser.addEventListener("load", function(aEvent) {
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
@ -37,7 +37,7 @@ function test() {
else
is(node.value, fieldValues[id], "username was saved/restored");
}
// clean up
if (gPrefService.prefHasUserValue("browser.sessionstore.privacy_level"))
gPrefService.clearUserPref("browser.sessionstore.privacy_level");

View File

@ -4,12 +4,12 @@
function test() {
/** Test for Bug 456342 **/
waitForExplicitFinish();
// make sure we do save form data
gPrefService.setIntPref("browser.sessionstore.privacy_level", 0);
let rootDir = getRootDirectory(gTestPath);
let testURL = rootDir + "browser_456342_sample.xhtml";
let tab = gBrowser.addTab(testURL);
@ -24,10 +24,10 @@ function test() {
formEls[i].value = expectedValue;
gBrowser.removeTab(tab);
let undoItems = JSON.parse(ss.getClosedTabData(window));
let savedFormData = undoItems[0].state.entries[0].formdata;
let countGood = 0, countBad = 0;
for each (let value in savedFormData.id) {
if (value == expectedValue)
@ -44,7 +44,7 @@ function test() {
is(countGood, 4, "Saved text for non-standard input fields");
is(countBad, 0, "Didn't save text for ignored field types");
// clean up
if (gPrefService.prefHasUserValue("browser.sessionstore.privacy_level"))
gPrefService.clearUserPref("browser.sessionstore.privacy_level");

View File

@ -23,7 +23,7 @@
document.getElementsByTagName("iframe")[0].onload =
function() { documentInjected = true; };
frames[0].location = "browser_459906_empty.html";
// ... and ensure that it has time to load
for (var c = 0; !documentInjected && c < 20; c++) {
var r = new XMLHttpRequest();

View File

@ -4,9 +4,9 @@
function test() {
/** Test for Bug 463205 **/
waitForExplicitFinish();
let rootDir = "http://mochi.test:8888/browser/browser/components/sessionstore/test/";
let testURL = rootDir + "browser_463205_sample.html";
@ -18,7 +18,7 @@ function test() {
let frame3URL = "data:text/html,mark2";
let frameCount = 0;
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
// wait for all frames to load completely
@ -50,15 +50,15 @@ function test() {
return;
}
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
function typeText(aTextField, aValue) {
aTextField.value = aValue;
let event = aTextField.ownerDocument.createEvent("UIEvents");
event.initUIEvent("input", true, true, aTextField.ownerDocument.defaultView, 0);
aTextField.dispatchEvent(event);
}
let uniqueValue = "Unique: " + Math.random();
let win = tab.linkedBrowser.contentWindow;
typeText(win.frames[0].document.getElementById("original"), uniqueValue);
@ -112,11 +112,11 @@ function test() {
"subframes must match URL to get text restored");
is(win.frames[1].document.getElementById("original").value, uniqueValue,
"text still gets restored for all other subframes");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
}, true);
}, true);

View File

@ -11,7 +11,7 @@
<script type="application/javascript">
frames[2].addEventListener("DOMContentLoaded", function() {
frames[2].removeEventListener("DOMContentLoaded", arguments.callee, false);
if (frames[2].document.location.href == "data:text/html,mark1") {
frames[2].document.location = "data:text/html,mark2";
}

View File

@ -4,12 +4,12 @@
function test() {
/** Test for Bug 463206 **/
waitForExplicitFinish();
let testURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_463206_sample.html";
var frameCount = 0;
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
@ -17,20 +17,20 @@ function test() {
if (frameCount++ < 5)
return;
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
function typeText(aTextField, aValue) {
aTextField.value = aValue;
let event = aTextField.ownerDocument.createEvent("UIEvents");
event.initUIEvent("input", true, true, aTextField.ownerDocument.defaultView, 0);
aTextField.dispatchEvent(event);
}
let doc = tab.linkedBrowser.contentDocument;
typeText(doc.getElementById("out1"), Date.now());
typeText(doc.getElementsByName("1|#out2")[0], Math.random());
typeText(doc.defaultView.frames[0].frames[1].document.getElementById("in1"), new Date());
frameCount = 0;
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("load", function(aEvent) {
@ -53,11 +53,11 @@ function test() {
// "id prefixes aren't mixed up");
is(win.frames[1].frames[0].document.getElementById("in1").value, "",
"id prefixes aren't mixed up");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
}, true);
}, true);

View File

@ -46,7 +46,7 @@ function test() {
gPrefService.setIntPref("browser.sessionstore.max_tabs_undo",
test_state.windows[0]._closedTabs.length);
ss.setWindowState(newWin, JSON.stringify(test_state), true);
let closedTabs = JSON.parse(ss.getClosedTabData(newWin));
is(closedTabs.length, test_state.windows[0]._closedTabs.length,
"Closed tab list has the expected length");

View File

@ -9,7 +9,7 @@
var targetUrl = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_464620_xd.html";
var firstPass;
function setup() {
if (firstPass !== undefined)
return;
@ -19,7 +19,7 @@
}
frames[1].location = targetUrl;
}
function step() {
var x = frames[0].document.getElementById("x");
if (x.value == "")
@ -27,15 +27,15 @@
x.style.display = "none";
frames[0].document.designMode = "on";
}
function xss() {
step();
var documentInjected = false;
document.getElementsByTagName("iframe")[0].onload =
function() { documentInjected = true; };
frames[0].location = targetUrl;
for (var c = 0; !documentInjected && c < 20; c++) {
var r = new XMLHttpRequest();
r.open("GET", location.href, false);
@ -43,7 +43,7 @@
r.send(null);
}
document.getElementById("state").textContent = "done";
var event = document.createEvent("MessageEvent");
event.initMessageEvent("464620_a", true, false, "done", location.href, "", window);
document.dispatchEvent(event);

View File

@ -4,12 +4,12 @@
function test() {
/** Test for Bug 464620 (injection on input) **/
waitForExplicitFinish();
let testURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_464620_a.html";
var frameCount = 0;
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
@ -17,14 +17,14 @@ function test() {
if (frameCount++ < 4)
return;
this.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
frameCount = 0;
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("464620_a", function(aEvent) {
tab2.linkedBrowser.removeEventListener("464620_a", arguments.callee, true);
is(aEvent.data, "done", "XSS injection was attempted");
// let form restoration complete and take into account the
// setTimeout(..., 0) in sss_restoreDocument_proxy
executeSoon(function() {
@ -34,11 +34,11 @@ function test() {
"cross domain document was loaded");
ok(!/XXX/.test(win.frames[0].document.body.innerHTML),
"no content was injected");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
}, 0);
});

View File

@ -10,7 +10,7 @@
var targetUrl = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_464620_xd.html";
var firstPass;
function setup() {
if (firstPass !== undefined)
return;
@ -21,25 +21,25 @@
}
frames[2].location = targetUrl;
}
function step() {
frames[0].document.designMode = "on";
if (firstPass)
return;
var body = frames[0].document.body;
body.addEventListener("DOMNodeInserted", function() {
body.removeEventListener("DOMNodeInserted", arguments.callee, true);
xss();
}, true);
}
function xss() {
var documentInjected = false;
document.getElementsByTagName("iframe")[1].onload =
function() { documentInjected = true; };
frames[1].location = targetUrl;
for (var c = 0; !documentInjected && c < 20; c++) {
var r = new XMLHttpRequest();
r.open("GET", location.href, false);
@ -47,7 +47,7 @@
r.send(null);
}
document.getElementById("state").textContent = "done";
var event = document.createEvent("MessageEvent");
event.initMessageEvent("464620_b", true, false, "done", location.href, "", window);
document.dispatchEvent(event);

View File

@ -4,12 +4,12 @@
function test() {
/** Test for Bug 464620 (injection on DOM node insertion) **/
waitForExplicitFinish();
let testURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_464620_b.html";
var frameCount = 0;
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
@ -17,14 +17,14 @@ function test() {
if (frameCount++ < 6)
return;
this.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
frameCount = 0;
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("464620_b", function(aEvent) {
tab2.linkedBrowser.removeEventListener("464620_b", arguments.callee, true);
is(aEvent.data, "done", "XSS injection was attempted");
// let form restoration complete and take into account the
// setTimeout(..., 0) in sss_restoreDocument_proxy
executeSoon(function() {
@ -34,11 +34,11 @@ function test() {
"cross domain document was loaded");
ok(!/XXX/.test(win.frames[1].document.body.innerHTML),
"no content was injected");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
}, 0);
});

View File

@ -4,32 +4,32 @@
function test() {
/** Test for Bug 465215 **/
waitForExplicitFinish();
let uniqueName = "bug 465215";
let uniqueValue1 = "as good as unique: " + Date.now();
let uniqueValue2 = "as good as unique: " + Math.random();
// set a unique value on a new, blank tab
let tab1 = gBrowser.addTab();
tab1.linkedBrowser.addEventListener("load", function() {
tab1.linkedBrowser.removeEventListener("load", arguments.callee, true);
ss.setTabValue(tab1, uniqueName, uniqueValue1);
// duplicate the tab with that value
let tab2 = ss.duplicateTab(window, tab1);
is(ss.getTabValue(tab2, uniqueName), uniqueValue1, "tab value was duplicated");
ss.setTabValue(tab2, uniqueName, uniqueValue2);
isnot(ss.getTabValue(tab1, uniqueName), uniqueValue2, "tab values aren't sync'd");
// overwrite the tab with the value which should remove it
ss.setTabState(tab1, JSON.stringify({ entries: [] }));
tab1.linkedBrowser.addEventListener("load", function() {
tab1.linkedBrowser.removeEventListener("load", arguments.callee, true);
is(ss.getTabValue(tab1, uniqueName), "", "tab value was cleared");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab1);

View File

@ -4,7 +4,7 @@
function test() {
/** Test for Bug 466937 **/
waitForExplicitFinish();
var file = Components.classes["@mozilla.org/file/directory_service;1"]
@ -13,17 +13,17 @@ function test() {
file.append("466937_test.file");
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
let testPath = file.path;
let testURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_466937_sample.html";
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
let doc = tab.linkedBrowser.contentDocument;
doc.getElementById("reverse_thief").value = "/home/user/secret2";
doc.getElementById("bystander").value = testPath;
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("load", function(aEvent) {
tab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
@ -34,11 +34,11 @@ function test() {
"text field value wasn't set to full file path");
is(doc.getElementById("bystander").value, testPath,
"normal case: file path was correctly preserved");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
}, true);
}, true);

View File

@ -5,7 +5,7 @@
function test() {
/** Test for Bug 477657 **/
waitForExplicitFinish();
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
newWin.addEventListener("load", function(aEvent) {
newWin.removeEventListener("load", arguments.callee, false);

View File

@ -4,11 +4,11 @@
function test() {
/** Test for Bug 485482 **/
waitForExplicitFinish();
let uniqueValue = Math.random();
let rootDir = getRootDirectory(gTestPath);
let testURL = rootDir + "browser_485482_sample.html";
let tab = gBrowser.addTab(testURL);
@ -17,7 +17,7 @@ function test() {
let doc = tab.linkedBrowser.contentDocument;
doc.querySelector("input[type=text]").value = uniqueValue;
doc.querySelector("input[type=checkbox]").checked = true;
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("load", function(aEvent) {
tab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
@ -26,7 +26,7 @@ function test() {
"generated XPath expression was valid");
ok(doc.querySelector("input[type=checkbox]").checked,
"generated XPath expression was valid");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);

View File

@ -4,11 +4,11 @@
function test() {
/** Test for Bug 485563 **/
waitForExplicitFinish();
let uniqueValue = Math.random() + "\u2028Second line\u2029Second paragraph\u2027";
let tab = gBrowser.addTab();
tab.linkedBrowser.addEventListener("load", function(aEvent) {
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
@ -20,7 +20,7 @@ function test() {
ss.setTabState(tab, JSON.stringify(tabState));
is(ss.getTabValue(tab, "bug485563"), uniqueValue,
"unicode line separator was correctly preserved");
gBrowser.removeTab(tab);
finish();
}, true);

View File

@ -4,10 +4,10 @@
function test() {
/** Test for Bug 491577 **/
// test setup
waitForExplicitFinish();
const REMEMBER = Date.now(), FORGET = Math.random();
let test_state = {
windows: [ { tabs: [{ entries: [{ url: "http://example.com/" }] }], selected: 1 } ],
@ -64,10 +64,10 @@ function test() {
]
};
let remember_count = 1;
function countByTitle(aClosedWindowList, aTitle)
aClosedWindowList.filter(function(aData) aData.title == aTitle).length;
function testForError(aFunction) {
try {
aFunction();
@ -77,7 +77,7 @@ function test() {
return ex.name == "NS_ERROR_ILLEGAL_VALUE";
}
}
// open a window and add the above closed window list
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
newWin.addEventListener("load", function(aEvent) {
@ -85,7 +85,7 @@ function test() {
gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
test_state._closedWindows.length);
ss.setWindowState(newWin, JSON.stringify(test_state), true);
let closedWindows = JSON.parse(ss.getClosedWindowData());
is(closedWindows.length, test_state._closedWindows.length,
"Closed window list has the expected length");
@ -94,17 +94,17 @@ function test() {
"The correct amount of windows are to be forgotten");
is(countByTitle(closedWindows, REMEMBER), remember_count,
"Everything is set up.");
// all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
ok(testForError(function() ss.forgetClosedWindow(-1)),
"Invalid window for forgetClosedWindow throws");
ok(testForError(function() ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
"Invalid window for forgetClosedWindow throws");
// Remove third window, then first window
ss.forgetClosedWindow(2);
ss.forgetClosedWindow(null);
closedWindows = JSON.parse(ss.getClosedWindowData());
is(closedWindows.length, remember_count,
"The correct amount of windows were removed");

View File

@ -9,7 +9,7 @@ function test() {
tab.linkedBrowser.stop();
let tabState = JSON.parse(ss.getTabState(tab));
is(tabState.disallow || "", "", "Everything is allowed per default");
// collect all permissions that can be set on a docShell (i.e. all
// attributes starting with "allow" such as "allowJavascript") and
// disallow them all, as SessionStore only remembers disallowed ones
@ -21,7 +21,7 @@ function test() {
docShell[attribute] = false;
}
}
// make sure that all available permissions have been remembered
tabState = JSON.parse(ss.getTabState(tab));
let disallow = tabState.disallow.split(",");
@ -30,6 +30,6 @@ function test() {
});
// IF A TEST FAILS, please add the missing permission's name (without the
// leading "allow") to nsSessionStore.js's CAPABILITIES array. Thanks.
gBrowser.removeTab(tab);
}

View File

@ -4,7 +4,7 @@
function test() {
/** Test for Bug 495495 **/
waitForExplicitFinish();
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no,toolbar=yes");

View File

@ -25,7 +25,7 @@ function test() {
return -1;
}
// delete existing sessionstore.js, to make sure we're not reading
// delete existing sessionstore.js, to make sure we're not reading
// the mtime of an old one initialy
let (sessionStoreJS = getSessionstoreFile()) {
if (sessionStoreJS.exists())
@ -62,7 +62,7 @@ function test() {
tab.linkedBrowser.contentWindow.scrollTo(1100, 1200);
setTimeout(function step2(e) {
let mtime2 = getSessionstorejsModificationTime();
is(mtime2, mtime1,
is(mtime2, mtime1,
"tab selection and scrolling: sessionstore.js not updated");
// ok, done, cleanup and finish

View File

@ -4,7 +4,7 @@
function test() {
/** Test for Bug 526613 **/
// test setup
waitForExplicitFinish();

View File

@ -12,7 +12,7 @@ function test() {
};
let pageData = {
url: "about:sessionrestore",
formdata: { id: { "sessionData": oldState } }
formdata: { id: { "sessionData": oldState } }
};
let state = { windows: [{ tabs: [{ entries: [pageData] }] }] };

View File

@ -36,7 +36,7 @@ function test() {
}
function windowObserver(aSubject, aTopic, aData) {
if (aTopic == "domwindowopened") {
if (aTopic == "domwindowopened") {
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);

View File

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// This test ensures that attempts made to save/restore ("duplicate") pages
// using designmode AND make changes to document structure (remove body)
// don't result in uncaught errors and a broken browser state.
function test() {
waitForExplicitFinish();
let testURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_739531_sample.html";
let loadCount = 0;
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function onLoad(aEvent) {
// make sure both the page and the frame are loaded
if (++loadCount < 2)
return;
tab.linkedBrowser.removeEventListener("load", onLoad, true);
// executeSoon to allow the JS to execute on the page
executeSoon(function() {
let tab2;
let caughtError = false;
try {
tab2 = ss.duplicateTab(window, tab);
}
catch (e) {
caughtError = true;
info(e);
}
is(gBrowser.tabs.length, 3, "there should be 3 tabs")
ok(!caughtError, "duplicateTab didn't throw");
// if the test fails, we don't want to try to close a tab that doesn't exist
if (tab2)
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
});
}, true);
}

View File

@ -0,0 +1,24 @@
<!-- originally a crash test for bug 713417
https://bug713417.bugzilla.mozilla.org/attachment.cgi?id=584240 -->
<!DOCTYPE html>
<html>
<head>
<script>
function boom()
{
var w = document.getElementById("f").contentWindow;
var d = w.document;
d.designMode = 'on';
var r = d.documentElement;
d.removeChild(r);
document.adoptNode(r);
}
</script>
</head>
<body onload="boom();">
<iframe src="data:text/html,1" id="f"></iframe>
</body>
</html>

View File

@ -9,6 +9,7 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const FRAME_STEP_CACHE_DURATION = 100; // ms
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
Cu.import("resource:///modules/source-editor.jsm");
@ -341,6 +342,7 @@ function StackFrames() {
this._onResume = this._onResume.bind(this);
this._onFrames = this._onFrames.bind(this);
this._onFramesCleared = this._onFramesCleared.bind(this);
this._afterFramesCleared = this._afterFramesCleared.bind(this);
}
StackFrames.prototype = {
@ -369,6 +371,8 @@ StackFrames.prototype = {
* The next function in the initialization sequence.
*/
connect: function SF_connect(aCallback) {
window.addEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
this.activeThread.addListener("paused", this._onPaused);
this.activeThread.addListener("resumed", this._onResume);
this.activeThread.addListener("framesadded", this._onFrames);
@ -383,6 +387,8 @@ StackFrames.prototype = {
* Disconnect from the client.
*/
disconnect: function SF_disconnect() {
window.removeEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
if (!this.activeThread) {
return;
}
@ -431,12 +437,24 @@ StackFrames.prototype = {
* Handler for the thread client's framescleared notification.
*/
_onFramesCleared: function SF__onFramesCleared() {
DebuggerView.StackFrames.emptyText();
// After each frame step (in, over, out), framescleared is fired, which
// forces the UI to be emptied and rebuilt on framesadded. Most of the times
// this is not necessary, and will result in a brief redraw flicker.
// To avoid it, invalidate the UI only after a short time if necessary.
window.setTimeout(this._afterFramesCleared, FRAME_STEP_CACHE_DURATION);
this.selectedFrame = null;
},
// Clear the properties as well.
DebuggerView.Properties.localScope.empty();
DebuggerView.Properties.globalScope.empty();
/**
* Called soon after the thread client's framescleared notification.
*/
_afterFramesCleared: function SF__afterFramesCleared() {
if (!this.activeThread.cachedFrames.length) {
DebuggerView.StackFrames.emptyText();
DebuggerView.Properties.localScope.empty();
DebuggerView.Properties.globalScope.empty();
DebuggerController.dispatchEvent("Debugger:AfterFramesCleared");
}
},
/**
@ -500,6 +518,9 @@ StackFrames.prototype = {
editor.setDebugLocation(-1);
}
// Start recording any added variables or properties in any scope.
DebuggerView.Properties.createHierarchyStore();
// Display the local variables.
let localScope = DebuggerView.Properties.localScope;
localScope.empty();
@ -539,6 +560,13 @@ StackFrames.prototype = {
DebuggerController.dispatchEvent("Debugger:FetchedVariables");
},
/**
* Called afters variables have been fetched after a frame was selected.
*/
_onFetchedVars: function SF__onFetchedVars() {
DebuggerView.Properties.commitHierarchy();
},
/**
* Adds an 'onexpand' callback for a variable, lazily handling the addition of
* new properties.
@ -631,6 +659,18 @@ StackFrames.prototype = {
return aFrame["calleeName"] ? aFrame["calleeName"] : "(anonymous)";
}
return "(" + aFrame.type + ")";
},
/**
* Evaluate an expression in the context of the selected frame. This is used
* for modifying the value of variables in scope.
*
* @param string aExpression
* The expression to evaluate.
*/
evaluate: function SF_evaluate(aExpression) {
let frame = this.activeThread.cachedFrames[this.selectedFrame];
this.activeThread.eval(frame.actor, aExpression);
}
};
@ -709,6 +749,11 @@ SourceScripts.prototype = {
* Handler for the debugger client's unsolicited newScript notification.
*/
_onNewScript: function SS__onNewScript(aNotification, aPacket) {
// Ignore scripts generated from 'clientEvaluate' packets.
if (aPacket.url == "debugger eval code") {
return;
}
this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
},

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const PROPERTY_VIEW_FLASH_DURATION = 400; // ms
/**
* Object mediating visual changes and event listeners between the debugger and
* the html view.
@ -482,12 +484,12 @@ StackFramesView.prototype = {
// If we're paused, show a pause label and a resume label on the button.
if (aState == "paused") {
resume.label = L10N.getStr("resumeLabel");
resume.setAttribute("tooltiptext", L10N.getStr("resumeTooltip"));
resume.setAttribute("checked", true);
}
// If we're attached, do the opposite.
else if (aState == "attached") {
resume.label = L10N.getStr("pauseLabel");
resume.setAttribute("tooltiptext", L10N.getStr("pauseTooltip"));
resume.removeAttribute("checked");
}
@ -511,11 +513,11 @@ StackFramesView.prototype = {
// Make sure the container is empty first.
this.empty();
let item = document.createElement("div");
let item = document.createElement("label");
// The empty node should look grayed out to avoid confusion.
item.className = "empty list-item";
item.appendChild(document.createTextNode(L10N.getStr("emptyStackText")));
item.className = "list-item empty";
item.setAttribute("value", L10N.getStr("emptyStackText"));
this._frames.appendChild(item);
},
@ -540,21 +542,25 @@ StackFramesView.prototype = {
return null;
}
let frame = document.createElement("div");
let frameName = document.createElement("span");
let frameDetails = document.createElement("span");
let frame = document.createElement("box");
let frameName = document.createElement("label");
let frameDetails = document.createElement("label");
// Create a list item to be added to the stackframes container.
frame.id = "stackframe-" + aDepth;
frame.className = "dbg-stackframe list-item";
// This list should display the name and details for the frame.
frameName.className = "dbg-stackframe-name";
frameDetails.className = "dbg-stackframe-details";
frameName.appendChild(document.createTextNode(aFrameNameText));
frameDetails.appendChild(document.createTextNode(aFrameDetailsText));
frameName.className = "dbg-stackframe-name plain";
frameDetails.className = "dbg-stackframe-details plain";
frameName.setAttribute("value", aFrameNameText);
frameDetails.setAttribute("value", aFrameDetailsText);
let spacer = document.createElement("spacer");
spacer.setAttribute("flex", "1");
frame.appendChild(frameName);
frame.appendChild(spacer);
frame.appendChild(frameDetails);
this._frames.appendChild(frame);
@ -825,7 +831,7 @@ PropertiesView.prototype = {
// Contains generic nodes and functionality.
let element = this._createPropertyElement(aName, aId, "variable",
aScope.querySelector(".details"));
aScope.getElementsByClassName("details")[0]);
// Make sure the element was created successfully.
if (!element) {
@ -844,21 +850,41 @@ PropertiesView.prototype = {
// Setup the additional elements specific for a variable node.
element.refresh(function() {
let separator = document.createElement("span");
let info = document.createElement("span");
let title = element.querySelector(".title");
let arrow = element.querySelector(".arrow");
let separatorLabel = document.createElement("label");
let valueLabel = document.createElement("label");
let title = element.getElementsByClassName("title")[0];
// Separator shouldn't be selectable.
separator.className = "unselectable";
separator.appendChild(document.createTextNode(": "));
// Separator between the variable name and its value.
separatorLabel.className = "plain";
separatorLabel.setAttribute("value", ":");
// The variable information (type, class and/or value).
info.className = "info";
valueLabel.className = "value plain";
title.appendChild(separator);
title.appendChild(info);
// Handle the click event when pressing the element value label.
valueLabel.addEventListener("click", this._activateElementInputMode.bind({
scope: this,
element: element,
valueLabel: valueLabel
}));
// Maintain the symbolic name of the variable.
Object.defineProperty(element, "token", {
value: aName,
writable: false,
enumerable: true,
configurable: true
});
title.appendChild(separatorLabel);
title.appendChild(valueLabel);
// Remember a simple hierarchy between the parent and the element.
this._saveHierarchy({
parent: aScope,
element: element,
valueLabel: valueLabel
});
}.bind(this));
// Return the element for later use if necessary.
@ -898,19 +924,37 @@ PropertiesView.prototype = {
aGrip = { type: "null" };
}
let info = aVar.querySelector(".info") || aVar.target.info;
let valueLabel = aVar.getElementsByClassName("value")[0];
// Make sure the info node exists.
if (!info) {
// Make sure the value node exists.
if (!valueLabel) {
return null;
}
info.textContent = this._propertyString(aGrip);
info.classList.add(this._propertyColor(aGrip));
this._applyGrip(valueLabel, aGrip);
return aVar;
},
/**
* Applies the necessary text content and class name to a value node based
* on a grip.
*
* @param object aValueLabel
* The value node to apply the changes to.
* @param object aGrip
* @see DebuggerView.Properties._setGrip
*/
_applyGrip: function DVP__applyGrip(aValueLabel, aGrip) {
let prevGrip = aValueLabel.currentGrip;
if (prevGrip) {
aValueLabel.classList.remove(this._propertyColor(prevGrip));
}
aValueLabel.setAttribute("value", this._propertyString(aGrip));
aValueLabel.classList.add(this._propertyColor(aGrip));
aValueLabel.currentGrip = aGrip;
},
/**
* Adds multiple properties to a specified variable.
* This function handles two types of properties: data properties and
@ -1000,7 +1044,7 @@ PropertiesView.prototype = {
// Contains generic nodes and functionality.
let element = this._createPropertyElement(aName, aId, "property",
aVar.querySelector(".details"));
aVar.getElementsByClassName("details")[0]);
// Make sure the element was created successfully.
if (!element) {
@ -1019,40 +1063,51 @@ PropertiesView.prototype = {
// Setup the additional elements specific for a variable node.
element.refresh(function(pKey, pGrip) {
let propertyString = this._propertyString(pGrip);
let propertyColor = this._propertyColor(pGrip);
let key = document.createElement("div");
let value = document.createElement("div");
let separator = document.createElement("span");
let title = element.querySelector(".title");
let arrow = element.querySelector(".arrow");
// Use a key element to specify the property name.
key.className = "key";
key.appendChild(document.createTextNode(pKey));
// Use a value element to specify the property value.
value.className = "value";
value.appendChild(document.createTextNode(propertyString));
value.classList.add(propertyColor);
// Separator shouldn't be selected.
separator.className = "unselectable";
separator.appendChild(document.createTextNode(": "));
let title = element.getElementsByClassName("title")[0];
let nameLabel = title.getElementsByClassName("name")[0];
let separatorLabel = document.createElement("label");
let valueLabel = document.createElement("label");
if ("undefined" !== typeof pKey) {
title.appendChild(key);
// Use a key element to specify the property name.
nameLabel.className = "key plain";
nameLabel.setAttribute("value", pKey.trim());
title.appendChild(nameLabel);
}
if ("undefined" !== typeof pGrip) {
title.appendChild(separator);
title.appendChild(value);
// Separator between the variable name and its value.
separatorLabel.className = "plain";
separatorLabel.setAttribute("value", ":");
// Use a value element to specify the property value.
valueLabel.className = "value plain";
this._applyGrip(valueLabel, pGrip);
title.appendChild(separatorLabel);
title.appendChild(valueLabel);
}
// Make the property also behave as a variable, to allow
// recursively adding properties to properties.
element.target = {
info: value
};
// Handle the click event when pressing the element value label.
valueLabel.addEventListener("click", this._activateElementInputMode.bind({
scope: this,
element: element,
valueLabel: valueLabel
}));
// Maintain the symbolic name of the property.
Object.defineProperty(element, "token", {
value: aVar.token + "['" + pKey + "']",
writable: false,
enumerable: true,
configurable: true
});
// Remember a simple hierarchy between the parent and the element.
this._saveHierarchy({
parent: aVar,
element: element,
valueLabel: valueLabel
});
// Save the property to the variable for easier access.
Object.defineProperty(aVar, pKey, { value: element,
@ -1065,6 +1120,109 @@ PropertiesView.prototype = {
return element;
},
/**
* Makes an element's (variable or priperty) value editable.
* Make sure 'this' is bound to an object containing the properties:
* {
* "scope": the original scope to be used, probably DebuggerView.Properties,
* "element": the element whose value should be made editable,
* "valueLabel": the label displaying the value
* }
*
* @param event aEvent [optional]
* The event requesting this action.
*/
_activateElementInputMode: function DVP__activateElementInputMode(aEvent) {
if (aEvent) {
aEvent.stopPropagation();
}
let self = this.scope;
let element = this.element;
let valueLabel = this.valueLabel;
let titleNode = valueLabel.parentNode;
let initialValue = valueLabel.getAttribute("value");
// When editing an object we need to collapse it first, in order to avoid
// displaying an inconsistent state while the user is editing.
element._previouslyExpanded = element.expanded;
element._preventExpand = true;
element.collapse();
element.forceHideArrow();
// Create a texbox input element which will be shown in the current
// element's value location.
let textbox = document.createElement("textbox");
textbox.setAttribute("value", initialValue);
textbox.className = "element-input";
textbox.width = valueLabel.clientWidth + 1;
// Save the new value when the texbox looses focus or ENTER is pressed.
function DVP_element_textbox_blur(aTextboxEvent) {
DVP_element_textbox_save();
}
function DVP_element_textbox_keyup(aTextboxEvent) {
if (aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_LEFT ||
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_RIGHT ||
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_UP ||
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_DOWN) {
return;
}
if (aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_RETURN ||
aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_ENTER) {
DVP_element_textbox_save();
return;
}
if (aTextboxEvent.keyCode === aTextboxEvent.DOM_VK_ESCAPE) {
valueLabel.setAttribute("value", initialValue);
DVP_element_textbox_clear();
return;
}
}
// The actual save mechanism for the new variable/property value.
function DVP_element_textbox_save() {
if (textbox.value !== valueLabel.getAttribute("value")) {
valueLabel.setAttribute("value", textbox.value);
let expr = "(" + element.token + "=" + textbox.value + ")";
DebuggerController.StackFrames.evaluate(expr);
}
DVP_element_textbox_clear();
}
// Removes the event listeners and appends the value node again.
function DVP_element_textbox_clear() {
element._preventExpand = false;
if (element._previouslyExpanded) {
element._previouslyExpanded = false;
element.expand();
}
element.showArrow();
textbox.removeEventListener("blur", DVP_element_textbox_blur, false);
textbox.removeEventListener("keyup", DVP_element_textbox_keyup, false);
titleNode.removeChild(textbox);
titleNode.appendChild(valueLabel);
}
textbox.addEventListener("blur", DVP_element_textbox_blur, false);
textbox.addEventListener("keyup", DVP_element_textbox_keyup, false);
titleNode.removeChild(valueLabel);
titleNode.appendChild(textbox);
textbox.select();
// When the value is a string (displayed as "value"), then we probably want
// to change it to another string in the textbox, so to avoid typing the ""
// again, tackle with the selection bounds just a bit.
if (valueLabel.getAttribute("value").match(/^"[^"]*"$/)) {
textbox.selectionEnd--;
textbox.selectionStart++;
}
},
/**
* Returns a custom formatted property string for a type and a value.
*
@ -1129,6 +1287,8 @@ PropertiesView.prototype = {
/**
* Creates an element which contains generic nodes and functionality used by
* any scope, variable or property added to the tree.
* If the variable or property already exists, null is returned.
* Otherwise, the newly created element is returned.
*
* @param string aName
* A generic name used in a title strip.
@ -1150,11 +1310,12 @@ PropertiesView.prototype = {
return null;
}
let element = document.createElement("div");
let arrow = document.createElement("span");
let name = document.createElement("span");
let title = document.createElement("div");
let details = document.createElement("div");
let element = document.createElement("vbox");
let arrow = document.createElement("box");
let name = document.createElement("label");
let title = document.createElement("box");
let details = document.createElement("vbox");
// Create a scope node to contain all the elements.
element.id = aId;
@ -1165,16 +1326,24 @@ PropertiesView.prototype = {
arrow.style.visibility = "hidden";
// The name element.
name.className = "name unselectable";
name.appendChild(document.createTextNode(aName || ""));
name.className = "name plain";
name.setAttribute("value", aName || "");
// The title element, containing the arrow and the name.
title.className = "title";
title.addEventListener("click", function() { element.toggle(); }, true);
title.setAttribute("align", "center")
// The node element which will contain any added scope variables.
details.className = "details";
// Add the click event handler for the title, or arrow and name.
if (aClass === "scope") {
title.addEventListener("click", function() { element.toggle(); }, false);
} else {
arrow.addEventListener("click", function() { element.toggle(); }, false);
name.addEventListener("click", function() { element.toggle(); }, false);
}
title.appendChild(arrow);
title.appendChild(name);
@ -1217,6 +1386,9 @@ PropertiesView.prototype = {
* The same element.
*/
element.expand = function DVP_element_expand() {
if (element._preventExpand) {
return;
}
arrow.setAttribute("open", "");
details.setAttribute("open", "");
@ -1232,6 +1404,9 @@ PropertiesView.prototype = {
* The same element.
*/
element.collapse = function DVP_element_collapse() {
if (element._preventCollapse) {
return;
}
arrow.removeAttribute("open");
details.removeAttribute("open");
@ -1261,39 +1436,49 @@ PropertiesView.prototype = {
* The same element.
*/
element.showArrow = function DVP_element_showArrow() {
if (details.childNodes.length) {
if (element._forceShowArrow || details.childNodes.length) {
arrow.style.visibility = "visible";
}
return element;
};
/**
* Forces the element expand/collapse arrow to be visible, even if there
* are no child elements.
*
* @param boolean aPreventHideFlag
* Prevents the arrow to be hidden when requested.
* @return object
* The same element.
*/
element.forceShowArrow = function DVP_element_forceShowArrow(aPreventHideFlag) {
element._preventHide = aPreventHideFlag;
arrow.style.visibility = "visible";
return element;
};
/**
* Hides the element expand/collapse arrow.
* @return object
* The same element.
*/
element.hideArrow = function DVP_element_hideArrow() {
if (!element._preventHide) {
if (!element._forceShowArrow) {
arrow.style.visibility = "hidden";
}
return element;
};
/**
* Forces the element expand/collapse arrow to be visible, even if there
* are no child elements.
*
* @return object
* The same element.
*/
element.forceShowArrow = function DVP_element_forceShowArrow() {
element._forceShowArrow = true;
arrow.style.visibility = "visible";
return element;
};
/**
* Forces the element expand/collapse arrow to be hidden, even if there
* are some child elements.
*
* @return object
* The same element.
*/
element.forceHideArrow = function DVP_element_forceHideArrow() {
arrow.style.visibility = "hidden";
return element;
};
/**
* Returns if the element is visible.
* @return boolean
@ -1336,7 +1521,7 @@ PropertiesView.prototype = {
* The same element.
*/
element.empty = function DVP_element_empty() {
// this details node won't have any elements, so hide the arrow
// This details node won't have any elements, so hide the arrow.
arrow.style.visibility = "hidden";
while (details.firstChild) {
details.removeChild(details.firstChild);
@ -1362,6 +1547,24 @@ PropertiesView.prototype = {
return element;
};
/**
* Returns if the element expander (arrow) is visible.
* @return boolean
* True if the arrow is visible.
*/
Object.defineProperty(element, "arrowVisible", {
get: function DVP_element_getArrowVisible() {
return arrow.style.visibility !== "hidden";
},
set: function DVP_element_setExpanded(value) {
if (value) {
element.showArrow();
} else {
element.hideArrow();
}
}
});
/**
* Generic function refreshing the internal state of the element when
* it's modified (e.g. a child detail, variable, property is added).
@ -1377,8 +1580,8 @@ PropertiesView.prototype = {
}
let node = aParent.parentNode;
let arrow = node.querySelector(".arrow");
let children = node.querySelector(".details").childNodes.length;
let arrow = node.getElementsByClassName("arrow")[0];
let children = node.getElementsByClassName("details")[0].childNodes.length;
// If the parent details node has at least one element, set the
// expand/collapse arrow visible.
@ -1393,6 +1596,95 @@ PropertiesView.prototype = {
return element;
},
/**
* Remember a simple hierarchy of parent->element->children.
*
* @param object aProperties
* Container for the parent, element and the associated value node.
*/
_saveHierarchy: function DVP__saveHierarchy(aProperties) {
let parent = aProperties.parent;
let element = aProperties.element;
let valueLabel = aProperties.valueLabel;
let store = aProperties.store || parent._children;
// Make sure we have a valid element and a children storage object.
if (!element || !store) {
return;
}
let relation = {
root: parent ? (parent._root || parent) : null,
parent: parent || null,
element: element,
valueLabel: valueLabel,
children: {}
};
store[element.id] = relation;
element._root = relation.root;
element._children = relation.children;
},
/**
* Creates an object to store a hierarchy of scopes, variables and properties
* and saves the previous store.
*/
createHierarchyStore: function DVP_createHierarchyStore() {
this._prevHierarchy = this._currHierarchy;
this._currHierarchy = {};
this._saveHierarchy({ element: this._globalScope, store: this._currHierarchy });
this._saveHierarchy({ element: this._localScope, store: this._currHierarchy });
this._saveHierarchy({ element: this._withScope, store: this._currHierarchy });
this._saveHierarchy({ element: this._closureScope, store: this._currHierarchy });
},
/**
* Briefly flash the variables that changed between pauses.
*/
commitHierarchy: function DVS_commitHierarchy() {
for (let i in this._currHierarchy) {
let currScope = this._currHierarchy[i];
let prevScope = this._prevHierarchy[i];
for (let v in currScope.children) {
let currVar = currScope.children[v];
let prevVar = prevScope.children[v];
let action = "";
if (prevVar) {
let prevValue = prevVar.valueLabel.getAttribute("value");
let currValue = currVar.valueLabel.getAttribute("value");
if (currValue != prevValue) {
action = "changed";
} else {
action = "unchanged";
}
} else {
action = "added";
}
if (action) {
currVar.element.setAttribute(action, "");
window.setTimeout(function() {
currVar.element.removeAttribute(action);
}, PROPERTY_VIEW_FLASH_DURATION);
}
}
}
},
/**
* A simple model representation of all the scopes, variables and properties,
* with parent-child relations.
*/
_currHierarchy: null,
_prevHierarchy: null,
/**
* Returns the global scope container.
*/
@ -1494,6 +1786,8 @@ PropertiesView.prototype = {
* Initialization function, called when the debugger is initialized.
*/
initialize: function DVP_initialize() {
this.createHierarchyStore();
this._vars = document.getElementById("variables");
this._localScope = this._addScope(L10N.getStr("localScope")).expand();
this._withScope = this._addScope(L10N.getStr("withScope")).hide();
@ -1505,6 +1799,8 @@ PropertiesView.prototype = {
* Destruction function, called when the debugger is shut down.
*/
destroy: function DVP_destroy() {
this._currHierarchy = null;
this._prevHierarchy = null;
this._vars = null;
this._globalScope = null;
this._localScope = null;

View File

@ -4,133 +4,38 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Debugger content
*/
.dbg-default {
cursor: default;
}
/**
* Stack frames
*/
#stack {
width: 200px;
}
#stackframes {
overflow: auto;
}
.dbg-stackframe {
display: block;
}
.dbg-stackframe-name {
float: left;
}
.dbg-stackframe-details {
float: right;
}
.dbg-stackframe-name:-moz-locale-dir(rtl) {
float: right;
}
.dbg-stackframe-details:-moz-locale-dir(rtl) {
float: left;
}
/**
* Properties elements
*/
#properties {
width: 250px;
}
#variables {
overflow: auto;
}
/**
* Scope element
* Scope, variable and property elements
*/
.scope {
display: -moz-box;
-moz-box-orient: vertical;
}
.scope > .details {
.details {
display: none;
}
.scope > .details[open] {
.details[open] {
display: -moz-box;
-moz-box-orient: vertical;
}
/**
* Variable element
* Toolbar
*/
.variable {
display: -moz-box;
-moz-box-orient: vertical;
}
.variable > .title {
white-space: nowrap;
}
.variable > .details {
.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
display: none;
}
.variable > .details[open] {
display: -moz-box;
-moz-box-orient: vertical;
}
/**
* Property element
*/
.property {
display: -moz-box;
-moz-box-orient: vertical;
}
.property > .title {
white-space: nowrap;
}
.property > .title > .key {
display: inline-block;
}
.property > .title > .value {
display: inline-block;
}
.property > .details {
display: none;
}
.property > .details[open] {
display: -moz-box;
-moz-box-orient: vertical;
}
/**
* Display helpers
*/
.unselectable {
-moz-user-select: -moz-none;
cursor: default;
}

View File

@ -41,24 +41,29 @@
<vbox id="body" flex="1">
<toolbar id="dbg-toolbar" class="devtools-toolbar">
#ifdef XP_MACOSX
<toolbarbutton id="close" class="devtools-closebutton"/>
<toolbarbutton id="close"
tooltiptext="&debuggerUI.closeButton.tooltip;"
class="devtools-closebutton"/>
#endif
<toolbarbutton id="resume"
class="devtools-toolbarbutton"
type="checkbox"
tabindex="0"/>
<toolbarbutton id="step-over"
class="devtools-toolbarbutton"
label="&debuggerUI.stepOverButton;"
tabindex="0"/>
<toolbarbutton id="step-in"
class="devtools-toolbarbutton"
label="&debuggerUI.stepInButton;"
tabindex="0"/>
<toolbarbutton id="step-out"
class="devtools-toolbarbutton"
label="&debuggerUI.stepOutButton;"
tabindex="0"/>
<hbox id="debugger-controls">
<toolbarbutton id="resume"
class="devtools-toolbarbutton"
type="checkbox"
tabindex="0"/>
<toolbarbutton id="step-over"
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.stepOverButton.tooltip;"
tabindex="0"/>
<toolbarbutton id="step-in"
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.stepInButton.tooltip;"
tabindex="0"/>
<toolbarbutton id="step-out"
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.stepOutButton.tooltip;"
tabindex="0"/>
</hbox>
<menulist id="scripts" class="devtools-menulist"
label="&debuggerUI.emptyScriptText;"/>
<textbox id="scripts-search" type="search"
@ -66,7 +71,9 @@
emptytext="&debuggerUI.emptyFilterText;"/>
<spacer flex="1"/>
#ifndef XP_MACOSX
<toolbarbutton id="close" class="devtools-closebutton"/>
<toolbarbutton id="close"
tooltiptext="&debuggerUI.closeButton.tooltip;"
class="devtools-closebutton"/>
#endif
</toolbar>
<hbox id="dbg-content" flex="1">

View File

@ -30,6 +30,7 @@ _BROWSER_TEST_FILES = \
browser_dbg_propertyview-06.js \
browser_dbg_propertyview-07.js \
browser_dbg_propertyview-08.js \
browser_dbg_propertyview-edit.js \
browser_dbg_panesize.js \
browser_dbg_stack-01.js \
browser_dbg_stack-02.js \

View File

@ -33,7 +33,7 @@ function testAnonCall() {
is(frames.querySelectorAll(".dbg-stackframe").length, 3,
"Should have three frames.");
is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").textContent,
is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").getAttribute("value"),
"anonFunc", "Frame name should be anonFunc");
resumeAndFinish();

View File

@ -23,8 +23,8 @@ function testPause() {
"Should be running after debug_tab_pane.");
let button = gDebugger.document.getElementById("resume");
is(button.label, gDebugger.L10N.getStr("pauseLabel"),
"Button label should be pause when running.");
is(button.getAttribute("tooltiptext"), gDebugger.L10N.getStr("pauseTooltip"),
"Button tooltip should be pause when running.");
gDebugger.DebuggerController.activeThread.addOneTimeListener("paused", function() {
Services.tm.currentThread.dispatch({ run: function() {
@ -35,8 +35,8 @@ function testPause() {
is(gDebugger.DebuggerController.activeThread.paused, true,
"Should be paused after an interrupt request.");
is(button.label, gDebugger.L10N.getStr("resumeLabel"),
"Button label should be resume when paused.");
is(button.getAttribute("tooltiptext"), gDebugger.L10N.getStr("resumeTooltip"),
"Button tooltip should be resume when paused.");
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames when paused in the main loop.");
@ -58,8 +58,8 @@ function testResume() {
"Should be paused after an interrupt request.");
let button = gDebugger.document.getElementById("resume");
is(button.label, gDebugger.L10N.getStr("pauseLabel"),
"Button label should be pause when running.");
is(button.getAttribute("tooltiptext"), gDebugger.L10N.getStr("pauseTooltip"),
"Button tooltip should be pause when running.");
closeDebuggerAndFinish(gTab);
}}, 0);

View File

@ -31,7 +31,7 @@ function testSimpleCall() {
is(testScope.id, "test-scope",
"The newly created scope should have the default id set.");
is(testScope.querySelector(".name").textContent, "test",
is(testScope.querySelector(".name").getAttribute("value"), "test",
"Any new scope should have the designated title.");
is(testScope.querySelector(".details").childNodes.length, 0,

View File

@ -36,13 +36,17 @@ function testSimpleCall() {
is(testVar.id, "test-scope->something-variable",
"The newly created scope should have the default id set.");
is(testVar.querySelector(".name").textContent, "something",
is(testVar.querySelector(".name").getAttribute("value"), "something",
"Any new variable should have the designated title.");
is(testVar.querySelector(".details").childNodes.length, 0,
"Any new variable should have a details container with no child nodes.");
let properties = testVar.addProperties({ "child": { "value": { "type": "object",
"class": "Object" } } });
ok(!testVar.expanded,
"Any new created variable should be initially collapsed.");
@ -84,18 +88,87 @@ function testSimpleCall() {
"The testVar should remember it is collapsed even if it is hidden.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.querySelector(".title"),
testVar.querySelector(".name"),
gDebugger);
ok(testVar.expanded,
"Clicking the testScope tilte should expand it.");
"Clicking the testVar name should expand it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.querySelector(".name"),
gDebugger);
ok(!testVar.expanded,
"Clicking again the testVar name should collapse it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.querySelector(".arrow"),
gDebugger);
ok(testVar.expanded,
"Clicking the testVar arrow should expand it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.querySelector(".arrow"),
gDebugger);
ok(!testVar.expanded,
"Clicking again the testVar arrow should collapse it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.querySelector(".title"),
gDebugger);
ok(!testVar.expanded,
"Clicking again the testScope tilte should collapse it.");
"Clicking the testVar title div shouldn't expand it.");
testScope.show();
testScope.expand();
testVar.show();
testVar.expand();
ok(!testVar.child.expanded,
"The testVar child property should remember it is collapsed even if it is hidden.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.child.querySelector(".key"),
gDebugger);
ok(testVar.child.expanded,
"Clicking the testVar child property name should expand it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.child.querySelector(".key"),
gDebugger);
ok(!testVar.child.expanded,
"Clicking again the testVar child property name should collapse it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.child.querySelector(".arrow"),
gDebugger);
ok(testVar.child.expanded,
"Clicking the testVar child property arrow should expand it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.child.querySelector(".arrow"),
gDebugger);
ok(!testVar.child.expanded,
"Clicking again the testVar child property arrow should collapse it.");
EventUtils.sendMouseEvent({ type: "click" },
testVar.child.querySelector(".title"),
gDebugger);
ok(!testVar.child.expanded,
"Clicking the testVar child property title div shouldn't expand it.");
let emptyCallbackSender = null;

View File

@ -28,7 +28,7 @@ function testSimpleCall() {
testVar.setGrip(1.618);
is(testVar.querySelector(".info").textContent, "1.618",
is(testVar.querySelector(".value").getAttribute("value"), "1.618",
"The grip information for the variable wasn't set correctly.");
is(testVar.querySelector(".details").childNodes.length, 0,
@ -40,7 +40,7 @@ function testSimpleCall() {
is(testVar.querySelector(".details").childNodes.length, 0,
"Adding type and class properties shouldn't add any new tree nodes.");
is(testVar.querySelector(".info").textContent, "[object Window]",
is(testVar.querySelector(".value").getAttribute("value"), "[object Window]",
"The information for the variable wasn't set correctly.");

View File

@ -104,28 +104,28 @@ function testSimpleCall() {
"The localVar5.someProp5 doesn't contain all the created properties.");
is(windowVar.querySelector(".info").textContent, "[object Window]",
is(windowVar.querySelector(".value").getAttribute("value"), "[object Window]",
"The grip information for the windowVar wasn't set correctly.");
is(documentVar.querySelector(".info").textContent, "[object HTMLDocument]",
is(documentVar.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
"The grip information for the documentVar wasn't set correctly.");
is(localVar0.querySelector(".info").textContent, "42",
is(localVar0.querySelector(".value").getAttribute("value"), "42",
"The grip information for the localVar0 wasn't set correctly.");
is(localVar1.querySelector(".info").textContent, "true",
is(localVar1.querySelector(".value").getAttribute("value"), "true",
"The grip information for the localVar1 wasn't set correctly.");
is(localVar2.querySelector(".info").textContent, "\"nasu\"",
is(localVar2.querySelector(".value").getAttribute("value"), "\"nasu\"",
"The grip information for the localVar2 wasn't set correctly.");
is(localVar3.querySelector(".info").textContent, "undefined",
is(localVar3.querySelector(".value").getAttribute("value"), "undefined",
"The grip information for the localVar3 wasn't set correctly.");
is(localVar4.querySelector(".info").textContent, "null",
is(localVar4.querySelector(".value").getAttribute("value"), "null",
"The grip information for the localVar4 wasn't set correctly.");
is(localVar5.querySelector(".info").textContent, "[object Object]",
is(localVar5.querySelector(".value").getAttribute("value"), "[object Object]",
"The grip information for the localVar5 wasn't set correctly.");
gDebugger.DebuggerController.activeThread.resume(function() {

View File

@ -55,37 +55,37 @@ function testFrameParameters()
is(localNodes.length, 11,
"The localScope should contain all the created variable elements.");
is(localNodes[0].querySelector(".info").textContent, "[object Proxy]",
is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Proxy]",
"Should have the right property value for 'this'.");
is(localNodes[1].querySelector(".info").textContent, "[object Object]",
is(localNodes[1].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'aArg'.");
is(localNodes[2].querySelector(".info").textContent, '"beta"',
is(localNodes[2].querySelector(".value").getAttribute("value"), '"beta"',
"Should have the right property value for 'bArg'.");
is(localNodes[3].querySelector(".info").textContent, "3",
is(localNodes[3].querySelector(".value").getAttribute("value"), "3",
"Should have the right property value for 'cArg'.");
is(localNodes[4].querySelector(".info").textContent, "false",
is(localNodes[4].querySelector(".value").getAttribute("value"), "false",
"Should have the right property value for 'dArg'.");
is(localNodes[5].querySelector(".info").textContent, "null",
is(localNodes[5].querySelector(".value").getAttribute("value"), "null",
"Should have the right property value for 'eArg'.");
is(localNodes[6].querySelector(".info").textContent, "undefined",
is(localNodes[6].querySelector(".value").getAttribute("value"), "undefined",
"Should have the right property value for 'fArg'.");
is(localNodes[7].querySelector(".info").textContent, "1",
is(localNodes[7].querySelector(".value").getAttribute("value"), "1",
"Should have the right property value for 'a'.");
is(localNodes[8].querySelector(".info").textContent, "[object Object]",
is(localNodes[8].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'b'.");
is(localNodes[9].querySelector(".info").textContent, "[object Object]",
is(localNodes[9].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'c'.");
is(localNodes[10].querySelector(".info").textContent, "[object Arguments]",
is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Arguments]",
"Should have the right property value for 'arguments'.");
resumeAndFinish();
@ -98,16 +98,15 @@ function testFrameParameters()
}
function resumeAndFinish() {
gDebugger.DebuggerController.activeThread.addOneTimeListener("framescleared", function() {
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._frames;
gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames.");
var frames = gDebugger.DebuggerView.StackFrames._frames;
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames.");
closeDebuggerAndFinish(gTab);
}}, 0);
});
closeDebuggerAndFinish(gTab);
}, true);
gDebugger.DebuggerController.activeThread.resume();
}

View File

@ -53,7 +53,7 @@ function testFrameParameters()
is(localNodes.length, 11,
"The localScope should contain all the created variable elements.");
is(localNodes[0].querySelector(".info").textContent, "[object Proxy]",
is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Proxy]",
"Should have the right property value for 'this'.");
// Expand the 'this', 'arguments' and 'c' tree nodes. This causes
@ -79,34 +79,34 @@ function testFrameParameters()
}
window.clearInterval(intervalID);
is(localNodes[0].querySelector(".property > .title > .key")
.textContent, "__proto__ ",
.getAttribute("value"), "__proto__",
"Should have the right property name for __proto__.");
ok(localNodes[0].querySelector(".property > .title > .value")
.textContent.search(/object/) != -1,
.getAttribute("value").search(/object/) != -1,
"__proto__ should be an object.");
is(localNodes[9].querySelector(".info").textContent, "[object Object]",
is(localNodes[9].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'c'.");
is(localNodes[9].querySelectorAll(".property > .title > .key")[1]
.textContent, "a",
.getAttribute("value"), "a",
"Should have the right property name for 'a'.");
is(localNodes[9].querySelectorAll(".property > .title > .value")[1]
.textContent, 1,
.getAttribute("value"), 1,
"Should have the right value for 'c.a'.");
is(localNodes[10].querySelector(".info").textContent,
is(localNodes[10].querySelector(".value").getAttribute("value"),
"[object Arguments]",
"Should have the right property value for 'arguments'.");
is(localNodes[10].querySelectorAll(".property > .title > .key")[7]
.textContent, "length",
.getAttribute("value"), "length",
"Should have the right property name for 'length'.");
is(localNodes[10].querySelectorAll(".property > .title > .value")[7]
.textContent, 5,
.getAttribute("value"), 5,
"Should have the right argument length.");
resumeAndFinish();
@ -120,19 +120,17 @@ function testFrameParameters()
}
function resumeAndFinish() {
let thread = gDebugger.DebuggerController.activeThread;
thread.addOneTimeListener("framescleared", function() {
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._frames;
gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames.");
var frames = gDebugger.DebuggerView.StackFrames._frames;
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames.");
closeDebuggerAndFinish(gTab);
}}, 0);
});
closeDebuggerAndFinish(gTab);
}, true);
thread.resume();
gDebugger.DebuggerController.activeThread.resume();
}
registerCleanupFunction(function() {

View File

@ -0,0 +1,103 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
function test() {
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.contentWindow;
testFrameEval();
});
}
function testFrameEval() {
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
var localScope = gDebugger.DebuggerView.Properties.localScope,
localNodes = localScope.querySelector(".details").childNodes,
varA = localNodes[7];
is(varA.querySelector(".name").getAttribute("value"), "a",
"Should have the right name for 'a'.");
is(varA.querySelector(".value").getAttribute("value"), 1,
"Should have the right initial value for 'a'.");
testModification(varA, function(aVar) {
testModification(aVar, function(aVar) {
testModification(aVar, function(aVar) {
resumeAndFinish();
}, "document.title", '"Debugger Function Call Parameter Test"');
}, "b", "[object Object]");
}, "{ a: 1 }", "[object Object]");
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
content.document.querySelector("button"),
content.window);
}
function testModification(aVar, aCallback, aNewValue, aNewResult) {
function makeChangesAndExitInputMode() {
EventUtils.sendString(aNewValue);
EventUtils.sendKey("RETURN");
}
EventUtils.sendMouseEvent({ type: "click" },
aVar.querySelector(".value"),
gDebugger);
executeSoon(function() {
ok(aVar.querySelector(".element-input"),
"There should be an input element created.");
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
// Get the variable reference anew, since the old ones were discarded when
// we resumed.
var localScope = gDebugger.DebuggerView.Properties.localScope,
localNodes = localScope.querySelector(".details").childNodes,
varA = localNodes[7];
is(varA.querySelector(".value").getAttribute("value"), aNewResult,
"Should have the right value for 'a'.");
executeSoon(function() {
aCallback(varA);
});
}, false);
makeChangesAndExitInputMode();
});
}
function resumeAndFinish() {
gDebugger.DebuggerController.activeThread.resume(function() {
closeDebuggerAndFinish(gTab);
});
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -36,7 +36,7 @@ function testEvalCall() {
is(childNodes.length, frames.querySelectorAll(".dbg-stackframe").length,
"All children should be frames.");
is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").textContent,
is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").getAttribute("value"),
"(eval)", "Frame name should be (eval)");
ok(frames.querySelector("#stackframe-0").classList.contains("selected"),

View File

@ -37,7 +37,8 @@ function testEvalCallResume() {
"All children should be frames.");
gDebugger.DebuggerController.activeThread.addOneTimeListener("framescleared", function() {
gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames after resume");
@ -49,7 +50,7 @@ function testEvalCallResume() {
"Should have the empty list explanation.");
closeDebuggerAndFinish(gTab);
});
}, true);
gDebugger.DebuggerController.activeThread.resume();
}}, 0);

View File

@ -448,15 +448,16 @@ InspectorUI.prototype = {
this.win = this.browser.contentWindow;
this.winID = this.getWindowID(this.win);
this.toolbar = this.chromeDoc.getElementById("inspector-toolbar");
this.inspectMenuitem = this.chromeDoc.getElementById("Tools:Inspect");
this.inspectToolbutton =
this.chromeDoc.getElementById("inspector-inspect-toolbutton");
this.inspectCommand = this.chromeDoc.getElementById("Inspector:Inspect");
// Update menus:
this.inspectorUICommand = this.chromeDoc.getElementById("Tools:Inspect");
this.inspectorUICommand.setAttribute("checked", "true");
this.chromeWin.Tilt.setup();
this.treePanel = new TreePanel(this.chromeWin, this);
this.toolbar.hidden = false;
this.inspectMenuitem.setAttribute("checked", true);
// initialize the HTML Breadcrumbs
this.breadcrumbs = new HTMLBreadcrumbs(this);
@ -649,7 +650,8 @@ InspectorUI.prototype = {
if (!aKeepInspector)
this.store.deleteInspector(this.winID);
this.inspectMenuitem.removeAttribute("checked");
this.inspectorUICommand.setAttribute("checked", "false");
this.browser = this.win = null; // null out references to browser and window
this.winID = null;
this.selection = null;
@ -658,6 +660,8 @@ InspectorUI.prototype = {
delete this.treePanel;
delete this.stylePanel;
delete this.inspectorUICommand;
delete this.inspectCommand;
delete this.toolbar;
Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null);
@ -677,7 +681,7 @@ InspectorUI.prototype = {
if (this.treePanel && this.treePanel.editingContext)
this.treePanel.closeEditor();
this.inspectToolbutton.checked = true;
this.inspectCommand.setAttribute("checked", "true");
this.inspecting = true;
this.highlighter.unlock();
@ -702,7 +706,7 @@ InspectorUI.prototype = {
return;
}
this.inspectToolbutton.checked = false;
this.inspectCommand.setAttribute("checked", "false");
this.inspecting = false;
if (this.highlighter.getNode()) {

View File

@ -68,6 +68,8 @@ function runSelectionTests(subject)
is(subject.wrappedJSObject, InspectorUI,
"InspectorUI accessible in the observer");
InspectorUI.highlighter.veilContainer.setAttribute("disable-transitions", "true");
executeSoon(function() {
InspectorUI.highlighter.addListener("nodeselected", performTestComparisons);
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);

View File

@ -10,6 +10,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/inspector.jsm");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
Cu.import("resource:///modules/devtools/CssLogic.jsm");
var EXPORTED_SYMBOLS = ["LayoutView"];
@ -24,6 +25,7 @@ function LayoutView(aOptions)
LayoutView.prototype = {
init: function LV_init() {
this.cssLogic = new CssLogic();
this.update = this.update.bind(this);
this.onMessage = this.onMessage.bind(this);
@ -40,13 +42,16 @@ LayoutView.prototype = {
// We update the values when:
// a node is locked
// we get the MozAfterPaint event and the node is locked
function onLock() {
this.undim();
this.update();
// We make sure we never add 2 listeners.
if (!this.trackingPaint) {
this.browser.addEventListener("MozAfterPaint", this.update, true);
this.trackingPaint = true;
function onSelect() {
if (this.inspector.locked) {
this.cssLogic.highlight(this.inspector.selection);
this.undim();
this.update();
// We make sure we never add 2 listeners.
if (!this.trackingPaint) {
this.browser.addEventListener("MozAfterPaint", this.update, true);
this.trackingPaint = true;
}
}
}
@ -56,9 +61,9 @@ LayoutView.prototype = {
this.dim();
}
this.onLock = onLock.bind(this);
this.onSelect= onSelect.bind(this);
this.onUnlock = onUnlock.bind(this);
this.inspector.on("locked", this.onLock);
this.inspector.on("select", this.onSelect);
this.inspector.on("unlocked", this.onUnlock);
// Build the layout view in the sidebar.
@ -118,7 +123,7 @@ LayoutView.prototype = {
* Destroy the nodes. Remove listeners.
*/
destroy: function LV_destroy() {
this.inspector.removeListener("locked", this.onLock);
this.inspector.removeListener("select", this.onSelect);
this.inspector.removeListener("unlocked", this.onUnlock);
this.browser.removeEventListener("MozAfterPaint", this.update, true);
this.iframe.removeEventListener("keypress", this.bound_handleKeypress, true);
@ -158,7 +163,7 @@ LayoutView.prototype = {
// inside the iframe.
if (this.inspector.locked)
this.onLock();
this.onSelect();
else
this.onUnlock();
@ -301,6 +306,16 @@ LayoutView.prototype = {
let selector = this.map[i].selector;
let property = this.map[i].property;
this.map[i].value = parseInt(style.getPropertyValue(property));
}
let margins = this.processMargins(node);
if ("top" in margins) this.map.marginTop.value = "auto";
if ("right" in margins) this.map.marginRight.value = "auto";
if ("bottom" in margins) this.map.marginBottom.value = "auto";
if ("left" in margins) this.map.marginLeft.value = "auto";
for (let i in this.map) {
let selector = this.map[i].selector;
let span = this.doc.querySelector(selector);
span.textContent = this.map[i].value;
}
@ -313,4 +328,21 @@ LayoutView.prototype = {
this.doc.querySelector(".size > span").textContent = width + "x" + height;
},
/**
* Find margins declared 'auto'
*/
processMargins: function LV_processMargins(node) {
let margins = {};
for each (let prop in ["top", "bottom", "left", "right"]) {
let info = this.cssLogic.getPropertyInfo("margin-" + prop);
let selectors = info.matchedSelectors;
if (selectors && selectors.length > 0 && selectors[0].value == "auto") {
margins[prop] = "auto";
}
}
return margins;
},
}

View File

@ -16,9 +16,9 @@ function test() {
{selector: "#element-size", value: "160x160"},
{selector: ".size > span", value: "100x100"},
{selector: ".margin.top > span", value: 30},
{selector: ".margin.left > span", value: 30},
{selector: ".margin.left > span", value: "auto"},
{selector: ".margin.bottom > span", value: 30},
{selector: ".margin.right > span", value: 30},
{selector: ".margin.right > span", value: "auto"},
{selector: ".padding.top > span", value: 20},
{selector: ".padding.left > span", value: 20},
{selector: ".padding.bottom > span", value: 20},
@ -30,16 +30,16 @@ function test() {
];
let res2 = [
{selector: "#element-size", value: "160x210"},
{selector: "#element-size", value: "190x210"},
{selector: ".size > span", value: "100x150"},
{selector: ".margin.top > span", value: 30},
{selector: ".margin.left > span", value: 30},
{selector: ".margin.left > span", value: "auto"},
{selector: ".margin.bottom > span", value: 30},
{selector: ".margin.right > span", value: 50},
{selector: ".margin.right > span", value: "auto"},
{selector: ".padding.top > span", value: 20},
{selector: ".padding.left > span", value: 20},
{selector: ".padding.bottom > span", value: 20},
{selector: ".padding.right > span", value: 20},
{selector: ".padding.right > span", value: 50},
{selector: ".border.top > span", value: 10},
{selector: ".border.left > span", value: 10},
{selector: ".border.bottom > span", value: 10},
@ -53,7 +53,7 @@ function test() {
waitForFocus(setupTest, content);
}, true);
let style = "div { position: absolute; top: 42px; left: 42px; height: 100px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px; }";
let style = "div { position: absolute; top: 42px; left: 42px; height: 100px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px auto;}";
let html = "<style>" + style + "</style><div></div>"
content.location = "data:text/html," + encodeURIComponent(html);
@ -106,7 +106,7 @@ function test() {
}
InspectorUI.selection.style.height = "150px";
InspectorUI.selection.style.marginRight = "50px";
InspectorUI.selection.style.paddingRight = "50px";
setTimeout(test2, 200); // Should be enough to get a MozAfterPaint event
}

View File

@ -487,11 +487,11 @@ var Scratchpad = {
GetStringFromName("propertyPanel.updateButton.label"),
accesskey: this.strings.
GetStringFromName("propertyPanel.updateButton.accesskey"),
oncommand: function () {
oncommand: function _SP_PP_Update_onCommand() {
let [error, result] = self.evalForContext(aEvalString);
if (!error) {
propPanel.treeView.data = result;
propPanel.treeView.data = { object: result };
}
}
});
@ -499,8 +499,9 @@ var Scratchpad = {
let doc = this.browserWindow.document;
let parent = doc.getElementById("mainPopupSet");
let title = aOutputObject.toString();
propPanel = new PropertyPanel(parent, doc, title, aOutputObject, buttons);
let title = String(aOutputObject);
propPanel = new PropertyPanel(parent, title, { object: aOutputObject },
buttons);
let panel = propPanel.panel;
panel.setAttribute("class", "scratchpad_propertyPanel");

View File

@ -25,6 +25,10 @@ function runTest() {
document.getElementById("Tools:DevToolbar").doCommand();
}
function isChecked(b) {
return b.getAttribute("checked") == "true";
}
function checkOpen() {
ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in checkOpen");
@ -35,21 +39,21 @@ function checkOpen() {
ok(close, "Close button exists");
ok(!webconsole.checked, "web console button state 1");
ok(!inspector.checked, "inspector button state 1");
ok(!debuggr.checked, "debugger button state 1");
ok(!isChecked(webconsole), "web console button state 1");
ok(!isChecked(inspector), "inspector button state 1");
ok(!isChecked(debuggr), "debugger button state 1");
document.getElementById("Tools:WebConsole").doCommand();
ok(webconsole.checked, "web console button state 2");
ok(!inspector.checked, "inspector button state 2");
ok(!debuggr.checked, "debugger button state 2");
ok(isChecked(webconsole), "web console button state 2");
ok(!isChecked(inspector), "inspector button state 2");
ok(!isChecked(debuggr), "debugger button state 2");
document.getElementById("Tools:Inspect").doCommand();
ok(webconsole.checked, "web console button state 3");
ok(inspector.checked, "inspector button state 3");
ok(!debuggr.checked, "debugger button state 3");
ok(isChecked(webconsole), "web console button state 3");
ok(isChecked(inspector), "inspector button state 3");
ok(!isChecked(debuggr), "debugger button state 3");
// Christmas tree!
@ -59,24 +63,24 @@ function checkOpen() {
document.getElementById("Tools:WebConsole").doCommand();
ok(!webconsole.checked, "web console button state 6");
ok(inspector.checked, "inspector button state 6");
ok(!debuggr.checked, "debugger button state 6");
ok(!isChecked(webconsole), "web console button state 6");
ok(isChecked(inspector), "inspector button state 6");
ok(!isChecked(debuggr), "debugger button state 6");
document.getElementById("Tools:Inspect").doCommand();
ok(!webconsole.checked, "web console button state 7");
ok(!inspector.checked, "inspector button state 7");
ok(!debuggr.checked, "debugger button state 7");
ok(!isChecked(webconsole), "web console button state 7");
ok(!isChecked(inspector), "inspector button state 7");
ok(!isChecked(debuggr), "debugger button state 7");
// All closed
// Check we can open and close and retain button state
document.getElementById("Tools:Inspect").doCommand();
ok(!webconsole.checked, "web console button state 8");
ok(inspector.checked, "inspector button state 8");
ok(!debuggr.checked, "debugger button state 8");
ok(!isChecked(webconsole), "web console button state 8");
ok(isChecked(inspector), "inspector button state 8");
ok(!isChecked(debuggr), "debugger button state 8");
oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE, catchFail(checkClosed));
document.getElementById("Tools:DevToolbar").doCommand();
@ -99,9 +103,9 @@ function checkReOpen() {
let inspector = document.getElementById("developer-toolbar-inspector");
let debuggr = document.getElementById("developer-toolbar-debugger");
ok(webconsole.checked, "web console button state 9");
ok(inspector.checked, "inspector button state 9");
ok(!debuggr.checked, "debugger button state 9");
ok(isChecked(webconsole), "web console button state 9");
ok(isChecked(inspector), "inspector button state 9");
ok(!isChecked(debuggr), "debugger button state 9");
oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE, catchFail(checkReClosed));
document.getElementById("developer-toolbar-closebutton").doCommand();

View File

@ -23,6 +23,7 @@ let Services = tempScope.Services;
let gConsoleStorage = tempScope.ConsoleAPIStorage;
let WebConsoleUtils = tempScope.WebConsoleUtils;
let l10n = WebConsoleUtils.l10n;
let JSPropertyProvider = tempScope.JSPropertyProvider;
tempScope = null;
let _alive = true; // Track if this content script should still be alive.
@ -32,7 +33,6 @@ let _alive = true; // Track if this content script should still be alive.
*/
let Manager = {
get window() content,
get console() this.window.console,
sandbox: null,
hudId: null,
_sequence: 0,
@ -60,11 +60,7 @@ let Manager = {
// Need to track the owner XUL window to listen to the unload and TabClose
// events, to avoid memory leaks.
let xulWindow = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView;
let xulWindow = this._xulWindow();
xulWindow.addEventListener("unload", this._onXULWindowClose, false);
let tabContainer = xulWindow.gBrowser.tabContainer;
@ -357,6 +353,19 @@ let Manager = {
}
},
/**
* Find the XUL window that owns the content script.
* @private
* @return Window
* The XUL window that owns the content script.
*/
_xulWindow: function Manager__xulWindow()
{
return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView;
},
/**
* Destroy the Web Console content script instance.
*/
@ -366,11 +375,7 @@ let Manager = {
Services.obs.removeObserver(this, "quit-application-granted");
_alive = false;
let xulWindow = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView;
let xulWindow = this._xulWindow();
xulWindow.removeEventListener("unload", this._onXULWindowClose, false);
let tabContainer = xulWindow.gBrowser.tabContainer;
tabContainer.removeEventListener("TabClose", this._onTabClose, false);
@ -389,11 +394,238 @@ let Manager = {
},
};
/**
* JSTerm helper functions.
*
* Defines a set of functions ("helper functions") that are available from the
* Web Console but not from the web page.
*
* A list of helper functions used by Firebug can be found here:
* http://getfirebug.com/wiki/index.php/Command_Line_API
*/
function JSTermHelper(aJSTerm)
{
/**
* Find a node by ID.
*
* @param string aId
* The ID of the element you want.
* @return nsIDOMNode or null
* The result of calling document.getElementById(aId).
*/
aJSTerm.sandbox.$ = function JSTH_$(aId)
{
return aJSTerm.window.document.getElementById(aId);
};
/**
* Find the nodes matching a CSS selector.
*
* @param string aSelector
* A string that is passed to window.document.querySelectorAll.
* @return nsIDOMNodeList
* Returns the result of document.querySelectorAll(aSelector).
*/
aJSTerm.sandbox.$$ = function JSTH_$$(aSelector)
{
return aJSTerm.window.document.querySelectorAll(aSelector);
};
/**
* Runs an xPath query and returns all matched nodes.
*
* @param string aXPath
* xPath search query to execute.
* @param [optional] nsIDOMNode aContext
* Context to run the xPath query on. Uses window.document if not set.
* @returns array of nsIDOMNode
*/
aJSTerm.sandbox.$x = function JSTH_$x(aXPath, aContext)
{
let nodes = [];
let doc = aJSTerm.window.document;
let aContext = aContext || doc;
try {
let results = doc.evaluate(aXPath, aContext, null,
Ci.nsIDOMXPathResult.ANY_TYPE, null);
let node;
while (node = results.iterateNext()) {
nodes.push(node);
}
}
catch (ex) {
aJSTerm.console.error(ex.message);
}
return nodes;
};
/**
* Returns the currently selected object in the highlighter.
*
* Warning: this implementation crosses the process boundaries! This is not
* usable within a remote browser. To implement this feature correctly we need
* support for remote inspection capabilities within the Inspector as well.
*
* @return nsIDOMElement|null
* The DOM element currently selected in the highlighter.
*/
Object.defineProperty(aJSTerm.sandbox, "$0", {
get: function() {
try {
return Manager._xulWindow().InspectorUI.selection;
}
catch (ex) {
aJSTerm.console.error(ex.message);
}
},
enumerable: true,
configurable: false
});
/**
* Clears the output of the JSTerm.
*/
aJSTerm.sandbox.clear = function JSTH_clear()
{
aJSTerm.helperEvaluated = true;
Manager.sendMessage("JSTerm:ClearOutput", {});
};
/**
* Returns the result of Object.keys(aObject).
*
* @param object aObject
* Object to return the property names from.
* @returns array of string
*/
aJSTerm.sandbox.keys = function JSTH_keys(aObject)
{
return Object.keys(WebConsoleUtils.unwrap(aObject));
};
/**
* Returns the values of all properties on aObject.
*
* @param object aObject
* Object to display the values from.
* @returns array of string
*/
aJSTerm.sandbox.values = function JSTH_values(aObject)
{
let arrValues = [];
let obj = WebConsoleUtils.unwrap(aObject);
try {
for (let prop in obj) {
arrValues.push(obj[prop]);
}
}
catch (ex) {
aJSTerm.console.error(ex.message);
}
return arrValues;
};
/**
* Opens a help window in MDN.
*/
aJSTerm.sandbox.help = function JSTH_help()
{
aJSTerm.helperEvaluated = true;
aJSTerm.window.open(
"https://developer.mozilla.org/AppLinks/WebConsoleHelp?locale=" +
aJSTerm.window.navigator.language, "help", "");
};
/**
* Inspects the passed aObject. This is done by opening the PropertyPanel.
*
* @param object aObject
* Object to inspect.
*/
aJSTerm.sandbox.inspect = function JSTH_inspect(aObject)
{
if (!WebConsoleUtils.isObjectInspectable(aObject)) {
return aObject;
}
aJSTerm.helperEvaluated = true;
let message = {
input: aJSTerm._evalInput,
objectCacheId: Manager.sequenceId,
};
message.resultObject =
aJSTerm.prepareObjectForRemote(WebConsoleUtils.unwrap(aObject),
message.objectCacheId);
Manager.sendMessage("JSTerm:InspectObject", message);
};
/**
* Prints aObject to the output.
*
* @param object aObject
* Object to print to the output.
* @return string
*/
aJSTerm.sandbox.pprint = function JSTH_pprint(aObject)
{
aJSTerm.helperEvaluated = true;
if (aObject === null || aObject === undefined || aObject === true ||
aObject === false) {
aJSTerm.console.error(l10n.getStr("helperFuncUnsupportedTypeError"));
return;
}
else if (typeof aObject == "function") {
aJSTerm.helperRawOutput = true;
return aObject + "\n";
}
aJSTerm.helperRawOutput = true;
let output = [];
let pairs = WebConsoleUtils.namesAndValuesOf(WebConsoleUtils.unwrap(aObject));
pairs.forEach(function(aPair) {
output.push(aPair.name + ": " + aPair.value);
});
return " " + output.join("\n ");
};
/**
* Print a string to the output, as-is.
*
* @param string aString
* A string you want to output.
* @returns void
*/
aJSTerm.sandbox.print = function JSTH_print(aString)
{
aJSTerm.helperEvaluated = true;
aJSTerm.helperRawOutput = true;
return String(aString);
};
}
/**
* The JavaScript terminal is meant to allow remote code execution for the Web
* Console.
*/
let JSTerm = {
get window() Manager.window,
get console() this.window.console,
/**
* The Cu.Sandbox() object where code is evaluated.
*/
sandbox: null,
_messageHandlers: {},
/**
* Evaluation result objects are cached in this object. The chrome process can
* request any object based on its ID.
@ -406,16 +638,112 @@ let JSTerm = {
init: function JST_init()
{
this._objectCache = {};
this._messageHandlers = {
"JSTerm:EvalRequest": this.handleEvalRequest,
"JSTerm:GetEvalObject": this.handleGetEvalObject,
"JSTerm:Autocomplete": this.handleAutocomplete,
"JSTerm:ClearObjectCache": this.handleClearObjectCache,
};
Manager.addMessageHandler("JSTerm:GetEvalObject",
this.handleGetEvalObject.bind(this));
Manager.addMessageHandler("JSTerm:ClearObjectCache",
this.handleClearObjectCache.bind(this));
for (let name in this._messageHandlers) {
let handler = this._messageHandlers[name].bind(this);
Manager.addMessageHandler(name, handler);
}
this._createSandbox();
},
/**
* Handler for the "JSTerm:EvalRequest" remote message. This method evaluates
* user input in the JavaScript sandbox and sends the result back to the
* remote process. The "JSTerm:EvalResult" message includes the following
* data:
* - id - the same ID as the EvalRequest (for tracking purposes).
* - input - the JS string that was evaluated.
* - resultString - the evaluation result converted to a string formatted
* for display.
* - timestamp - timestamp when evaluation occurred (Date.now(),
* milliseconds since the UNIX epoch).
* - inspectable - boolean that tells if the evaluation result object can be
* inspected or not.
* - error - the evaluation exception object (if any).
* - errorMessage - the exception object converted to a string (if any error
* occurred).
* - helperResult - boolean that tells if a JSTerm helper was evaluated.
* - helperRawOutput - boolean that tells if the helper evaluation result
* should be displayed as raw output.
*
* If the result object is inspectable then two additional properties are
* included:
* - childrenCacheId - tells where child objects are cached. This is the
* same as aRequest.resultCacheId.
* - resultObject - the result object prepared for the remote process. See
* this.prepareObjectForRemote().
*
* @param object aRequest
* The code evaluation request object:
* - id - request ID.
* - str - string to evaluate.
* - resultCacheId - where to cache the evaluation child objects.
*/
handleEvalRequest: function JST_handleEvalRequest(aRequest)
{
let id = aRequest.id;
let input = aRequest.str;
let result, error = null;
let timestamp;
this.helperEvaluated = false;
this.helperRawOutput = false;
this._evalInput = input;
try {
timestamp = Date.now();
result = this.evalInSandbox(input);
}
catch (ex) {
error = ex;
}
delete this._evalInput;
let inspectable = !error && WebConsoleUtils.isObjectInspectable(result);
let resultString = undefined;
if (!error) {
resultString = this.helperRawOutput ? result :
WebConsoleUtils.formatResult(result);
}
let message = {
id: id,
input: input,
resultString: resultString,
timestamp: timestamp,
error: error,
errorMessage: error ? String(error) : null,
inspectable: inspectable,
helperResult: this.helperEvaluated,
helperRawOutput: this.helperRawOutput,
};
if (inspectable) {
message.childrenCacheId = aRequest.resultCacheId;
message.resultObject =
this.prepareObjectForRemote(result, message.childrenCacheId);
}
Manager.sendMessage("JSTerm:EvalResult", message);
},
/**
* Handler for the remote "JSTerm:GetEvalObject" message. This allows the
* remote Web Console instance to retrieve an object from the content process.
* The "JSTerm:EvalObject" message is sent back to the remote process:
* - id - the request ID, used to trace back to the initial request.
* - cacheId - the cache ID where the requested object is stored.
* - objectId - the ID of the object being sent.
* - object - the object representation prepared for remote inspection. See
* this.prepareObjectForRemote().
* - childrenCacheId - the cache ID where any child object of |object| are
* stored.
*
* @param object aRequest
* The message that requests the content object. Properties: cacheId,
@ -481,8 +809,7 @@ let JSTerm = {
* method in aObject. Each element describes the property. For details
* see WebConsoleUtils.namesAndValuesOf().
*/
prepareObjectForRemote:
function JST_prepareObjectForRemote(aObject, aCacheId)
prepareObjectForRemote: function JST_prepareObjectForRemote(aObject, aCacheId)
{
// Cache the properties that have inspectable values.
let propCache = this._objectCache[aCacheId] || {};
@ -494,14 +821,103 @@ let JSTerm = {
return result;
},
/**
* Handler for the "JSTerm:Autocomplete" remote message. This handler provides
* completion results for user input. The "JSterm:AutocompleteProperties"
* message is sent to the remote process:
* - id - the same as request ID.
* - input - the user input (same as in the request message).
* - matches - an array of matched properties (strings).
* - matchProp - the part that was used from the user input for finding the
* matches. For details see the JSPropertyProvider description and
* implementation.
*
*
* @param object aRequest
* The remote request object which holds two properties: an |id| and
* the user |input|.
*/
handleAutocomplete: function JST_handleAutocomplete(aRequest)
{
let result = JSPropertyProvider(this.window, aRequest.input) || {};
let message = {
id: aRequest.id,
input: aRequest.input,
matches: result.matches || [],
matchProp: result.matchProp,
};
Manager.sendMessage("JSTerm:AutocompleteProperties", message);
},
/**
* Create the JavaScript sandbox where user input is evaluated.
* @private
*/
_createSandbox: function JST__createSandbox()
{
this.sandbox = new Cu.Sandbox(this.window, {
sandboxPrototype: this.window,
wantXrays: false,
});
this.sandbox.console = this.console;
JSTermHelper(this);
},
/**
* Evaluates a string in the sandbox.
*
* @param string aString
* String to evaluate in the sandbox.
* @returns something
* The result of the evaluation.
*/
evalInSandbox: function JST_evalInSandbox(aString)
{
// The help function needs to be easy to guess, so we make the () optional
if (aString.trim() == "help" || aString.trim() == "?") {
aString = "help()";
}
let window = WebConsoleUtils.unwrap(this.sandbox.window);
let $ = null, $$ = null;
// We prefer to execute the page-provided implementations for the $() and
// $$() functions.
if (typeof window.$ == "function") {
$ = this.sandbox.$;
delete this.sandbox.$;
}
if (typeof window.$$ == "function") {
$$ = this.sandbox.$$;
delete this.sandbox.$$;
}
let result = Cu.evalInSandbox(aString, this.sandbox, "1.8",
"Web Console", 1);
if ($) {
this.sandbox.$ = $;
}
if ($$) {
this.sandbox.$$ = $$;
}
return result;
},
/**
* Destroy the JSTerm instance.
*/
destroy: function JST_destroy()
{
Manager.removeMessageHandler("JSTerm:GetEvalObject");
Manager.removeMessageHandler("JSTerm:ClearObjectCache");
for (let name in this._messageHandlers) {
Manager.removeMessageHandler(name);
}
delete this.sandbox;
delete this._messageHandlers;
delete this._objectCache;
},
};

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@ include $(DEPTH)/config/autoconf.mk
EXTRA_JS_MODULES = \
PropertyPanel.jsm \
PropertyPanelAsync.jsm \
NetworkHelper.jsm \
AutocompletePopup.jsm \
WebConsoleUtils.jsm \

View File

@ -4,6 +4,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
@ -11,242 +13,18 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView",
"namesAndValuesOf", "isNonNativeGetter"];
XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", function () {
let obj = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", obj);
return obj.WebConsoleUtils;
});
///////////////////////////////////////////////////////////////////////////
//// Helper for PropertyTreeView
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
const TYPE_OBJECT = 0, TYPE_FUNCTION = 1, TYPE_ARRAY = 2, TYPE_OTHER = 3;
/**
* Figures out the type of aObject and the string to display in the tree.
*
* @param object aObject
* The object to operate on.
* @returns object
* A object with the form:
* {
* type: TYPE_OBJECT || TYPE_FUNCTION || TYPE_ARRAY || TYPE_OTHER,
* display: string for displaying the object in the tree
* }
*/
function presentableValueFor(aObject)
{
if (aObject === null || aObject === undefined) {
return {
type: TYPE_OTHER,
display: aObject === undefined ? "undefined" : "null"
};
}
let presentable;
switch (aObject.constructor && aObject.constructor.name) {
case "Array":
return {
type: TYPE_ARRAY,
display: "Array"
};
case "String":
return {
type: TYPE_OTHER,
display: "\"" + aObject + "\""
};
case "Date":
case "RegExp":
case "Number":
case "Boolean":
return {
type: TYPE_OTHER,
display: aObject
};
case "Iterator":
return {
type: TYPE_OTHER,
display: "Iterator"
};
case "Function":
presentable = aObject.toString();
return {
type: TYPE_FUNCTION,
display: presentable.substring(0, presentable.indexOf(')') + 1)
};
default:
presentable = aObject.toString();
let m = /^\[object (\S+)\]/.exec(presentable);
try {
if (typeof aObject == "object" && typeof aObject.next == "function" &&
m && m[1] == "Generator") {
return {
type: TYPE_OTHER,
display: m[1]
};
}
}
catch (ex) {
// window.history.next throws in the typeof check above.
return {
type: TYPE_OBJECT,
display: m ? m[1] : "Object"
};
}
if (typeof aObject == "object" && typeof aObject.__iterator__ == "function") {
return {
type: TYPE_OTHER,
display: "Iterator"
};
}
return {
type: TYPE_OBJECT,
display: m ? m[1] : "Object"
};
}
}
/**
* Tells if the given function is native or not.
*
* @param function aFunction
* The function you want to check if it is native or not.
*
* @return boolean
* True if the given function is native, false otherwise.
*/
function isNativeFunction(aFunction)
{
return typeof aFunction == "function" && !("prototype" in aFunction);
}
/**
* Tells if the given property of the provided object is a non-native getter or
* not.
*
* @param object aObject
* The object that contains the property.
*
* @param string aProp
* The property you want to check if it is a getter or not.
*
* @return boolean
* True if the given property is a getter, false otherwise.
*/
function isNonNativeGetter(aObject, aProp) {
if (typeof aObject != "object") {
return false;
}
let desc;
while (aObject) {
try {
if (desc = Object.getOwnPropertyDescriptor(aObject, aProp)) {
break;
}
}
catch (ex) {
// Native getters throw here. See bug 520882.
if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
return false;
}
throw ex;
}
aObject = Object.getPrototypeOf(aObject);
}
if (desc && desc.get && !isNativeFunction(desc.get)) {
return true;
}
return false;
}
/**
* Get an array of property name value pairs for the tree.
*
* @param object aObject
* The object to get properties for.
* @returns array of object
* Objects have the name, value, display, type, children properties.
*/
function namesAndValuesOf(aObject)
{
let pairs = [];
let value, presentable;
let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
for (var propName in aObject) {
// See bug 632275: skip deprecated width and height properties.
if (isDOMDocument && (propName == "width" || propName == "height")) {
continue;
}
// Also skip non-native getters.
if (isNonNativeGetter(aObject, propName)) {
value = ""; // Value is never displayed.
presentable = {type: TYPE_OTHER, display: "Getter"};
}
else {
try {
value = aObject[propName];
presentable = presentableValueFor(value);
}
catch (ex) {
continue;
}
}
let pair = {};
pair.name = propName;
pair.display = propName + ": " + presentable.display;
pair.type = presentable.type;
pair.value = value;
// Convert the pair.name to a number for later sorting.
pair.nameNumber = parseFloat(pair.name)
if (isNaN(pair.nameNumber)) {
pair.nameNumber = false;
}
pairs.push(pair);
}
pairs.sort(function(a, b)
{
// Sort numbers.
if (a.nameNumber !== false && b.nameNumber === false) {
return -1;
}
else if (a.nameNumber === false && b.nameNumber !== false) {
return 1;
}
else if (a.nameNumber !== false && b.nameNumber !== false) {
return a.nameNumber - b.nameNumber;
}
// Sort string.
else if (a.name < b.name) {
return -1;
}
else if (a.name > b.name) {
return 1;
}
else {
return 0;
}
});
return pairs;
}
///////////////////////////////////////////////////////////////////////////
//// PropertyTreeView.
/**
* This is an implementation of the nsITreeView interface. For comments on the
* interface properties, see the documentation:
@ -254,30 +32,87 @@ function namesAndValuesOf(aObject)
*/
var PropertyTreeView = function() {
this._rows = [];
this._objectCache = {};
};
PropertyTreeView.prototype = {
/**
* Stores the visible rows of the tree.
* @private
*/
_rows: null,
/**
* Stores the nsITreeBoxObject for this tree.
* @private
*/
_treeBox: null,
/**
* Stores cached information about local objects being inspected.
* @private
*/
_objectCache: null,
/**
* Use this setter to update the content of the tree.
*
* @param object aObject
* The new object to be displayed in the tree.
* @returns void
* @param object aData
* A meta object that holds information about the object you want to
* display in the property panel. Object properties:
* - object:
* This is the raw object you want to display. You can only provide
* this object if you want the property panel to work in sync mode.
* - remoteObject:
* An array that holds information on the remote object being
* inspected. Each element in this array describes each property in the
* remote object. See WebConsoleUtils.namesAndValuesOf() for details.
* - rootCacheId:
* The cache ID where the objects referenced in remoteObject are found.
* - panelCacheId:
* The cache ID where any object retrieved by this property panel
* instance should be stored into.
* - remoteObjectProvider:
* A function that is invoked when a new object is needed. This is
* called when the user tries to expand an inspectable property. The
* callback must take four arguments:
* - fromCacheId:
* Tells from where to retrieve the object the user picked (from
* which cache ID).
* - objectId:
* The object ID the user wants.
* - panelCacheId:
* Tells in which cache ID to store the objects referenced by
* objectId so they can be retrieved later.
* - callback:
* The callback function to be invoked when the remote object is
* received. This function takes one argument: the raw message
* received from the Web Console content script.
*/
set data(aObject) {
set data(aData) {
let oldLen = this._rows.length;
this._rows = this.getChildItems(aObject, true);
this._cleanup();
if (!aData) {
return;
}
if (aData.remoteObject) {
this._rootCacheId = aData.rootCacheId;
this._panelCacheId = aData.panelCacheId;
this._remoteObjectProvider = aData.remoteObjectProvider;
this._rows = [].concat(aData.remoteObject);
this._updateRemoteObject(this._rows, 0);
}
else if (aData.object) {
this._rows = this._inspectObject(aData.object);
}
else {
throw new Error("First argument must have a .remoteObject or " +
"an .object property!");
}
if (this._treeBox) {
this._treeBox.beginUpdateBatch();
if (oldLen) {
@ -289,53 +124,66 @@ PropertyTreeView.prototype = {
},
/**
* Generates the child items for the treeView of a given aItem. If there is
* already a children property on the aItem, this cached one is returned.
* Update a remote object so it can be used with the tree view. This method
* adds properties to each array element.
*
* @param object aItem
* An item of the tree's elements to generate the children for.
* @param boolean aRootElement
* If set, aItem is handled as an JS object and not as an item
* element of the tree.
* @returns array of objects
* Child items of aItem.
* @private
* @param array aObject
* The remote object you want prepared for use with the tree view.
* @param number aLevel
* The level you want to give to each property in the remote object.
*/
getChildItems: function(aItem, aRootElement)
_updateRemoteObject: function PTV__updateRemoteObject(aObject, aLevel)
{
// If item.children is an array, then the children has already been
// computed and can get returned directly.
// Skip this checking if aRootElement is true. It could happen, that aItem
// is passed as ({children:[1,2,3]}) which would be true, although these
// "kind" of children has no value/type etc. data as needed to display in
// the tree. As the passed ({children:[1,2,3]}) are instanceof
// itsWindow.Array and not this modules's global Array
// aItem.children instanceof Array can't be true, but for saftey the
// !aRootElement is kept here.
if (!aRootElement && aItem && aItem.children instanceof Array) {
return aItem.children;
}
aObject.forEach(function(aElement) {
aElement.level = aLevel;
aElement.isOpened = false;
aElement.children = null;
});
},
let pairs;
let newPairLevel;
/**
* Inspect a local object.
*
* @private
* @param object aObject
* The object you want to inspect.
*/
_inspectObject: function PTV__inspectObject(aObject)
{
this._objectCache = {};
this._remoteObjectProvider = this._localObjectProvider.bind(this);
let children = WebConsoleUtils.namesAndValuesOf(aObject, this._objectCache);
this._updateRemoteObject(children, 0);
return children;
},
if (!aRootElement) {
newPairLevel = aItem.level + 1;
aItem = aItem.value;
}
else {
newPairLevel = 0;
}
pairs = namesAndValuesOf(aItem);
for each (var pair in pairs) {
pair.level = newPairLevel;
pair.isOpened = false;
pair.children = pair.type == TYPE_OBJECT || pair.type == TYPE_FUNCTION ||
pair.type == TYPE_ARRAY;
}
return pairs;
/**
* An object provider for when the user inspects local objects (not remote
* ones).
*
* @private
* @param string aFromCacheId
* The cache ID from where to retrieve the desired object.
* @param string aObjectId
* The ID of the object you want.
* @param string aDestCacheId
* The ID of the cache where to store any objects referenced by the
* desired object.
* @param function aCallback
* The function you want to receive the object.
*/
_localObjectProvider:
function PTV__localObjectProvider(aFromCacheId, aObjectId, aDestCacheId,
aCallback)
{
let object = WebConsoleUtils.namesAndValuesOf(this._objectCache[aObjectId],
this._objectCache);
aCallback({cacheId: aFromCacheId,
objectId: aObjectId,
object: object,
childrenCacheId: aDestCacheId || aFromCacheId,
});
},
/** nsITreeView interface implementation **/
@ -344,10 +192,19 @@ PropertyTreeView.prototype = {
get rowCount() { return this._rows.length; },
setTree: function(treeBox) { this._treeBox = treeBox; },
getCellText: function(idx, column) { return this._rows[idx].display; },
getLevel: function(idx) { return this._rows[idx].level; },
isContainer: function(idx) { return !!this._rows[idx].children; },
isContainerOpen: function(idx) { return this._rows[idx].isOpened; },
getCellText: function(idx, column) {
let row = this._rows[idx];
return row.name + ": " + row.value;
},
getLevel: function(idx) {
return this._rows[idx].level;
},
isContainer: function(idx) {
return !!this._rows[idx].inspectable;
},
isContainerOpen: function(idx) {
return this._rows[idx].isOpened;
},
isContainerEmpty: function(idx) { return false; },
isSeparator: function(idx) { return false; },
isSorted: function() { return false; },
@ -359,7 +216,7 @@ PropertyTreeView.prototype = {
if (this.getLevel(idx) == 0) {
return -1;
}
for (var t = idx - 1; t >= 0 ; t--) {
for (var t = idx - 1; t >= 0; t--) {
if (this.isContainer(t)) {
return t;
}
@ -375,13 +232,13 @@ PropertyTreeView.prototype = {
toggleOpenState: function(idx)
{
var item = this._rows[idx];
if (!item.children) {
let item = this._rows[idx];
if (!item.inspectable) {
return;
}
this._treeBox.beginUpdateBatch();
if (item.isOpened) {
this._treeBox.beginUpdateBatch();
item.isOpened = false;
var thisLevel = item.level;
@ -394,18 +251,38 @@ PropertyTreeView.prototype = {
this._rows.splice(idx + 1, deleteCount);
this._treeBox.rowCountChanged(idx + 1, -deleteCount);
}
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
}
else {
item.isOpened = true;
let levelUpdate = true;
let callback = function _onRemoteResponse(aResponse) {
this._treeBox.beginUpdateBatch();
item.isOpened = true;
var toInsert = this.getChildItems(item);
item.children = toInsert;
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(toInsert));
if (levelUpdate) {
this._updateRemoteObject(aResponse.object, item.level + 1);
item.children = aResponse.object;
}
this._treeBox.rowCountChanged(idx + 1, toInsert.length);
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item.children));
this._treeBox.rowCountChanged(idx + 1, item.children.length);
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
}.bind(this);
if (!item.children) {
let fromCacheId = item.level > 0 ? this._panelCacheId :
this._rootCacheId;
this._remoteObjectProvider(fromCacheId, item.objectId,
this._panelCacheId, callback);
}
else {
levelUpdate = false;
callback({object: item.children});
}
}
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
},
getImageSrc: function(idx, column) { },
@ -424,7 +301,21 @@ PropertyTreeView.prototype = {
setCellValue: function(row, col, value) { },
setCellText: function(row, col, value) { },
drop: function(index, orientation, dataTransfer) { },
canDrop: function(index, orientation, dataTransfer) { return false; }
canDrop: function(index, orientation, dataTransfer) { return false; },
_cleanup: function PTV__cleanup()
{
if (this._rows.length) {
// Reset the existing _rows children to the initial state.
this._updateRemoteObject(this._rows, 0);
this._rows = [];
}
delete this._objectCache;
delete this._rootCacheId;
delete this._panelCacheId;
delete this._remoteObjectProvider;
},
};
///////////////////////////////////////////////////////////////////////////
@ -477,21 +368,23 @@ function appendChild(aDocument, aParent, aTag, aAttributes)
/**
* Creates a new PropertyPanel.
*
* @see PropertyTreeView
* @param nsIDOMNode aParent
* Parent node to append the created panel to.
* @param nsIDOMDocument aDocument
* Document to create the new nodes on.
* @param string aTitle
* Title for the panel.
* @param string aObject
* Object to display in the tree.
* Object to display in the tree. For details about this object please
* see the PropertyTreeView constructor in this file.
* @param array of objects aButtons
* Array with buttons to display at the bottom of the panel.
*/
function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
function PropertyPanel(aParent, aTitle, aObject, aButtons)
{
let document = aParent.ownerDocument;
// Create the underlying panel
this.panel = createElement(aDocument, "panel", {
this.panel = createElement(document, "panel", {
label: aTitle,
titlebar: "normal",
noautofocus: "true",
@ -500,13 +393,13 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
});
// Create the tree.
let tree = this.tree = createElement(aDocument, "tree", {
let tree = this.tree = createElement(document, "tree", {
flex: 1,
hidecolumnpicker: "true"
});
let treecols = aDocument.createElement("treecols");
appendChild(aDocument, treecols, "treecol", {
let treecols = document.createElement("treecols");
appendChild(document, treecols, "treecol", {
primary: "true",
flex: 1,
hideheader: "true",
@ -514,18 +407,18 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
});
tree.appendChild(treecols);
tree.appendChild(aDocument.createElement("treechildren"));
tree.appendChild(document.createElement("treechildren"));
this.panel.appendChild(tree);
// Create the footer.
let footer = createElement(aDocument, "hbox", { align: "end" });
appendChild(aDocument, footer, "spacer", { flex: 1 });
let footer = createElement(document, "hbox", { align: "end" });
appendChild(document, footer, "spacer", { flex: 1 });
// The footer can have butttons.
let self = this;
if (aButtons) {
aButtons.forEach(function(button) {
let buttonNode = appendChild(aDocument, footer, "button", {
let buttonNode = appendChild(document, footer, "button", {
label: button.label,
accesskey: button.accesskey || "",
class: button.class || "",
@ -534,7 +427,7 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
});
}
appendChild(aDocument, footer, "resizer", { dir: "bottomend" });
appendChild(document, footer, "resizer", { dir: "bottomend" });
this.panel.appendChild(footer);
aParent.appendChild(this.panel);
@ -559,20 +452,15 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
}
/**
* Destroy the PropertyPanel. This closes the poped up panel and removes
* it from the browser DOM.
*
* @returns void
* Destroy the PropertyPanel. This closes the panel and removes it from the
* browser DOM.
*/
PropertyPanel.prototype.destroy = function PP_destroy()
{
this.treeView.data = null;
this.panel.parentNode.removeChild(this.panel);
this.treeView = null;
this.panel = null;
this.tree = null;
if (this.linkNode) {
this.linkNode._panelOpen = false;
this.linkNode = null;
}
}

View File

@ -1,466 +0,0 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", function () {
let obj = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", obj);
return obj.WebConsoleUtils;
});
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
///////////////////////////////////////////////////////////////////////////
//// PropertyTreeView.
/**
* This is an implementation of the nsITreeView interface. For comments on the
* interface properties, see the documentation:
* https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsITreeView
*/
var PropertyTreeView = function() {
this._rows = [];
this._objectCache = {};
};
PropertyTreeView.prototype = {
/**
* Stores the visible rows of the tree.
* @private
*/
_rows: null,
/**
* Stores the nsITreeBoxObject for this tree.
* @private
*/
_treeBox: null,
/**
* Stores cached information about local objects being inspected.
* @private
*/
_objectCache: null,
/**
* Use this setter to update the content of the tree.
*
* @param object aObject
* An object that holds information about the object you want to
* display in the property panel. Object properties:
* - object:
* This is the raw object you want to display. You can only provide
* this object if you want the property panel to work in sync mode.
* - remoteObject:
* An array that holds information on the remote object being
* inspected. Each element in this array describes each property in the
* remote object. See WebConsoleUtils.namesAndValuesOf() for details.
* - rootCacheId:
* The cache ID where the objects referenced in remoteObject are found.
* - panelCacheId:
* The cache ID where any object retrieved by this property panel
* instance should be stored into.
* - remoteObjectProvider:
* A function that is invoked when a new object is needed. This is
* called when the user tries to expand an inspectable property. The
* callback must take four arguments:
* - fromCacheId:
* Tells from where to retrieve the object the user picked (from
* which cache ID).
* - objectId:
* The object ID the user wants.
* - panelCacheId:
* Tells in which cache ID to store the objects referenced by
* objectId so they can be retrieved later.
* - callback:
* The callback function to be invoked when the remote object is
* received. This function takes one argument: the raw message
* received from the Web Console content script.
*/
set data(aData) {
let oldLen = this._rows.length;
this._cleanup();
if (!aData) {
return;
}
if (aData.remoteObject) {
this._rootCacheId = aData.rootCacheId;
this._panelCacheId = aData.panelCacheId;
this._remoteObjectProvider = aData.remoteObjectProvider;
this._rows = [].concat(aData.remoteObject);
this._updateRemoteObject(this._rows, 0);
}
else if (aData.object) {
this._rows = this._inspectObject(aData.object);
}
else {
throw new Error("First argument must have a .remoteObject or " +
"an .object property!");
}
if (this._treeBox) {
this._treeBox.beginUpdateBatch();
if (oldLen) {
this._treeBox.rowCountChanged(0, -oldLen);
}
this._treeBox.rowCountChanged(0, this._rows.length);
this._treeBox.endUpdateBatch();
}
},
/**
* Update a remote object so it can be used with the tree view. This method
* adds properties to each array element.
*
* @private
* @param array aObject
* The remote object you want prepared for use with the tree view.
* @param number aLevel
* The level you want to give to each property in the remote object.
*/
_updateRemoteObject: function PTV__updateRemoteObject(aObject, aLevel)
{
aObject.forEach(function(aElement) {
aElement.level = aLevel;
aElement.isOpened = false;
aElement.children = null;
});
},
/**
* Inspect a local object.
*
* @private
* @param object aObject
* The object you want to inspect.
*/
_inspectObject: function PTV__inspectObject(aObject)
{
this._objectCache = {};
this._remoteObjectProvider = this._localObjectProvider.bind(this);
let children = WebConsoleUtils.namesAndValuesOf(aObject, this._objectCache);
this._updateRemoteObject(children, 0);
return children;
},
/**
* An object provider for when the user inspects local objects (not remote
* ones).
*
* @private
* @param string aFromCacheId
* The cache ID from where to retrieve the desired object.
* @param string aObjectId
* The ID of the object you want.
* @param string aDestCacheId
* The ID of the cache where to store any objects referenced by the
* desired object.
* @param function aCallback
* The function you want to receive the object.
*/
_localObjectProvider:
function PTV__localObjectProvider(aFromCacheId, aObjectId, aDestCacheId,
aCallback)
{
let object = WebConsoleUtils.namesAndValuesOf(this._objectCache[aObjectId],
this._objectCache);
aCallback({cacheId: aFromCacheId,
objectId: aObjectId,
object: object,
childrenCacheId: aDestCacheId || aFromCacheId,
});
},
/** nsITreeView interface implementation **/
selection: null,
get rowCount() { return this._rows.length; },
setTree: function(treeBox) { this._treeBox = treeBox; },
getCellText: function(idx, column) {
let row = this._rows[idx];
return row.name + ": " + row.value;
},
getLevel: function(idx) {
return this._rows[idx].level;
},
isContainer: function(idx) {
return !!this._rows[idx].inspectable;
},
isContainerOpen: function(idx) {
return this._rows[idx].isOpened;
},
isContainerEmpty: function(idx) { return false; },
isSeparator: function(idx) { return false; },
isSorted: function() { return false; },
isEditable: function(idx, column) { return false; },
isSelectable: function(row, col) { return true; },
getParentIndex: function(idx)
{
if (this.getLevel(idx) == 0) {
return -1;
}
for (var t = idx - 1; t >= 0; t--) {
if (this.isContainer(t)) {
return t;
}
}
return -1;
},
hasNextSibling: function(idx, after)
{
var thisLevel = this.getLevel(idx);
return this._rows.slice(after + 1).some(function (r) r.level == thisLevel);
},
toggleOpenState: function(idx)
{
let item = this._rows[idx];
if (!item.inspectable) {
return;
}
if (item.isOpened) {
this._treeBox.beginUpdateBatch();
item.isOpened = false;
var thisLevel = item.level;
var t = idx + 1, deleteCount = 0;
while (t < this._rows.length && this.getLevel(t++) > thisLevel) {
deleteCount++;
}
if (deleteCount) {
this._rows.splice(idx + 1, deleteCount);
this._treeBox.rowCountChanged(idx + 1, -deleteCount);
}
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
}
else {
let levelUpdate = true;
let callback = function _onRemoteResponse(aResponse) {
this._treeBox.beginUpdateBatch();
item.isOpened = true;
if (levelUpdate) {
this._updateRemoteObject(aResponse.object, item.level + 1);
item.children = aResponse.object;
}
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item.children));
this._treeBox.rowCountChanged(idx + 1, item.children.length);
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
}.bind(this);
if (!item.children) {
let fromCacheId = item.level > 0 ? this._panelCacheId :
this._rootCacheId;
this._remoteObjectProvider(fromCacheId, item.objectId,
this._panelCacheId, callback);
}
else {
levelUpdate = false;
callback({object: item.children});
}
}
},
getImageSrc: function(idx, column) { },
getProgressMode : function(idx,column) { },
getCellValue: function(idx, column) { },
cycleHeader: function(col, elem) { },
selectionChanged: function() { },
cycleCell: function(idx, column) { },
performAction: function(action) { },
performActionOnCell: function(action, index, column) { },
performActionOnRow: function(action, row) { },
getRowProperties: function(idx, column, prop) { },
getCellProperties: function(idx, column, prop) { },
getColumnProperties: function(column, element, prop) { },
setCellValue: function(row, col, value) { },
setCellText: function(row, col, value) { },
drop: function(index, orientation, dataTransfer) { },
canDrop: function(index, orientation, dataTransfer) { return false; },
_cleanup: function PTV__cleanup()
{
if (this._rows.length) {
// Reset the existing _rows children to the initial state.
this._updateRemoteObject(this._rows, 0);
this._rows = [];
}
delete this._objectCache;
delete this._rootCacheId;
delete this._panelCacheId;
delete this._remoteObjectProvider;
},
};
///////////////////////////////////////////////////////////////////////////
//// Helper for creating the panel.
/**
* Creates a DOMNode and sets all the attributes of aAttributes on the created
* element.
*
* @param nsIDOMDocument aDocument
* Document to create the new DOMNode.
* @param string aTag
* Name of the tag for the DOMNode.
* @param object aAttributes
* Attributes set on the created DOMNode.
* @returns nsIDOMNode
*/
function createElement(aDocument, aTag, aAttributes)
{
let node = aDocument.createElement(aTag);
for (var attr in aAttributes) {
node.setAttribute(attr, aAttributes[attr]);
}
return node;
}
/**
* Creates a new DOMNode and appends it to aParent.
*
* @param nsIDOMDocument aDocument
* Document to create the new DOMNode.
* @param nsIDOMNode aParent
* A parent node to append the created element.
* @param string aTag
* Name of the tag for the DOMNode.
* @param object aAttributes
* Attributes set on the created DOMNode.
* @returns nsIDOMNode
*/
function appendChild(aDocument, aParent, aTag, aAttributes)
{
let node = createElement(aDocument, aTag, aAttributes);
aParent.appendChild(node);
return node;
}
///////////////////////////////////////////////////////////////////////////
//// PropertyPanel
/**
* Creates a new PropertyPanel.
*
* @see PropertyTreeView
* @param nsIDOMNode aParent
* Parent node to append the created panel to.
* @param string aTitle
* Title for the panel.
* @param string aObject
* Object to display in the tree. For details about this object please
* see the PropertyTreeView.data property in this file.
* @param array of objects aButtons
* Array with buttons to display at the bottom of the panel.
*/
function PropertyPanel(aParent, aTitle, aObject, aButtons)
{
let document = aParent.ownerDocument;
// Create the underlying panel
this.panel = createElement(document, "panel", {
label: aTitle,
titlebar: "normal",
noautofocus: "true",
noautohide: "true",
close: "true",
});
// Create the tree.
let tree = this.tree = createElement(document, "tree", {
flex: 1,
hidecolumnpicker: "true"
});
let treecols = document.createElement("treecols");
appendChild(document, treecols, "treecol", {
primary: "true",
flex: 1,
hideheader: "true",
ignoreincolumnpicker: "true"
});
tree.appendChild(treecols);
tree.appendChild(document.createElement("treechildren"));
this.panel.appendChild(tree);
// Create the footer.
let footer = createElement(document, "hbox", { align: "end" });
appendChild(document, footer, "spacer", { flex: 1 });
// The footer can have butttons.
let self = this;
if (aButtons) {
aButtons.forEach(function(button) {
let buttonNode = appendChild(document, footer, "button", {
label: button.label,
accesskey: button.accesskey || "",
class: button.class || "",
});
buttonNode.addEventListener("command", button.oncommand, false);
});
}
appendChild(document, footer, "resizer", { dir: "bottomend" });
this.panel.appendChild(footer);
aParent.appendChild(this.panel);
// Create the treeView object.
this.treeView = new PropertyTreeView();
this.treeView.data = aObject;
// Set the treeView object on the tree view. This has to be done *after* the
// panel is shown. This is because the tree binding must be attached first.
this.panel.addEventListener("popupshown", function onPopupShow()
{
self.panel.removeEventListener("popupshown", onPopupShow, false);
self.tree.view = self.treeView;
}, false);
this.panel.addEventListener("popuphidden", function onPopupHide()
{
self.panel.removeEventListener("popuphidden", onPopupHide, false);
self.destroy();
}, false);
}
/**
* Destroy the PropertyPanel. This closes the panel and removes it from the
* browser DOM.
*/
PropertyPanel.prototype.destroy = function PP_destroy()
{
this.treeView.data = null;
this.panel.parentNode.removeChild(this.panel);
this.treeView = null;
this.panel = null;
this.tree = null;
}

View File

@ -6,14 +6,14 @@
"use strict";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
var EXPORTED_SYMBOLS = ["WebConsoleUtils"];
var EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider"];
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
@ -403,7 +403,7 @@ var WebConsoleUtils = {
let m = /^\[object (\S+)\]/.exec(presentable);
try {
if (type == "object" && typeof aObject.next == "function" &&
if (typeof aObject == "object" && typeof aObject.next == "function" &&
m && m[1] == "Generator") {
return {
type: TYPES.GENERATOR,
@ -419,7 +419,8 @@ var WebConsoleUtils = {
};
}
if (type == "object" && typeof aObject.__iterator__ == "function") {
if (typeof aObject == "object" &&
typeof aObject.__iterator__ == "function") {
return {
type: TYPES.ITERATOR,
display: "Iterator"
@ -514,10 +515,11 @@ var WebConsoleUtils = {
let value, presentable;
let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
let deprecated = ["width", "height", "inputEncoding"];
for (let propName in aObject) {
// See bug 632275: skip deprecated width and height properties.
if (isDOMDocument && (propName == "width" || propName == "height")) {
// See bug 632275: skip deprecated properties.
if (isDOMDocument && deprecated.indexOf(propName) > -1) {
continue;
}
@ -527,8 +529,13 @@ var WebConsoleUtils = {
presentable = {type: TYPES.GETTER, display: "Getter"};
}
else {
value = aObject[propName];
presentable = this.presentableValueFor(value);
try {
value = aObject[propName];
presentable = this.presentableValueFor(value);
}
catch (ex) {
continue;
}
}
let pair = {};
@ -704,3 +711,228 @@ WebConsoleUtils.l10n = {
XPCOMUtils.defineLazyGetter(WebConsoleUtils.l10n, "stringBundle", function() {
return Services.strings.createBundle(STRINGS_URI);
});
//////////////////////////////////////////////////////////////////////////
// JS Completer
//////////////////////////////////////////////////////////////////////////
var JSPropertyProvider = (function _JSPP(WCU) {
const STATE_NORMAL = 0;
const STATE_QUOTE = 2;
const STATE_DQUOTE = 3;
const OPEN_BODY = "{[(".split("");
const CLOSE_BODY = "}])".split("");
const OPEN_CLOSE_BODY = {
"{": "}",
"[": "]",
"(": ")",
};
/**
* Analyses a given string to find the last statement that is interesting for
* later completion.
*
* @param string aStr
* A string to analyse.
*
* @returns object
* If there was an error in the string detected, then a object like
*
* { err: "ErrorMesssage" }
*
* is returned, otherwise a object like
*
* {
* state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
* startPos: index of where the last statement begins
* }
*/
function findCompletionBeginning(aStr)
{
let bodyStack = [];
let state = STATE_NORMAL;
let start = 0;
let c;
for (let i = 0; i < aStr.length; i++) {
c = aStr[i];
switch (state) {
// Normal JS state.
case STATE_NORMAL:
if (c == '"') {
state = STATE_DQUOTE;
}
else if (c == "'") {
state = STATE_QUOTE;
}
else if (c == ";") {
start = i + 1;
}
else if (c == " ") {
start = i + 1;
}
else if (OPEN_BODY.indexOf(c) != -1) {
bodyStack.push({
token: c,
start: start
});
start = i + 1;
}
else if (CLOSE_BODY.indexOf(c) != -1) {
var last = bodyStack.pop();
if (!last || OPEN_CLOSE_BODY[last.token] != c) {
return {
err: "syntax error"
};
}
if (c == "}") {
start = i + 1;
}
else {
start = last.start;
}
}
break;
// Double quote state > " <
case STATE_DQUOTE:
if (c == "\\") {
i++;
}
else if (c == "\n") {
return {
err: "unterminated string literal"
};
}
else if (c == '"') {
state = STATE_NORMAL;
}
break;
// Single quote state > ' <
case STATE_QUOTE:
if (c == "\\") {
i++;
}
else if (c == "\n") {
return {
err: "unterminated string literal"
};
}
else if (c == "'") {
state = STATE_NORMAL;
}
break;
}
}
return {
state: state,
startPos: start
};
}
/**
* Provides a list of properties, that are possible matches based on the passed
* scope and inputValue.
*
* @param object aScope
* Scope to use for the completion.
*
* @param string aInputValue
* Value that should be completed.
*
* @returns null or object
* If no completion valued could be computed, null is returned,
* otherwise a object with the following form is returned:
* {
* matches: [ string, string, string ],
* matchProp: Last part of the inputValue that was used to find
* the matches-strings.
* }
*/
function JSPropertyProvider(aScope, aInputValue)
{
let obj = WCU.unwrap(aScope);
// Analyse the aInputValue and find the beginning of the last part that
// should be completed.
let beginning = findCompletionBeginning(aInputValue);
// There was an error analysing the string.
if (beginning.err) {
return null;
}
// If the current state is not STATE_NORMAL, then we are inside of an string
// which means that no completion is possible.
if (beginning.state != STATE_NORMAL) {
return null;
}
let completionPart = aInputValue.substring(beginning.startPos);
// Don't complete on just an empty string.
if (completionPart.trim() == "") {
return null;
}
let properties = completionPart.split(".");
let matchProp;
if (properties.length > 1) {
matchProp = properties.pop().trimLeft();
for (let i = 0; i < properties.length; i++) {
let prop = properties[i].trim();
// If obj is undefined or null, then there is no chance to run completion
// on it. Exit here.
if (typeof obj === "undefined" || obj === null) {
return null;
}
// Check if prop is a getter function on obj. Functions can change other
// stuff so we can't execute them to get the next object. Stop here.
if (WCU.isNonNativeGetter(obj, prop)) {
return null;
}
try {
obj = obj[prop];
}
catch (ex) {
return null;
}
}
}
else {
matchProp = properties[0].trimLeft();
}
// If obj is undefined or null, then there is no chance to run
// completion on it. Exit here.
if (typeof obj === "undefined" || obj === null) {
return null;
}
// Skip Iterators and Generators.
if (WCU.isIteratorOrGenerator(obj)) {
return null;
}
let matches = [];
for (let prop in obj) {
if (prop.indexOf(matchProp) == 0) {
matches.push(prop);
}
}
return {
matchProp: matchProp,
matches: matches.sort(),
};
}
return JSPropertyProvider;
})(WebConsoleUtils);

View File

@ -8,12 +8,14 @@ let HUD;
function test() {
addTab(TEST_URI);
browser.addEventListener("load", tabLoaded, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function tabLoaded() {
browser.removeEventListener("load", tabLoaded, true);
openConsole();
function consoleOpened(aHud) {
HUD = aHud;
content.wrappedJSObject.foobarBug585991 = {
"item0": "value0",
@ -22,16 +24,14 @@ function tabLoaded() {
"item3": "value3",
};
let hudId = HUDService.getHudIdByWindow(content);
HUD = HUDService.hudReferences[hudId];
let jsterm = HUD.jsterm;
let popup = jsterm.autocompletePopup;
let completeNode = jsterm.completeNode;
ok(!popup.isOpen, "popup is not open");
popup._panel.addEventListener("popupshown", function() {
popup._panel.removeEventListener("popupshown", arguments.callee, false);
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
ok(popup.isOpen, "popup is open");
@ -79,7 +79,7 @@ function autocompletePopupHidden()
let completeNode = jsterm.completeNode;
let inputNode = jsterm.inputNode;
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
ok(!popup.isOpen, "popup is not open");
@ -88,8 +88,8 @@ function autocompletePopupHidden()
ok(!completeNode.value, "completeNode is empty");
popup._panel.addEventListener("popupshown", function() {
popup._panel.removeEventListener("popupshown", arguments.callee, false);
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
ok(popup.isOpen, "popup is open");
@ -104,8 +104,8 @@ function autocompletePopupHidden()
is(popup.selectedItem.label, "item0", "item0 is selected");
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
popup._panel.addEventListener("popuphidden", function() {
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
popup._panel.addEventListener("popuphidden", function onHidden() {
popup._panel.removeEventListener("popuphidden", onHidden, false);
ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
@ -135,8 +135,8 @@ function testReturnKey()
let completeNode = jsterm.completeNode;
let inputNode = jsterm.inputNode;
popup._panel.addEventListener("popupshown", function() {
popup._panel.removeEventListener("popupshown", arguments.callee, false);
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
ok(popup.isOpen, "popup is open");
@ -157,8 +157,8 @@ function testReturnKey()
is(popup.selectedItem.label, "item1", "item1 is selected");
is(completeNode.value, prefix + "item1", "completeNode.value holds item1");
popup._panel.addEventListener("popuphidden", function() {
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
popup._panel.addEventListener("popuphidden", function onHidden() {
popup._panel.removeEventListener("popuphidden", onHidden, false);
ok(!popup.isOpen, "popup is not open after VK_RETURN");

View File

@ -13,12 +13,8 @@ let HUD;
let outputItem;
function tabLoad1(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
openConsole();
HUD = HUDService.getHudByWindow(content);
function consoleOpened(aHud) {
HUD = aHud;
outputNode = HUD.outputNode;
@ -26,11 +22,10 @@ function tabLoad1(aEvent) {
// Reload so we get some output in the console.
browser.contentWindow.location.reload();
log(document);
}
function tabLoad2(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
browser.removeEventListener(aEvent.type, tabLoad2, true);
outputItem = outputNode.querySelector(".hud-networkinfo .hud-clickable");
ok(outputItem, "found a network message");
@ -42,7 +37,7 @@ function tabLoad2(aEvent) {
}
function networkPanelShown(aEvent) {
document.removeEventListener(aEvent.type, arguments.callee, false);
document.removeEventListener(aEvent.type, networkPanelShown, false);
document.addEventListener("popupshown", networkPanelShowFailure, false);
@ -57,13 +52,13 @@ function networkPanelShown(aEvent) {
}
function networkPanelShowFailure(aEvent) {
document.removeEventListener(aEvent.type, arguments.callee, false);
document.removeEventListener(aEvent.type, networkPanelShowFailure, false);
ok(false, "the network panel should not show");
}
function networkPanelHidden(aEvent) {
this.removeEventListener(aEvent.type, arguments.callee, false);
this.removeEventListener(aEvent.type, networkPanelHidden, false);
// The network panel should not show because this is a mouse event that starts
// in a position and ends in another.
@ -92,20 +87,27 @@ function networkPanelHidden(aEvent) {
HUD.jsterm.setInputValue("document");
HUD.jsterm.execute();
outputItem = outputNode.querySelector(".webconsole-msg-output " +
".hud-clickable");
ok(outputItem, "found a jsterm output message");
waitForSuccess({
name: "jsterm output message",
validatorFn: function()
{
return outputNode.querySelector(".webconsole-msg-output .hud-clickable");
},
successFn: function()
{
document.addEventListener("popupshown", propertyPanelShown, false);
document.addEventListener("popupshown", properyPanelShown, false);
// Send the mousedown and click events such that the property panel opens.
EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
EventUtils.sendMouseEvent({type: "click"}, outputItem);
// Send the mousedown and click events such that the property panel opens.
EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
EventUtils.sendMouseEvent({type: "click"}, outputItem);
},
failureFn: finishTest,
});
});
}
function properyPanelShown(aEvent) {
document.removeEventListener(aEvent.type, arguments.callee, false);
function propertyPanelShown(aEvent) {
document.removeEventListener(aEvent.type, propertyPanelShown, false);
document.addEventListener("popupshown", propertyPanelShowFailure, false);
@ -120,13 +122,13 @@ function properyPanelShown(aEvent) {
}
function propertyPanelShowFailure(aEvent) {
document.removeEventListener(aEvent.type, arguments.callee, false);
document.removeEventListener(aEvent.type, propertyPanelShowFailure, false);
ok(false, "the property panel should not show");
}
function propertyPanelHidden(aEvent) {
this.removeEventListener(aEvent.type, arguments.callee, false);
this.removeEventListener(aEvent.type, propertyPanelHidden, false);
// The property panel should not show because this is a mouse event that
// starts in a position and ends in another.
@ -149,13 +151,16 @@ function propertyPanelHidden(aEvent) {
executeSoon(function() {
document.removeEventListener("popupshown", propertyPanelShowFailure, false);
outputItem = null;
finishTest();
HUD = outputItem = null;
executeSoon(finishTest);
});
}
function test() {
addTab(TEST_URI);
browser.addEventListener("load", tabLoad1, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}

View File

@ -169,6 +169,18 @@ function testGen() {
HUD.jsterm.setInputValue("print(" + inputValue + ")");
HUD.jsterm.execute();
waitForSuccess({
name: "jsterm print() output for test #" + cpos,
validatorFn: function()
{
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
},
successFn: subtestNext,
failureFn: testNext,
});
yield;
outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
"last-child");
ok(outputItem,
@ -178,6 +190,32 @@ function testGen() {
// Test jsterm execution output.
HUD.jsterm.clearOutput();
HUD.jsterm.setInputValue(inputValue);
HUD.jsterm.execute();
waitForSuccess({
name: "jsterm output for test #" + cpos,
validatorFn: function()
{
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
},
successFn: subtestNext,
failureFn: testNext,
});
yield;
outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
"last-child");
ok(outputItem, "found the jsterm output line for inputValues[" + cpos + "]");
ok(outputItem.textContent.indexOf(expectedOutput) > -1,
"jsterm output is correct for inputValues[" + cpos + "]");
let messageBody = outputItem.querySelector(".webconsole-msg-body");
ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
// Test click on output.
let eventHandlerID = eventHandlers.length + 1;
let propertyPanelShown = function(aEvent) {
@ -205,19 +243,6 @@ function testGen() {
eventHandlers.push(propertyPanelShown);
HUD.jsterm.clearOutput();
HUD.jsterm.setInputValue(inputValue);
HUD.jsterm.execute();
outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
"last-child");
ok(outputItem, "found the jsterm output line for inputValues[" + cpos + "]");
ok(outputItem.textContent.indexOf(expectedOutput) > -1,
"jsterm output is correct for inputValues[" + cpos + "]");
let messageBody = outputItem.querySelector(".webconsole-msg-body");
ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
// Send the mousedown, mouseup and click events to check if the property
// panel opens.
EventUtils.sendMouseEvent({ type: "mousedown" }, messageBody, window);
@ -251,7 +276,7 @@ function testEnd() {
}
}
testDriver = null;
HUD = inputValues = testDriver = null;
executeSoon(finishTest);
}

View File

@ -7,12 +7,13 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
function test() {
addTab(TEST_URI);
browser.addEventListener("load", function() {
browser.removeEventListener("load", arguments.callee, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole();
content.location.reload();
browser.addEventListener("load", tabLoaded, true);
openConsole(null, function() {
content.location.reload();
browser.addEventListener("load", tabLoaded, true);
});
}, true);
}
@ -30,9 +31,6 @@ function tabLoaded() {
let networkLink = networkMessage.querySelector(".webconsole-msg-link");
ok(networkLink, "found network message link");
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
ok(jstermMessage, "found output message");
let popupset = document.getElementById("mainPopupSet");
ok(popupset, "found #mainPopupSet");
@ -85,6 +83,18 @@ function tabLoaded() {
}
});
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
waitForSuccess({
name: "jsterm output message",
validatorFn: function()
{
return HUD.outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
},
failureFn: finishTest,
});
}

View File

@ -46,9 +46,6 @@ function tabLoaded() {
let networkLink = networkMessage.querySelector(".webconsole-msg-link");
ok(networkLink, "found network message link");
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
ok(jstermMessage, "found output message");
let popupset = document.getElementById("mainPopupSet");
ok(popupset, "found #mainPopupSet");
@ -112,8 +109,20 @@ function tabLoaded() {
});
// Show the network and object inspector panels.
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
waitForSuccess({
name: "jsterm output message",
validatorFn: function()
{
return HUD.outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
},
failureFn: finishTest,
});
}
function togglePBAndThen(callback) {

View File

@ -9,35 +9,60 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html";
function tabLoad(aEvent) {
browser.removeEventListener(aEvent.type, tabLoad, true);
function test$(HUD) {
HUD.jsterm.clearOutput();
openConsole(null, function(HUD) {
HUD.jsterm.clearOutput();
HUD.jsterm.setInputValue("$(document.body)");
HUD.jsterm.execute();
HUD.jsterm.setInputValue("$(document.body)");
HUD.jsterm.execute();
waitForSuccess({
name: "jsterm output for $()",
validatorFn: function()
{
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
},
successFn: function()
{
let outputItem = HUD.outputNode.
querySelector(".webconsole-msg-output:last-child");
ok(outputItem.textContent.indexOf("<p>") > -1,
"jsterm output is correct for $()");
let outputItem = HUD.outputNode.
querySelector(".webconsole-msg-output:last-child");
ok(outputItem.textContent.indexOf("<p>") > -1,
"jsterm output is correct for $()");
test$$(HUD);
},
failureFn: test$$.bind(null, HUD),
});
}
HUD.jsterm.clearOutput();
function test$$(HUD) {
HUD.jsterm.clearOutput();
HUD.jsterm.setInputValue("$$(document)");
HUD.jsterm.execute();
HUD.jsterm.setInputValue("$$(document)");
HUD.jsterm.execute();
outputItem = HUD.outputNode.
querySelector(".webconsole-msg-output:last-child");
ok(outputItem.textContent.indexOf("621644") > -1,
"jsterm output is correct for $$()");
waitForSuccess({
name: "jsterm output for $$()",
validatorFn: function()
{
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
},
successFn: function()
{
let outputItem = HUD.outputNode.
querySelector(".webconsole-msg-output:last-child");
ok(outputItem.textContent.indexOf("621644") > -1,
"jsterm output is correct for $$()");
executeSoon(finishTest);
executeSoon(finishTest);
},
failureFn: finishTest,
});
}
function test() {
addTab(TEST_URI);
browser.addEventListener("load", tabLoad, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, test$);
}, true);
}

View File

@ -5,26 +5,27 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
function test() {
addTab(TEST_URI);
browser.addEventListener("load", tabLoaded, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function tabLoaded() {
browser.removeEventListener("load", tabLoaded, true);
openConsole();
let hudId = HUDService.getHudIdByWindow(content);
let HUD = HUDService.hudReferences[hudId];
function consoleOpened(HUD) {
let jsterm = HUD.jsterm;
let doc = content.wrappedJSObject.document;
let panel = jsterm.openPropertyPanel("Test1", doc);
let panel = jsterm.openPropertyPanel({ data: { object: doc }});
let rows = panel.treeView._rows;
let view = panel.treeView;
let find = function(regex) {
return rows.some(function(row) {
return regex.test(row.display);
});
for (let i = 0; i < view.rowCount; i++) {
if (regex.test(view.getCellText(i))) {
return true;
}
}
return false;
};
ok(!find(/^(width|height):/), "no document.width/height");
@ -33,8 +34,8 @@ function tabLoaded() {
let getterValue = doc.foobar._val;
panel = jsterm.openPropertyPanel("Test2", doc.foobar);
rows = panel.treeView._rows;
panel = jsterm.openPropertyPanel({ data: { object: doc.foobar }});
view = panel.treeView;
is(getterValue, doc.foobar._val, "getter did not execute");
is(getterValue+1, doc.foobar.val, "getter executed");

View File

@ -7,27 +7,25 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
function test() {
addTab(TEST_URI);
browser.addEventListener("load", tabLoaded, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function tabLoaded() {
browser.removeEventListener("load", tabLoaded, true);
function consoleOpened(HUD) {
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
let WCU = tmp.WebConsoleUtils;
let JSPropertyProvider = tmp.JSPropertyProvider;
tmp = null;
openConsole();
let hudId = HUDService.getHudIdByWindow(content);
let HUD = HUDService.hudReferences[hudId];
let jsterm = HUD.jsterm;
let win = content.wrappedJSObject;
// Make sure autocomplete does not walk through iterators and generators.
let result = win.gen1.next();
let completion = jsterm.propertyProvider(win, "gen1.");
let completion = JSPropertyProvider(win, "gen1.");
is(completion, null, "no matchees for gen1");
ok(!WCU.isObjectInspectable(win.gen1),
"gen1 is not inspectable");
@ -36,7 +34,7 @@ function tabLoaded() {
result = win.gen2.next();
completion = jsterm.propertyProvider(win, "gen2.");
completion = JSPropertyProvider(win, "gen2.");
is(completion, null, "no matchees for gen2");
ok(!WCU.isObjectInspectable(win.gen2),
"gen2 is not inspectable");
@ -48,7 +46,7 @@ function tabLoaded() {
is(result[0], "foo", "iter1.next() [0] is correct");
is(result[1], "bar", "iter1.next() [1] is correct");
completion = jsterm.propertyProvider(win, "iter1.");
completion = JSPropertyProvider(win, "iter1.");
is(completion, null, "no matchees for iter1");
ok(!WCU.isObjectInspectable(win.iter1),
"iter1 is not inspectable");
@ -57,27 +55,57 @@ function tabLoaded() {
is(result[0], "baz", "iter1.next() [0] is correct");
is(result[1], "baaz", "iter1.next() [1] is correct");
completion = jsterm.propertyProvider(content, "iter2.");
completion = JSPropertyProvider(content, "iter2.");
is(completion, null, "no matchees for iter2");
ok(!WCU.isObjectInspectable(win.iter2),
"iter2 is not inspectable");
completion = jsterm.propertyProvider(win, "window.");
completion = JSPropertyProvider(win, "window.");
ok(completion, "matches available for window");
ok(completion.matches.length, "matches available for window (length)");
ok(WCU.isObjectInspectable(win),
"window is inspectable");
let panel = jsterm.openPropertyPanel("Test", win);
ok(panel, "opened the Property Panel");
let rows = panel.treeView._rows;
ok(rows.length, "Property Panel rows are available");
jsterm.clearOutput();
jsterm.setInputValue("window");
jsterm.execute();
waitForSuccess({
name: "jsterm window object output",
validatorFn: function()
{
return HUD.outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
document.addEventListener("popupshown", function onShown(aEvent) {
document.removeEventListener("popupshown", onShown, false);
executeSoon(testPropertyPanel.bind(null, aEvent.target));
}, false);
let node = HUD.outputNode.querySelector(".webconsole-msg-output");
EventUtils.synthesizeMouse(node, 2, 2, {});
},
failureFn: finishTest,
});
}
function testPropertyPanel(aPanel) {
let tree = aPanel.querySelector("tree");
let view = tree.view;
let col = tree.columns[0];
ok(view.rowCount, "Property Panel rowCount");
let find = function(display, children) {
return rows.some(function(row) {
return row.display == display &&
row.children == children;
});
for (let i = 0; i < view.rowCount; i++) {
if (view.isContainer(i) == children &&
view.getCellText(i, col) == display) {
return true;
}
}
return false;
};
ok(find("gen1: Generator", false),
@ -92,13 +120,5 @@ function tabLoaded() {
ok(find("iter2: Iterator", false),
"iter2 is correctly displayed in the Property Panel");
/*
* - disabled, see bug 632347, c#9
* ok(find("parent: Window", true),
* "window.parent is correctly displayed in the Property Panel");
*/
panel.destroy();
finishTest();
executeSoon(finishTest);
}

View File

@ -8,13 +8,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
function tabLoad(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
openConsole();
let hudId = HUDService.getHudIdByWindow(content);
let HUD = HUDService.hudReferences[hudId];
function consoleOpened(HUD) {
let jsterm = HUD.jsterm;
let stringToCopy = "foobazbarBug642615";
@ -24,41 +18,11 @@ function tabLoad(aEvent) {
jsterm.setInputValue("doc");
let completionValue;
// wait for key "u"
jsterm.inputNode.addEventListener("keyup", function() {
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
let completionValue = jsterm.completeNode.value;
ok(completionValue, "we have a completeNode.value");
// wait for paste
jsterm.inputNode.addEventListener("input", function() {
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
ok(!jsterm.completeNode.value, "no completeNode.value after clipboard paste");
// wait for undo
jsterm.inputNode.addEventListener("input", function() {
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
is(jsterm.completeNode.value, completionValue,
"same completeNode.value after undo");
// wait for paste (via keyboard event)
jsterm.inputNode.addEventListener("keyup", function() {
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
ok(!jsterm.completeNode.value,
"no completeNode.value after clipboard paste (via keyboard event)");
executeSoon(finishTest);
}, false);
EventUtils.synthesizeKey("v", {accelKey: true});
}, false);
goDoCommand("cmd_undo");
}, false);
function onCompletionValue() {
completionValue = jsterm.completeNode.value;
// Arguments: expected, setup, success, failure.
waitForClipboard(
@ -66,17 +30,73 @@ function tabLoad(aEvent) {
function() {
clipboardHelper.copyString(stringToCopy);
},
function() {
updateEditUIVisibility();
goDoCommand("cmd_paste");
onClipboardCopy,
finishTest);
}
function onClipboardCopy() {
updateEditUIVisibility();
goDoCommand("cmd_paste");
waitForSuccess(waitForPaste);
}
let waitForPaste = {
name: "no completion value after paste",
validatorFn: function()
{
return !jsterm.completeNode.value;
},
successFn: onClipboardPaste,
failureFn: finishTest,
};
function onClipboardPaste() {
goDoCommand("cmd_undo");
waitForSuccess({
name: "completion value for 'docu' after undo",
validatorFn: function()
{
return !!jsterm.completeNode.value;
},
finish);
}, false);
successFn: onCompletionValueAfterUndo,
failureFn: finishTest,
});
}
function onCompletionValueAfterUndo() {
is(jsterm.completeNode.value, completionValue,
"same completeNode.value after undo");
EventUtils.synthesizeKey("v", {accelKey: true});
waitForSuccess({
name: "no completion after ctrl-v (paste)",
validatorFn: function()
{
return !jsterm.completeNode.value;
},
successFn: finishTest,
failureFn: finishTest,
});
}
EventUtils.synthesizeKey("u", {});
waitForSuccess({
name: "completion value for 'docu'",
validatorFn: function()
{
return !!jsterm.completeNode.value;
},
successFn: onCompletionValue,
failureFn: finishTest,
});
}
function test() {
addTab(TEST_URI);
browser.addEventListener("load", tabLoad, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}

View File

@ -6,38 +6,36 @@
// Tests that document.body autocompletes in the web console.
let tempScope = {};
Cu.import("resource:///modules/PropertyPanel.jsm", tempScope);
let PropertyPanel = tempScope.PropertyPanel;
let PropertyTreeView = tempScope.PropertyTreeView;
let namesAndValuesOf = tempScope.namesAndValuesOf;
let isNonNativeGetter = tempScope.isNonNativeGetter;
function test() {
addTab("data:text/html;charset=utf-8,Web Console autocompletion bug in document.body");
browser.addEventListener("load", onLoad, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
var gHUD;
function onLoad(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
openConsole();
let hudId = HUDService.getHudIdByWindow(content);
gHUD = HUDService.hudReferences[hudId];
function consoleOpened(aHud) {
gHUD = aHud;
let jsterm = gHUD.jsterm;
let popup = jsterm.autocompletePopup;
let completeNode = jsterm.completeNode;
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
let WCU = tmp.WebConsoleUtils;
tmp = null;
ok(!popup.isOpen, "popup is not open");
popup._panel.addEventListener("popupshown", function() {
popup._panel.removeEventListener("popupshown", arguments.callee, false);
popup._panel.addEventListener("popupshown", function onShown() {
popup._panel.removeEventListener("popupshown", onShown, false);
ok(popup.isOpen, "popup is open");
let props = namesAndValuesOf(content.wrappedJSObject.document.body).length;
is(popup.itemCount, props, "popup.itemCount is correct");
let props = WCU.namesAndValuesOf(content.wrappedJSObject.document.body);
is(popup.itemCount, props.length, "popup.itemCount is correct");
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
@ -55,32 +53,80 @@ function autocompletePopupHidden()
let completeNode = jsterm.completeNode;
let inputNode = jsterm.inputNode;
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
ok(!popup.isOpen, "popup is not open");
let inputStr = "document.b";
jsterm.setInputValue(inputStr);
EventUtils.synthesizeKey("o", {});
let testStr = inputStr.replace(/./g, " ") + " ";
is(completeNode.value, testStr + "dy", "completeNode is empty");
jsterm.setInputValue("");
// Check the property panel as well. It's a bit gross to parse the properties
// out of the treeView cell text, but nsITreeView doesn't give us a good
// structured way to get at the data. :-(
let propPanel = jsterm.openPropertyPanel("Test", content.document);
waitForSuccess({
name: "autocomplete shows document.body",
validatorFn: function()
{
return completeNode.value == testStr + "dy";
},
successFn: testPropertyPanel,
failureFn: finishTest,
});
}
function testPropertyPanel()
{
let jsterm = gHUD.jsterm;
jsterm.clearOutput();
jsterm.setInputValue("document");
jsterm.execute();
waitForSuccess({
name: "jsterm document object output",
validatorFn: function()
{
return gHUD.outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
document.addEventListener("popupshown", function onShown(aEvent) {
document.removeEventListener("popupshown", onShown, false);
executeSoon(propertyPanelShown.bind(null, aEvent.target));
}, false);
let node = gHUD.outputNode.querySelector(".webconsole-msg-output");
EventUtils.synthesizeMouse(node, 2, 2, {});
},
failureFn: finishTest,
});
}
function propertyPanelShown(aPanel)
{
let tree = aPanel.querySelector("tree");
let view = tree.view;
let col = tree.columns[0];
ok(view.rowCount, "Property Panel rowCount");
let foundBody = false;
let propPanelProps = [];
for (let idx = 0; idx < propPanel.treeView.rowCount; ++idx)
propPanelProps.push(propPanel.treeView.getCellText(idx, null).split(':')[0]);
for (let idx = 0; idx < view.rowCount; ++idx) {
let text = view.getCellText(idx, col);
if (text == "body: HTMLBodyElement" || text == "body: Object")
foundBody = true;
propPanelProps.push(text.split(":")[0]);
}
// NB: We pull the properties off the prototype, rather than off object itself,
// so that expandos like |constructor|, which the propPanel can't see, are not
// included.
for (let prop in Object.getPrototypeOf(content.document))
for (let prop in Object.getPrototypeOf(content.document)) {
if (prop == "inputEncoding") {
continue;
}
ok(propPanelProps.indexOf(prop) != -1, "Property |" + prop + "| should be reflected in propertyPanel");
}
ok(foundBody, "found document.body");
let treeRows = propPanel.treeView._rows;
is (treeRows[30].display, "body: Object", "found document.body");
propPanel.destroy();
executeSoon(finishTest);
}

View File

@ -5,11 +5,9 @@
// Tests that the $0 console helper works as intended.
let doc;
let h1;
function createDocument()
{
let doc = content.document;
let div = doc.createElement("div");
let h1 = doc.createElement("h1");
let p1 = doc.createElement("p");
@ -44,7 +42,7 @@ function createDocument()
function setupHighlighterTests()
{
h1 = doc.querySelectorAll("h1")[0];
let h1 = content.document.querySelector("h1");
ok(h1, "we have the header node");
Services.obs.addObserver(runSelectionTests,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
@ -58,6 +56,7 @@ function runSelectionTests()
executeSoon(function() {
InspectorUI.highlighter.addListener("nodeselected", performTestComparisons);
let h1 = content.document.querySelector("h1");
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
});
}
@ -67,6 +66,8 @@ function performTestComparisons()
InspectorUI.highlighter.removeListener("nodeselected", performTestComparisons);
InspectorUI.stopInspecting();
let h1 = content.document.querySelector("h1");
is(InspectorUI.highlighter.node, h1, "node selected");
is(InspectorUI.selection, h1, "selection matches node");
@ -80,16 +81,44 @@ function performWebConsoleTests(hud)
jsterm.clearOutput();
jsterm.execute("$0");
findLogEntry("[object HTMLHeadingElement");
jsterm.clearOutput();
let msg = "foo";
jsterm.execute("$0.textContent = '" + msg + "'");
findLogEntry(msg);
is(InspectorUI.selection.textContent, msg, "node successfully updated");
waitForSuccess({
name: "$0 output",
validatorFn: function()
{
return outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
let node = outputNode.querySelector(".webconsole-msg-output");
isnot(node.textContent.indexOf("[object HTMLHeadingElement"), -1,
"correct output for $0");
doc = h1 = null;
executeSoon(finishUp);
jsterm.clearOutput();
jsterm.execute("$0.textContent = 'bug653531'");
waitForSuccess(waitForNodeUpdate);
},
failureFn: finishUp,
});
let waitForNodeUpdate = {
name: "$0.textContent update",
validatorFn: function()
{
return outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
let node = outputNode.querySelector(".webconsole-msg-output");
isnot(node.textContent.indexOf("bug653531"), -1,
"correct output for $0.textContent");
is(InspectorUI.selection.textContent, "bug653531",
"node successfully updated");
executeSoon(finishUp);
},
failureFn: finishUp,
};
}
function finishUp() {
@ -103,7 +132,6 @@ function test()
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function() {
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
doc = content.document;
waitForFocus(createDocument, content);
}, true);

View File

@ -6,11 +6,10 @@
// Tests that code completion works properly.
function test() {
addTab(getBrowserURL());
browser.addEventListener("DOMContentLoaded", function onLoad() {
browser.removeEventListener("DOMContentLoaded", onLoad, true);
openConsole();
testChrome(HUDService.getHudByWindow(content));
addTab("about:addons");
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testChrome);
}, true);
}
@ -28,10 +27,9 @@ function testChrome(hud) {
// Test typing 'docu'.
input.value = "docu";
input.setSelectionRange(4, 4);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
is(jsterm.completeNode.value, " ment", "'docu' completion");
gBrowser.removeCurrentTab();
executeSoon(finishTest);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, function() {
is(jsterm.completeNode.value, " ment", "'docu' completion");
executeSoon(finishTest);
});
}

View File

@ -5,32 +5,46 @@
// Tests that code completion works properly.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
const TEST_URI = "data:text/html;charset=utf8,<p>test code completion";
let testDriver;
function test() {
addTab(TEST_URI);
browser.addEventListener("DOMContentLoaded", testCompletion, false);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, function(hud) {
testDriver = testCompletion(hud);
testDriver.next();
});
}, true);
}
function testCompletion() {
browser.removeEventListener("DOMContentLoaded", testCompletion, false);
function testNext() {
executeSoon(function() {
testDriver.next();
});
}
openConsole();
var jsterm = HUDService.getHudByWindow(content).jsterm;
var input = jsterm.inputNode;
function testCompletion(hud) {
let jsterm = hud.jsterm;
let input = jsterm.inputNode;
// Test typing 'docu'.
input.value = "docu";
input.setSelectionRange(4, 4);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
is(input.value, "docu", "'docu' completion");
is(jsterm.completeNode.value, " ment", "'docu' completion");
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield;
is(input.value, "docu", "'docu' completion (input.value)");
is(jsterm.completeNode.value, " ment", "'docu' completion (completeNode)");
// Test typing 'docu' and press tab.
input.value = "docu";
input.setSelectionRange(4, 4);
jsterm.complete(jsterm.COMPLETE_FORWARD);
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
yield;
is(input.value, "document", "'docu' tab completion");
is(input.selectionStart, 8, "start selection is alright");
is(input.selectionEnd, 8, "end selection is alright");
@ -39,35 +53,45 @@ function testCompletion() {
// Test typing 'document.getElem'.
input.value = "document.getElem";
input.setSelectionRange(16, 16);
jsterm.complete(jsterm.COMPLETE_FORWARD);
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
yield;
is(input.value, "document.getElem", "'document.getElem' completion");
is(jsterm.completeNode.value, " entById", "'document.getElem' completion");
// Test pressing tab another time.
jsterm.complete(jsterm.COMPLETE_FORWARD);
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
yield;
is(input.value, "document.getElem", "'document.getElem' completion");
is(jsterm.completeNode.value, " entsByClassName", "'document.getElem' another tab completion");
// Test pressing shift_tab.
jsterm.complete(jsterm.COMPLETE_BACKWARD);
jsterm.complete(jsterm.COMPLETE_BACKWARD, testNext);
yield;
is(input.value, "document.getElem", "'document.getElem' untab completion");
is(jsterm.completeNode.value, " entById", "'document.getElem' completion");
jsterm.clearOutput();
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
input.value = "docu";
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield;
is(jsterm.completeNode.value, " ment", "'docu' completion");
jsterm.execute();
is(jsterm.completeNode.value, "", "clear completion on execute()");
// Test multi-line completion works
input.value = "console.log('one');\nconsol";
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
yield;
is(jsterm.completeNode.value, " \n e", "multi-line completion");
jsterm = input = null;
finishTest();
testDriver = jsterm = input = null;
executeSoon(finishTest);
yield;
}

View File

@ -14,17 +14,15 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
function test()
{
addTab(TEST_URI);
browser.addEventListener("load", function() {
browser.removeEventListener("load", arguments.callee, true);
testOpenWebConsole();
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testOpenWebConsole);
}, true);
}
function testOpenWebConsole()
function testOpenWebConsole(aHud)
{
openConsole();
hud = HUDService.getHudByWindow(content);
hud = aHud;
ok(hud, "WebConsole was opened");
testOwnConsole();
@ -42,8 +40,8 @@ function testOwnConsole()
// overwritten by the WebConsole's console.
testConsoleOnPage(console);
// Check that the console object is set on the jsterm object although there
// Check that the console object is set on the HUD object although there
// is no console object added to the page.
ok(hud.jsterm.console, "JSTerm console is defined");
ok(hud.console, "HUD console is defined");
finishTest();
}

View File

@ -19,17 +19,27 @@ function testExecutionScope(hud) {
let jsterm = hud.jsterm;
jsterm.clearOutput();
jsterm.execute("location;");
jsterm.execute("window.location;");
let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
is(nodes.length, 2, "Two children in output");
waitForSuccess({
name: "jsterm execution output (two nodes)",
validatorFn: function()
{
return jsterm.outputNode.querySelectorAll(".hud-msg-node").length == 2;
},
successFn: function()
{
let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
is(/location;/.test(nodes[0].textContent), true,
"'location;' written to output");
is(/window.location;/.test(nodes[0].textContent), true,
"'window.location;' written to output");
ok(nodes[0].textContent.indexOf(TEST_URI),
"command was executed in the window scope");
isnot(nodes[1].textContent.indexOf(TEST_URI), -1,
"command was executed in the window scope");
executeSoon(finishTest);
executeSoon(finishTest);
},
failureFn: finishTest,
});
}

View File

@ -7,22 +7,29 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
function test() {
addTab(TEST_URI);
browser.addEventListener("DOMContentLoaded", testForOf, false);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testForOf);
}, true);
}
function testForOf() {
browser.removeEventListener("DOMContentLoaded", testForOf, false);
openConsole();
var hud = HUDService.getHudByWindow(content);
function testForOf(hud) {
var jsterm = hud.jsterm;
jsterm.execute("{ [x.tagName for (x of document.body.childNodes) if (x.nodeType === 1)].join(' '); }");
let node = hud.outputNode.querySelector(".webconsole-msg-output");
ok(/H1 DIV H2 P/.test(node.textContent),
"for-of loop should find all top-level nodes");
jsterm.clearOutput();
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
finishTest();
waitForSuccess({
name: "jsterm output displayed",
validatorFn: function()
{
return hud.outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
let node = hud.outputNode.querySelector(".webconsole-msg-output");
ok(/H1 DIV H2 P/.test(node.textContent),
"for-of loop should find all top-level nodes");
finishTest();
},
failureFn: finishTest,
});
}

View File

@ -10,38 +10,38 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
function test() {
addTab(TEST_URI);
browser.addEventListener("DOMContentLoaded", testJSInputAndOutputStyling,
false);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testJSInputAndOutputStyling);
}, true);
}
function testJSInputAndOutputStyling() {
browser.removeEventListener("DOMContentLoaded",
testJSInputAndOutputStyling, false);
openConsole();
let jsterm = HUDService.getHudByWindow(content).jsterm;
function testJSInputAndOutputStyling(hud) {
let jsterm = hud.jsterm;
jsterm.clearOutput();
jsterm.execute("2 + 2");
let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
let jsInputNode = nodes[0];
let jsInputNode = jsterm.outputNode.querySelector(".hud-msg-node");
isnot(jsInputNode.textContent.indexOf("2 + 2"), -1,
"JS input node contains '2 + 2'");
ok(jsInputNode.classList.contains("webconsole-msg-input"),
"JS input node is of the CSS class 'webconsole-msg-input'");
let jsOutputNodes = jsterm.outputNode.
querySelectorAll(".webconsole-msg-output");
isnot(jsOutputNodes[0].textContent.indexOf("4"), -1,
"JS output node contains '4'");
ok(jsOutputNodes[0].classList.contains("webconsole-msg-output"),
"JS output node is of the CSS class 'webconsole-msg-output'");
jsterm.clearOutput();
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
finishTest();
waitForSuccess({
name: "jsterm output is displayed",
validatorFn: function()
{
return jsterm.outputNode.querySelector(".webconsole-msg-output");
},
successFn: function()
{
let node = jsterm.outputNode.querySelector(".webconsole-msg-output");
isnot(node.textContent.indexOf("4"), -1,
"JS output node contains '4'");
finishTest();
},
failureFn: finishTest,
});
}

View File

@ -5,21 +5,46 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
let jsterm;
let jsterm, testDriver;
function test() {
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testJSTerm);
openConsole(null, function(hud) {
testDriver = testJSTerm(hud);
testDriver.next();
});
}, true);
}
function nextTest() {
testDriver.next();
}
function checkResult(msg, desc, lines) {
let labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
is(labels.length, lines, "correct number of results shown for " + desc);
is(labels[lines-1].textContent.trim(), msg, "correct message shown for " +
desc);
waitForSuccess({
name: "correct number of results shown for " + desc,
validatorFn: function()
{
let nodes = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
return nodes.length == lines;
},
successFn: function()
{
let labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
if (typeof msg == "string") {
is(labels[lines-1].textContent.trim(), msg,
"correct message shown for " + desc);
}
else if (typeof msg == "function") {
ok(msg(labels), "correct message shown for " + desc);
}
nextTest();
},
failureFn: nextTest,
});
}
function testJSTerm(hud)
@ -29,34 +54,52 @@ function testJSTerm(hud)
jsterm.clearOutput();
jsterm.execute("'id=' + $('header').getAttribute('id')");
checkResult('"id=header"', "$() worked", 1);
yield;
jsterm.clearOutput();
jsterm.execute("headerQuery = $$('h1')");
jsterm.execute("'length=' + headerQuery.length");
checkResult('"length=1"', "$$() worked", 2);
yield;
jsterm.clearOutput();
jsterm.execute("xpathQuery = $x('.//*', document.body);");
jsterm.execute("'headerFound=' + (xpathQuery[0] == headerQuery[0])");
checkResult('"headerFound=true"', "$x() worked", 2);
yield;
// no jsterm.clearOutput() here as we clear the output using the clear() fn.
jsterm.execute("clear()");
let group = jsterm.outputNode.querySelector(".hud-group");
ok(!group, "clear() worked");
waitForSuccess({
name: "clear() worked",
validatorFn: function()
{
return jsterm.outputNode.childNodes.length == 0;
},
successFn: nextTest,
failureFn: nextTest,
});
yield;
jsterm.clearOutput();
jsterm.execute("'keysResult=' + (keys({b:1})[0] == 'b')");
checkResult('"keysResult=true"', "keys() worked", 1);
yield;
jsterm.clearOutput();
jsterm.execute("'valuesResult=' + (values({b:1})[0] == 1)");
checkResult('"valuesResult=true"', "values() worked", 1);
yield;
jsterm.clearOutput();
let tabs = gBrowser.tabs.length;
jsterm.execute("help()");
let output = jsterm.outputNode.querySelector(".webconsole-msg-output");
ok(!group, "help() worked");
ok(!output, "help() worked");
jsterm.execute("help");
output = jsterm.outputNode.querySelector(".webconsole-msg-output");
@ -66,57 +109,84 @@ function testJSTerm(hud)
output = jsterm.outputNode.querySelector(".webconsole-msg-output");
ok(!output, "? worked");
let foundTab = null;
waitForSuccess({
name: "help tab opened",
validatorFn: function()
{
let newTabOpen = gBrowser.tabs.length == tabs + 1;
if (!newTabOpen) {
return false;
}
foundTab = gBrowser.tabs[tabs];
return true;
},
successFn: function()
{
gBrowser.removeTab(foundTab);
nextTest();
},
failureFn: nextTest,
});
yield;
jsterm.clearOutput();
jsterm.execute("pprint({b:2, a:1})");
// Doesn't conform to checkResult format
let label = jsterm.outputNode.querySelector(".webconsole-msg-output");
is(label.textContent.trim(), "a: 1\n b: 2", "pprint() worked");
checkResult("a: 1\n b: 2", "pprint()", 1);
yield;
// check instanceof correctness, bug 599940
jsterm.clearOutput();
jsterm.execute("[] instanceof Array");
checkResult("true", "[] instanceof Array == true", 1);
yield;
jsterm.clearOutput();
jsterm.execute("({}) instanceof Object");
checkResult("true", "({}) instanceof Object == true", 1);
yield;
// check for occurrences of Object XRayWrapper, bug 604430
jsterm.clearOutput();
jsterm.execute("document");
let label = jsterm.outputNode.querySelector(".webconsole-msg-output");
is(label.textContent.trim().search(/\[object XrayWrapper/), -1,
"check for non-existence of [object XrayWrapper ");
checkResult(function(nodes) {
return nodes[0].textContent.search(/\[object xraywrapper/i) == -1;
}, "document - no XrayWrapper", 1);
yield;
// check that pprint(window) and keys(window) don't throw, bug 608358
jsterm.clearOutput();
jsterm.execute("pprint(window)");
let labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
is(labels.length, 1, "one line of output for pprint(window)");
checkResult(null, "pprint(window)", 1);
yield;
jsterm.clearOutput();
jsterm.execute("keys(window)");
labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
is(labels.length, 1, "one line of output for keys(window)");
checkResult(null, "keys(window)", 1);
yield;
// bug 614561
jsterm.clearOutput();
jsterm.execute("pprint('hi')");
// Doesn't conform to checkResult format, bug 614561
let label = jsterm.outputNode.querySelector(".webconsole-msg-output");
is(label.textContent.trim(), '0: "h"\n 1: "i"', 'pprint("hi") worked');
checkResult('0: "h"\n 1: "i"', "pprint('hi')", 1);
yield;
// check that pprint(function) shows function source, bug 618344
jsterm.clearOutput();
jsterm.execute("pprint(print)");
label = jsterm.outputNode.querySelector(".webconsole-msg-output");
isnot(label.textContent.indexOf("SEVERITY_LOG"), -1,
"pprint(function) shows function source");
checkResult(function(nodes) {
return nodes[0].textContent.indexOf("aJSTerm.") > -1;
}, "pprint(function) shows source", 1);
yield;
// check that an evaluated null produces "null", bug 650780
jsterm.clearOutput();
jsterm.execute("null");
checkResult("null", "null is null", 1);
yield;
jsterm = null;
jsterm = testDriver = null;
executeSoon(finishTest);
yield;
}

View File

@ -23,17 +23,40 @@ function testNullAndUndefinedOutput(hud) {
jsterm.clearOutput();
jsterm.execute("null;");
let nodes = outputNode.querySelectorAll(".hud-msg-node");
is(nodes.length, 2, "2 nodes in output");
ok(nodes[1].textContent.indexOf("null") > -1, "'null' printed to output");
waitForSuccess({
name: "null displayed",
validatorFn: function()
{
return outputNode.querySelectorAll(".hud-msg-node").length == 2;
},
successFn: function()
{
let nodes = outputNode.querySelectorAll(".hud-msg-node");
isnot(nodes[1].textContent.indexOf("null"), -1,
"'null' printed to output");
jsterm.clearOutput();
jsterm.execute("undefined;");
jsterm.clearOutput();
jsterm.execute("undefined;");
waitForSuccess(waitForUndefined);
},
failureFn: finishTest,
});
nodes = outputNode.querySelectorAll(".hud-msg-node");
is(nodes.length, 2, "2 nodes in output");
ok(nodes[1].textContent.indexOf("undefined") > -1, "'undefined' printed to output");
let waitForUndefined = {
name: "undefined displayed",
validatorFn: function()
{
return outputNode.querySelectorAll(".hud-msg-node").length == 2;
},
successFn: function()
{
let nodes = outputNode.querySelectorAll(".hud-msg-node");
isnot(nodes[1].textContent.indexOf("undefined"), -1,
"'undefined' printed to output");
executeSoon(finishTest);
finishTest();
},
failureFn: finishTest,
};
}

View File

@ -36,8 +36,7 @@ function testOutputOrder(hud) {
/console\.log\('foo', 'bar'\);/.test(nodes[0].textContent);
let outputSecond = /foo bar/.test(nodes[2].textContent);
ok(executedStringFirst && outputSecond, "executed string comes first");
jsterm.clearOutput();
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
finishTest();
},
failureFn: finishTest,

View File

@ -6,56 +6,63 @@
// Tests the functionality of the "property panel", which allows JavaScript
// objects and DOM nodes to be inspected.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
const TEST_URI = "data:text/html;charset=utf8,<p>property panel test";
function test() {
addTab(TEST_URI);
browser.addEventListener("DOMContentLoaded", testPropertyPanel, false);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testPropertyPanel);
}, true);
}
function testPropertyPanel() {
browser.removeEventListener("DOMContentLoaded", testPropertyPanel, false);
function testPropertyPanel(hud) {
let jsterm = hud.jsterm;
openConsole();
var jsterm = HUDService.getHudByWindow(content).jsterm;
let propPanel = jsterm.openPropertyPanel("Test", [
1,
/abc/,
null,
undefined,
function test() {},
{}
]);
let propPanel = jsterm.openPropertyPanel({
data: {
object: [
1,
/abc/,
null,
undefined,
function test() {},
{}
]
}
});
is (propPanel.treeView.rowCount, 6, "six elements shown in propertyPanel");
propPanel.destroy();
propPanel = jsterm.openPropertyPanel("Test2", {
"0.02": 0,
"0.01": 1,
"02": 2,
"1": 3,
"11": 4,
"1.2": 5,
"1.1": 6,
"foo": 7,
"bar": 8
propPanel = jsterm.openPropertyPanel({
data: {
object: {
"0.02": 0,
"0.01": 1,
"02": 2,
"1": 3,
"11": 4,
"1.2": 5,
"1.1": 6,
"foo": 7,
"bar": 8
}
}
});
is (propPanel.treeView.rowCount, 9, "nine elements shown in propertyPanel");
let treeRows = propPanel.treeView._rows;
is (treeRows[0].display, "0.01: 1", "1. element is okay");
is (treeRows[1].display, "0.02: 0", "2. element is okay");
is (treeRows[2].display, "1: 3", "3. element is okay");
is (treeRows[3].display, "1.1: 6", "4. element is okay");
is (treeRows[4].display, "1.2: 5", "5. element is okay");
is (treeRows[5].display, "02: 2", "6. element is okay");
is (treeRows[6].display, "11: 4", "7. element is okay");
is (treeRows[7].display, "bar: 8", "8. element is okay");
is (treeRows[8].display, "foo: 7", "9. element is okay");
let view = propPanel.treeView;
is (view.getCellText(0), "0.01: 1", "1. element is okay");
is (view.getCellText(1), "0.02: 0", "2. element is okay");
is (view.getCellText(2), "1: 3", "3. element is okay");
is (view.getCellText(3), "1.1: 6", "4. element is okay");
is (view.getCellText(4), "1.2: 5", "5. element is okay");
is (view.getCellText(5), "02: 2", "6. element is okay");
is (view.getCellText(6), "11: 4", "7. element is okay");
is (view.getCellText(7), "bar: 8", "8. element is okay");
is (view.getCellText(8), "foo: 7", "9. element is okay");
propPanel.destroy();
finishTest();
executeSoon(finishTest);
}

View File

@ -6,43 +6,37 @@
// Tests the property provider, which is part of the code completion
// infrastructure.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
const TEST_URI = "data:text/html;charset=utf8,<p>test the JS property provider";
function test() {
addTab(TEST_URI);
browser.addEventListener("DOMContentLoaded", testPropertyProvider, false);
browser.addEventListener("load", testPropertyProvider, true);
}
function testPropertyProvider() {
browser.removeEventListener("DOMContentLoaded", testPropertyProvider,
false);
browser.removeEventListener("load", testPropertyProvider, true);
openConsole();
let tmp = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
let JSPropertyProvider = tmp.JSPropertyProvider;
tmp = null;
var HUD = HUDService.getHudByWindow(content);
var jsterm = HUD.jsterm;
var context = jsterm.sandbox.window;
var completion;
// Test if the propertyProvider can be accessed from the jsterm object.
ok (jsterm.propertyProvider !== undefined, "JSPropertyProvider is defined");
completion = jsterm.propertyProvider(context, "thisIsNotDefined");
let completion = JSPropertyProvider(content, "thisIsNotDefined");
is (completion.matches.length, 0, "no match for 'thisIsNotDefined");
// This is a case the PropertyProvider can't handle. Should return null.
completion = jsterm.propertyProvider(context, "window[1].acb");
completion = JSPropertyProvider(content, "window[1].acb");
is (completion, null, "no match for 'window[1].acb");
// A very advanced completion case.
var strComplete =
'function a() { }document;document.getElementById(window.locatio';
completion = jsterm.propertyProvider(context, strComplete);
completion = JSPropertyProvider(content, strComplete);
ok(completion.matches.length == 2, "two matches found");
ok(completion.matchProp == "locatio", "matching part is 'test'");
ok(completion.matches[0] == "location", "the first match is 'location'");
ok(completion.matches[1] == "locationbar", "the second match is 'locationbar'");
context = completion = null;
finishTest();
}

View File

@ -27,21 +27,21 @@
- launches the debugger UI. Do not translate this one! -->
<!ENTITY debuggerMenu.commandkey "S">
<!-- LOCALIZATION NOTE (debuggerUI.closeButton): This is the label for the
- button that closes the debugger UI. -->
<!ENTITY debuggerUI.closeButton "Close">
<!-- LOCALIZATION NOTE (debuggerUI.closeButton.tooltip): This is the tooltip for
- the button that closes the debugger UI. -->
<!ENTITY debuggerUI.closeButton.tooltip "Close">
<!-- LOCALIZATION NOTE (debuggerUI.stepOverButton): This is the label for the
- button that steps over a function call. -->
<!ENTITY debuggerUI.stepOverButton "Step Over">
<!-- LOCALIZATION NOTE (debuggerUI.stepOverButton.tooltip): This is the tooltip for
- the button that steps over a function call. -->
<!ENTITY debuggerUI.stepOverButton.tooltip "Step Over">
<!-- LOCALIZATION NOTE (debuggerUI.stepInButton): This is the label for the
<!-- LOCALIZATION NOTE (debuggerUI.stepInButton): This is the tooltip for the
- button that steps into a function call. -->
<!ENTITY debuggerUI.stepInButton "Step In">
<!ENTITY debuggerUI.stepInButton.tooltip "Step In">
<!-- LOCALIZATION NOTE (debuggerUI.stepOutButton): This is the label for the
<!-- LOCALIZATION NOTE (debuggerUI.stepOutButton): This is the tooltip for the
- button that steps out of a function call. -->
<!ENTITY debuggerUI.stepOutButton "Step Out">
<!ENTITY debuggerUI.stepOutButton.tooltip "Step Out">
<!-- LOCALIZATION NOTE (debuggerUI.stackTitle): This is the label for the
- widget that displays the call stack frames in the debugger. -->

View File

@ -36,19 +36,11 @@ remoteDebuggerConnectionFailedMessage=Could not find a server at the specified h
# LOCALIZATION NOTE (pauseLabel): The label that is displayed on the pause
# button when the debugger is in a running state.
pauseLabel=Pause
pauseTooltip=Click to pause
# LOCALIZATION NOTE (resumeLabel): The label that is displayed on the pause
# button when the debugger is in a paused state.
resumeLabel=Resume
# LOCALIZATION NOTE (pausedState): The label that is displayed when the
# debugger is in a paused state.
pausedState=Paused
# LOCALIZATION NOTE (runningState): The label that is displayed when the
# debugger is in a running state.
runningState=Running
resumeTooltip=Click to resume
# LOCALIZATION NOTE (localScope): The label that is displayed in the variables
# pane as a header on the local scope container.

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