author | Richard Newman <rnewman@mozilla.com> |
Fri, 27 Apr 2012 10:36:01 -0700 | |
changeset 95734 | 31faeb7325134829e50aaa4f725f04c59b9f725f |
parent 95733 | c019b4bbe9ffc30ba0a1ee72a475fc0bce48d47d (current diff) |
parent 94873 | 0f8ea3826bf79f3b6c09b4da14abdd76200004ff (diff) |
child 95735 | 8e59a61ca83136b880fe273dfa7ac69f2cb36cd2 |
push id | 1439 |
push user | lsblakk@mozilla.com |
push date | Mon, 04 Jun 2012 20:19:22 +0000 |
treeherder | mozilla-aurora@ea74834dccd3 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 15.0a1 |
--- a/accessible/src/generic/ARIAGridAccessible.cpp +++ b/accessible/src/generic/ARIAGridAccessible.cpp @@ -648,48 +648,36 @@ ARIAGridAccessible::SelectColumn(PRInt32 rv = SetARIASelected(cell, true); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } -NS_IMETHODIMP -ARIAGridAccessible::UnselectRow(PRInt32 aRow) +void +ARIAGridAccessible::UnselectRow(PRUint32 aRowIdx) { - if (IsDefunct()) - return NS_ERROR_FAILURE; + nsAccessible* row = GetRowAt(aRowIdx); - nsAccessible *row = GetRowAt(aRow); - NS_ENSURE_ARG(row); - - return SetARIASelected(row, false); + if (row) + SetARIASelected(row, false); } -NS_IMETHODIMP -ARIAGridAccessible::UnselectColumn(PRInt32 aColumn) +void +ARIAGridAccessible::UnselectCol(PRUint32 aColIdx) { - NS_ENSURE_ARG(IsValidColumn(aColumn)); - - if (IsDefunct()) - return NS_ERROR_FAILURE; - AccIterator rowIter(this, filters::GetRow); - nsAccessible *row = nsnull; + nsAccessible* row = nsnull; while ((row = rowIter.Next())) { - nsAccessible *cell = GetCellInRowAt(row, aColumn); - if (cell) { - nsresult rv = SetARIASelected(cell, false); - NS_ENSURE_SUCCESS(rv, rv); - } + nsAccessible* cell = GetCellInRowAt(row, aColIdx); + if (cell) + SetARIASelected(cell, false); } - - return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // Protected bool ARIAGridAccessible::IsValidRow(PRInt32 aRow) {
--- a/accessible/src/generic/ARIAGridAccessible.h +++ b/accessible/src/generic/ARIAGridAccessible.h @@ -69,16 +69,18 @@ public: virtual mozilla::a11y::TableAccessible* AsTable() { return this; } // nsAccessNode virtual void Shutdown(); // TableAccessible virtual PRUint32 ColCount(); virtual PRUint32 RowCount(); + virtual void UnselectCol(PRUint32 aColIdx); + virtual void UnselectRow(PRUint32 aRowIdx); protected: /** * Return true if the given row index is valid. */ bool IsValidRow(PRInt32 aRow); /**
--- a/accessible/src/html/nsHTMLTableAccessible.cpp +++ b/accessible/src/html/nsHTMLTableAccessible.cpp @@ -1134,38 +1134,30 @@ nsHTMLTableAccessible::SelectColumn(PRIn nsISelectionPrivate::TABLESELECTION_COLUMN, true); NS_ENSURE_SUCCESS(rv, rv); return AddRowOrColumnToSelection(aColumn, nsISelectionPrivate::TABLESELECTION_COLUMN); } -NS_IMETHODIMP -nsHTMLTableAccessible::UnselectRow(PRInt32 aRow) +void +nsHTMLTableAccessible::UnselectRow(PRUint32 aRowIdx) { - if (IsDefunct()) - return NS_ERROR_FAILURE; - - return - RemoveRowsOrColumnsFromSelection(aRow, - nsISelectionPrivate::TABLESELECTION_ROW, - false); + RemoveRowsOrColumnsFromSelection(aRowIdx, + nsISelectionPrivate::TABLESELECTION_ROW, + false); } -NS_IMETHODIMP -nsHTMLTableAccessible::UnselectColumn(PRInt32 aColumn) +void +nsHTMLTableAccessible::UnselectCol(PRUint32 aColIdx) { - if (IsDefunct()) - return NS_ERROR_FAILURE; - - return - RemoveRowsOrColumnsFromSelection(aColumn, - nsISelectionPrivate::TABLESELECTION_COLUMN, - false); + RemoveRowsOrColumnsFromSelection(aColIdx, + nsISelectionPrivate::TABLESELECTION_COLUMN, + false); } nsresult nsHTMLTableAccessible::AddRowOrColumnToSelection(PRInt32 aIndex, PRUint32 aTarget) { bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
--- a/accessible/src/html/nsHTMLTableAccessible.h +++ b/accessible/src/html/nsHTMLTableAccessible.h @@ -124,19 +124,21 @@ public: NS_DECL_ISUPPORTS_INHERITED // nsIAccessible Table NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE // TableAccessible virtual nsAccessible* Caption(); + virtual void Summary(nsString& aSummary); virtual PRUint32 ColCount(); virtual PRUint32 RowCount(); - virtual void Summary(nsString& aSummary); + virtual void UnselectCol(PRUint32 aColIdx); + virtual void UnselectRow(PRUint32 aRowIdx); virtual bool IsProbablyLayoutTable(); // nsAccessNode virtual void Shutdown(); // nsAccessible virtual mozilla::a11y::TableAccessible* AsTable() { return this; } virtual void Description(nsString& aDescription);
--- a/accessible/src/xpcom/xpcAccessibleTable.cpp +++ b/accessible/src/xpcom/xpcAccessibleTable.cpp @@ -66,8 +66,36 @@ xpcAccessibleTable::IsProbablyForLayout( NS_ENSURE_ARG_POINTER(aResult); *aResult = false; if (!mTable) return NS_ERROR_FAILURE; *aResult = mTable->IsProbablyLayoutTable(); return NS_OK; } + +nsresult +xpcAccessibleTable::UnselectColumn(PRInt32 aColIdx) +{ + if (!mTable) + return NS_ERROR_FAILURE; + + if (aColIdx < 0 || aColIdx >= mTable->ColCount()) + return NS_ERROR_INVALID_ARG; + + mTable->UnselectCol(aColIdx); + return NS_OK; +} + +nsresult +xpcAccessibleTable::UnselectRow(PRInt32 aRowIdx) +{ + if (!mTable) + return NS_ERROR_FAILURE; + + if (aRowIdx < 0 || aRowIdx >= mTable->RowCount()) + return NS_ERROR_INVALID_ARG; + + mTable->UnselectRow(aRowIdx); + return NS_OK; +} + +
--- a/accessible/src/xpcom/xpcAccessibleTable.h +++ b/accessible/src/xpcom/xpcAccessibleTable.h @@ -18,19 +18,21 @@ class TableAccessible; } class xpcAccessibleTable { public: xpcAccessibleTable(mozilla::a11y::TableAccessible* aTable) : mTable(aTable) { } nsresult GetCaption(nsIAccessible** aCaption); + nsresult GetSummary(nsAString& aSummary); nsresult GetColumnCount(PRInt32* aColumnCount); nsresult GetRowCount(PRInt32* aRowCount); - nsresult GetSummary(nsAString& aSummary); + nsresult UnselectColumn(PRInt32 aColIdx); + nsresult UnselectRow(PRInt32 aRowIdx); nsresult IsProbablyForLayout(bool* aIsForLayout); protected: mozilla::a11y::TableAccessible* mTable; }; #define NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE \ NS_IMETHOD GetCaption(nsIAccessible** aCaption) \ @@ -57,14 +59,16 @@ protected: NS_SCRIPTABLE NS_IMETHOD GetSelectedColumnCount(PRUint32 *aSelectedColumnCount); \ NS_SCRIPTABLE NS_IMETHOD GetSelectedRowCount(PRUint32 *aSelectedRowCount); \ NS_SCRIPTABLE NS_IMETHOD GetSelectedCells(nsIArray * *aSelectedCells); \ NS_SCRIPTABLE NS_IMETHOD GetSelectedCellIndices(PRUint32 *cellsArraySize NS_OUTPARAM, PRInt32 **cellsArray NS_OUTPARAM); \ NS_SCRIPTABLE NS_IMETHOD GetSelectedColumnIndices(PRUint32 *rowsArraySize NS_OUTPARAM, PRInt32 **rowsArray NS_OUTPARAM); \ NS_SCRIPTABLE NS_IMETHOD GetSelectedRowIndices(PRUint32 *rowsArraySize NS_OUTPARAM, PRInt32 **rowsArray NS_OUTPARAM); \ NS_SCRIPTABLE NS_IMETHOD SelectRow(PRInt32 rowIndex); \ NS_SCRIPTABLE NS_IMETHOD SelectColumn(PRInt32 columnIndex); \ - NS_SCRIPTABLE NS_IMETHOD UnselectColumn(PRInt32 columnIndex); \ - NS_IMETHOD UnselectRow(PRInt32 aRowIdx); \ + NS_SCRIPTABLE NS_IMETHOD UnselectColumn(PRInt32 aColIdx) \ + { return xpcAccessibleTable::UnselectColumn(aColIdx); } \ + NS_IMETHOD UnselectRow(PRInt32 aRowIdx) \ + { return xpcAccessibleTable::UnselectRow(aRowIdx); } \ NS_IMETHOD IsProbablyForLayout(bool* aResult) \ { return xpcAccessibleTable::IsProbablyForLayout(aResult); } \ #endif // MOZILLA_A11Y_XPCOM_XPACCESSIBLETABLE_H_
--- a/accessible/src/xul/nsXULListboxAccessible.cpp +++ b/accessible/src/xul/nsXULListboxAccessible.cpp @@ -770,39 +770,29 @@ nsXULListboxAccessible::SelectRow(PRInt3 NS_IMETHODIMP nsXULListboxAccessible::SelectColumn(PRInt32 aColumn) { // xul:listbox and xul:richlistbox support row selection only. return NS_OK; } -NS_IMETHODIMP -nsXULListboxAccessible::UnselectRow(PRInt32 aRow) +void +nsXULListboxAccessible::UnselectRow(PRUint32 aRowIdx) { - if (IsDefunct()) - return NS_ERROR_FAILURE; - nsCOMPtr<nsIDOMXULMultiSelectControlElement> control = do_QueryInterface(mContent); NS_ASSERTION(control, "Doesn't implement nsIDOMXULMultiSelectControlElement."); - nsCOMPtr<nsIDOMXULSelectControlItemElement> item; - control->GetItemAtIndex(aRow, getter_AddRefs(item)); - NS_ENSURE_TRUE(item, NS_ERROR_INVALID_ARG); - - return control->RemoveItemFromSelection(item); -} - -NS_IMETHODIMP -nsXULListboxAccessible::UnselectColumn(PRInt32 aColumn) -{ - // xul:listbox and xul:richlistbox support row selection only. - return NS_OK; + if (control) { + nsCOMPtr<nsIDOMXULSelectControlItemElement> item; + control->GetItemAtIndex(aRowIdx, getter_AddRefs(item)); + control->RemoveItemFromSelection(item); + } } //////////////////////////////////////////////////////////////////////////////// // nsXULListboxAccessible: Widgets bool nsXULListboxAccessible::IsWidget() const {
--- a/accessible/src/xul/nsXULListboxAccessible.h +++ b/accessible/src/xul/nsXULListboxAccessible.h @@ -102,16 +102,17 @@ public: NS_DECL_ISUPPORTS_INHERITED // nsIAccessibleTable NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE // TableAccessible virtual PRUint32 ColCount(); virtual PRUint32 RowCount(); + virtual void UnselectRow(PRUint32 aRowIdx); // nsAccessNode virtual void Shutdown(); // nsAccessible virtual void Value(nsString& aValue); virtual mozilla::a11y::TableAccessible* AsTable() { return this; } virtual mozilla::a11y::role NativeRole();
--- a/accessible/src/xul/nsXULTreeGridAccessible.cpp +++ b/accessible/src/xul/nsXULTreeGridAccessible.cpp @@ -540,33 +540,27 @@ nsXULTreeGridAccessible::SelectRow(PRInt } NS_IMETHODIMP nsXULTreeGridAccessible::SelectColumn(PRInt32 aColumnIndex) { return NS_OK; } -NS_IMETHODIMP -nsXULTreeGridAccessible::UnselectRow(PRInt32 aRowIndex) +void +nsXULTreeGridAccessible::UnselectRow(PRUint32 aRowIdx) { if (!mTreeView) - return NS_ERROR_INVALID_ARG; + return; nsCOMPtr<nsITreeSelection> selection; mTreeView->GetSelection(getter_AddRefs(selection)); - NS_ENSURE_STATE(selection); - - return selection->ClearRange(aRowIndex, aRowIndex); -} - -NS_IMETHODIMP -nsXULTreeGridAccessible::UnselectColumn(PRInt32 aColumnIndex) -{ - return NS_OK; + + if (selection) + selection->ClearRange(aRowIdx, aRowIdx); } //////////////////////////////////////////////////////////////////////////////// // nsXULTreeGridAccessible: nsAccessNode implementation void nsXULTreeGridAccessible::Shutdown() {
--- a/accessible/src/xul/nsXULTreeGridAccessible.h +++ b/accessible/src/xul/nsXULTreeGridAccessible.h @@ -58,16 +58,17 @@ public: NS_DECL_ISUPPORTS_INHERITED // nsIAccessibleTable NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE // TableAccessible virtual PRUint32 ColCount(); virtual PRUint32 RowCount(); + virtual void UnselectRow(PRUint32 aRowIdx); // nsAccessNode virtual void Shutdown(); // nsAccessible virtual mozilla::a11y::TableAccessible* AsTable() { return this; } virtual mozilla::a11y::role NativeRole();
--- a/accessible/tests/mochitest/actions/test_keys.html +++ b/accessible/tests/mochitest/actions/test_keys.html @@ -21,18 +21,18 @@ is(acc.keyboardShortcut, aKey, "Wrong keyboard shortcut on " + prettyName(aAccOrElmOrID)); } function doTest() { testKeyboardShortcut("input1", ""); - testKeyboardShortcut("input2", MAC ? "⌃%b" : "Alt+Shift+b"); - testKeyboardShortcut("link", MAC ? "⌃%l" : "Alt+Shift+l"); + testKeyboardShortcut("input2", MAC ? "⌃⌥b" : "Alt+Shift+b"); + testKeyboardShortcut("link", MAC ? "⌃⌥l" : "Alt+Shift+l"); SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script>
--- a/accessible/tests/mochitest/bounds/test_zoom.html +++ b/accessible/tests/mochitest/bounds/test_zoom.html @@ -17,16 +17,24 @@ <script type="application/javascript" src="../layout.js"></script> <script type="application/javascript" src="../browser.js"></script> <script type="application/javascript"> function doTest() { + // Bug 746176: Failure of this whole test file on OS X. + if (MAC) { + todo(false, "Fix bug 746176 on Mac"); + closeBrowserWindow(); + SimpleTest.finish(); + return; + } + var tabDocument = currentTabDocument(); var p1 = tabDocument.getElementById("p1"); var p2 = tabDocument.getElementById("p2"); var imgMap = tabDocument.getElementById("imgmap"); ensureImageMapTree(imgMap); var imgMapAcc = getAccessible(imgMap); var area = imgMapAcc.firstChild;
--- a/accessible/tests/mochitest/events/test_focus_autocomplete.xul +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul @@ -379,18 +379,18 @@ // Tests //gA11yEventDumpID = "eventdump"; // debug stuff //gA11yEventDumpToConsole = true; // debug stuff var gInitQueue = null; function initTests() { - if (SEAMONKEY) { - todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237)"); + if (SEAMONKEY || MAC) { + todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)"); shutdownAutoComplete(); SimpleTest.finish(); return; } gInitQueue = new eventQueue(); gInitQueue.push(new loadFormAutoComplete("iframe")); gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello"));
--- a/accessible/tests/mochitest/events/test_focus_browserui.xul +++ b/accessible/tests/mochitest/events/test_focus_browserui.xul @@ -91,23 +91,27 @@ browserWindow())); // open new url, focus moves to new document gQueue.push(new loadURI(gButtonDocURI)); // back one page in history, moves moves on input of tab document gQueue.push(new goBack()); +if (!MAC) { // open new tab, focus moves to urlbar gQueue.push(new synthKey(tabDocument, "t", { ctrlKey: true, window: browserWindow() }, new focusChecker(urlbarInput))); // close open tab, focus goes on input of tab document gQueue.push(new synthKey(tabDocument, "w", { ctrlKey: true, window: browserWindow() }, new focusChecker(inputInDocument))); +} else { + todo(false, "Reenable on Mac after fixing bug 746178!"); +} gQueue.onFinish = function() { closeBrowserWindow(); } gQueue.invoke(); }
--- a/accessible/tests/mochitest/events/test_focus_contextmenu.xul +++ b/accessible/tests/mochitest/events/test_focus_contextmenu.xul @@ -22,16 +22,23 @@ <script type="application/javascript"> //gA11yEventDumpID = "eventdump"; // debug stuff //gA11yEventDumpToConsole = true; // debug stuff var gQueue = null; function doTests() { + // bug 746183 - Whole file times out on OS X + if (MAC) { + todo(false, "Reenable on mac after fixing bug 746183!"); + SimpleTest.finish(); + return; + } + // Test focus events. gQueue = new eventQueue(); gQueue.push(new synthFocus("button")); gQueue.push(new synthContextMenu("button", new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu"))); gQueue.push(new synthEscapeKey("contextmenu", new focusChecker("button")));
--- a/accessible/tests/mochitest/events/test_focus_general.html +++ b/accessible/tests/mochitest/events/test_focus_general.html @@ -104,18 +104,18 @@ gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc))); } gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker())); gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker())); gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc))); if (SEAMONKEY) { todo(false, "shift tab from editable document fails on (Windows) SeaMonkey! (Bug 718235)"); } else { - if (LINUX) - todo(false, "shift tab from editable document fails on linux!"); + if (LINUX || MAC) + todo(false, "shift tab from editable document fails on linux and Mac, bug 746519!"); else gQueue.push(new synthShiftTab("link", new focusChecker("link"))); } // ! SEAMONKEY gQueue.invoke(); // Will call SimpleTest.finish(); } SimpleTest.waitForExplicitFinish();
--- a/accessible/tests/mochitest/events/test_focus_general.xul +++ b/accessible/tests/mochitest/events/test_focus_general.xul @@ -68,32 +68,36 @@ gQueue.push(new synthClick("popupbutton", new nofocusChecker())); // select first menu item ("item 1"), focus on menu item gQueue.push(new synthDownKey("popupbutton", new focusChecker("bp_item1"))); // choose select menu item, focus gets back to menubutton gQueue.push(new synthEnterKey("bp_item1", new focusChecker("menubutton"))); // show popup again for the next test gQueue.push(new synthClick("popupbutton", new nofocusChecker())); +if (MAC) { // click menubutton of the 'menubutton' button while popup of button open. gQueue.push(new synthClick("mbb", new focusChecker("mbb"), { where: "right" })); // close popup, focus stays on menubutton, fire focus event gQueue.push(new synthEscapeKey("mbb", new focusChecker("mbb"))); // click menubutton, open popup, focus stays on menubutton gQueue.push(new synthClick("mbb", new nofocusChecker(), { where: "right" })); // select first menu item ("item 1"), focus on menu item gQueue.push(new synthDownKey("mbb", new focusChecker("mbb_item1"))); // choose select menu item, focus gets back to menubutton gQueue.push(new synthEnterKey("mbb_item1", new focusChecker("mbb"))); // open popup, focus stays on menubutton gQueue.push(new synthOpenComboboxKey("mbb", new nofocusChecker())); // select second menu item ("item 2"), focus on menu item gQueue.push(new synthUpKey("menubutton", new focusChecker("mbb_item2"))); // click on menu item of menubutton menu, focus menubutton gQueue.push(new synthClick("mbb_item2", new focusChecker("mbb"))); +} else { + todo(false, "mbb tests time out on OS X, fix bug 746970 and reenable!"); +} // focus colorpicker button gQueue.push(new synthFocus("colorpicker")); // click on button, open popup, focus goes to current color button var btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 0 }; var checker = new focusChecker(getColorBtn, btnObj); gQueue.push(new synthClick("colorpicker", checker)); // select sibling color button, focus on it
--- a/accessible/tests/mochitest/events/test_focus_listcontrols.xul +++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xul @@ -45,35 +45,41 @@ gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1"))); gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true })); gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox"))); gQueue.push(new synthFocus("menulist")); gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine"))); gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade"))); gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist"))); +if (!MAC) { gQueue.push(new synthDownKey("menulist", new nofocusChecker("ml_marmalade"))); gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker("ml_marmalade"))); gQueue.push(new synthEnterKey("ml_marmalade", new focusChecker("menulist"))); +} else { + todo(false, "Bug 746531 - timeouts of last three menulist tests on OS X"); +} var textentry = getAccessible("emenulist").firstChild; gQueue.push(new synthFocus("emenulist", new focusChecker(textentry))); gQueue.push(new synthDownKey(textentry, new nofocusChecker("eml_tangerine"))); gQueue.push(new synthUpKey(textentry, new focusChecker("eml_marmalade"))); gQueue.push(new synthEnterKey("eml_marmalade", new focusChecker(textentry))); gQueue.push(new synthOpenComboboxKey("emenulist", new focusChecker("eml_marmalade"))); gQueue.push(new synthEscapeKey("eml_marmalade", new focusChecker(textentry))); // no focus events for unfocused list controls when current item is // changed. gQueue.push(new synthFocus("emptylistbox")); gQueue.push(new changeCurrentItem("listbox", "lb_item1")); gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1")); +if (!MAC) { gQueue.push(new changeCurrentItem("menulist", "ml_tangerine")); +} gQueue.push(new changeCurrentItem("emenulist", "eml_tangerine")); gQueue.invoke(); // Will call SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTests); </script>
--- a/accessible/tests/mochitest/events/test_focus_selects.html +++ b/accessible/tests/mochitest/events/test_focus_selects.html @@ -23,16 +23,23 @@ <script type="application/javascript"> //gA11yEventDumpID = "eventdump"; // debug stuff //gA11yEventDumpToConsole = true; var gQueue = null; function doTests() { + // Bug 746534 - File causes crash or hang on OS X + if (MAC) { + todo(false, "Bug 746534 - test file causes crash or hang on OS X"); + SimpleTest.finish(); + return; + } + gQueue = new eventQueue(); // first item is focused until there's selection gQueue.push(new synthFocus("list", new focusChecker("orange"))); // item is selected and stays focused gQueue.push(new synthDownKey("list", new nofocusChecker()));
--- a/accessible/tests/mochitest/events/test_focus_tabbox.xul +++ b/accessible/tests/mochitest/events/test_focus_tabbox.xul @@ -22,16 +22,22 @@ <script type="application/javascript"> //gA11yEventDumpID = "eventdump"; // debug stuff //gA11yEventDumpToConsole = true; // debug stuff var gQueue = null; function doTests() { + if (MAC) { + todo(false, "Tests disabled because of imminent failure."); + SimpleTest.finish(); + return; + } + // Test focus events. gQueue = new eventQueue(); gQueue.push(new synthClick("tab1", new focusChecker("tab1"))); gQueue.push(new synthTab("tab1", new focusChecker("checkbox1"))); gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true }, new focusChecker("textbox"))); gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true },
--- a/accessible/tests/mochitest/hittest/test_general.html +++ b/accessible/tests/mochitest/hittest/test_general.html @@ -14,17 +14,21 @@ <script type="application/javascript"> function doTest() { // Not specific case, child and deepchild testing. var list = getAccessible("list"); var listitem = getAccessible("listitem"); var image = getAccessible("image"); +if (!MAC) { testChildAtPoint(list, 1, 1, listitem, image.firstChild); +} else { + todo(false, "Bug 746974 - children must match on all platforms, disable failing test on Mac"); +} // ::MustPrune case (in this case childAtPoint doesn't look inside a // textbox), point is inside of textbox. var txt = getAccessible("txt"); testChildAtPoint(txt, 1, 1, txt, txt); // ::MustPrune case, point is outside of textbox accessible but is in // document.
--- a/accessible/tests/mochitest/hittest/test_zoom.html +++ b/accessible/tests/mochitest/hittest/test_zoom.html @@ -15,29 +15,33 @@ <script type="application/javascript" src="../layout.js"></script> <script type="application/javascript" src="../browser.js"></script> <script type="application/javascript"> function doTest() { +if (!MAC) { var tabDocument = currentTabDocument(); var p1 = tabDocument.body.firstElementChild; var p2 = tabDocument.body.lastElementChild; hitTest(tabDocument, p1, p1.firstChild); hitTest(tabDocument, p2, p2.firstChild); zoomDocument(tabDocument, 2.0); hitTest(tabDocument, p1, p1.firstChild); hitTest(tabDocument, p2, p2.firstChild); closeBrowserWindow(); +} else { + todo(false, "Bug 746974 - deepest child must be correct on all platforms, disabling on Mac!"); +} SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); openBrowserWindow(doTest, "data:text/html,<html><body><p>para 1</p><p>para 2</p></body></html>", { left: 100, top: 100 }); </script>
--- a/accessible/tests/mochitest/states/test_inputs.html +++ b/accessible/tests/mochitest/states/test_inputs.html @@ -106,16 +106,17 @@ // autocomplete states testStates("autocomplete-default", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); testStates("autocomplete-off", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); testStates("autocomplete-formoff", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); testStates("autocomplete-list", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); testStates("autocomplete-list2", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); testStates("autocomplete-tel", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); testStates("autocomplete-email", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-search", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script> </head> @@ -155,16 +156,17 @@ title="File input control should be propogate states to descendants"> Mozilla Bug 699017 </a> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=733382" title="Editable state bit should be present on readonly inputs"> Mozilla Bug 733382 </a> + <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <form> <input id="input" type="input"> @@ -238,10 +240,17 @@ Email Address: <input id="autocomplete-email" type="email" list="contacts" value="xyzzy"> <datalist id="contacts"> <option>xyzzy@plughs.com</option> <option>nobody@mozilla.org</option> </datalist> + </br>Search for: + <input id="autocomplete-search" type="search" list="searchhisty" value="Gamma"> + <datalist id="searchhisty"> + <option>Gamma Rays</option> + <option>Gamma Ray Bursts</option> + </datalist> + </body> </html>
--- a/accessible/tests/mochitest/tree/test_cssoverflow.html +++ b/accessible/tests/mochitest/tree/test_cssoverflow.html @@ -82,16 +82,23 @@ // Do tests var gQueue = null; //gA11yEventDumpID = "eventdump"; // debug stuff //gA11yEventDumpToConsole = true; function doTests() { + // Shift+Tab not working, and a test timeout, bug 746977 + if (MAC) { + todo(false, "Shift+tab isn't working on OS X, needs to be disabled until bug 746977 is fixed!"); + SimpleTest.finish(); + return; + } + gQueue = new eventQueue(); // CSS 'overflow: scroll' property setting and unsetting causes accessible // recreation (and fire show/hide events). For example, the focus and // blur of HTML:a with ':focus {overflow: scroll; }' CSS style causes its // accessible recreation. The focus event should be fired on new // accessible. gQueue.push(new focusAnchor("a"));
--- a/accessible/tests/mochitest/tree/test_dochierarchy.html +++ b/accessible/tests/mochitest/tree/test_dochierarchy.html @@ -26,17 +26,17 @@ getAccessible(window.parent.document, [nsIAccessibleDocument]) : getAccessible(document, [nsIAccessibleDocument]); var testDoc = getAccessible(document, [nsIAccessibleDocument]); var iframeDoc = getAccessible("iframe").firstChild. QueryInterface(nsIAccessibleDocument); is(root.parentDocument, null, "Wrong parent document of root accessible"); - is(root.childDocumentCount, 1, + is(root.childDocumentCount, SEAMONKEY ? 1 : 3, "Wrong child document count of root accessible"); is(root.getChildDocumentAt(0), tabDoc, "Wrong child document at index 0 of root accessible"); is(tabDoc.parentDocument, root, "Wrong parent document of tab document"); is(tabDoc.childDocumentCount, 1, "Wrong child document count of tab document");
--- a/accessible/tests/mochitest/tree/test_txtctrl.html +++ b/accessible/tests/mochitest/tree/test_txtctrl.html @@ -98,16 +98,29 @@ role: ROLE_TEXT_LEAF, children: [] } ] }; testAccessibleTree("txc7", accTree); + // input@type="search", value + accTree = { + role: ROLE_ENTRY, + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [] + } + ] + }; + + testAccessibleTree("txc8", accTree); + SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script> </head> <body> @@ -147,10 +160,17 @@ Email Address: <input id="txc7" type="email" list="contacts" value="xyzzy"> <datalist id="contacts"> <option>xyzzy@plughs.com</option> <option>nobody@mozilla.org</option> </datalist> + </br>Search for: + <input id="txc8" type="search" list="searchhisty" value="Gamma"> + <datalist id="searchhisty"> + <option>Gamma Rays</option> + <option>Gamma Ray Bursts</option> + </datalist> + </body> </html>
--- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,14 +1,13 @@ dnl dnl Local autoconf macros used with mozilla dnl The contents of this file are under the Public Domain. dnl -builtin(include, build/autoconf/glib.m4)dnl builtin(include, build/autoconf/nspr.m4)dnl builtin(include, build/autoconf/nss.m4)dnl builtin(include, build/autoconf/pkg.m4)dnl builtin(include, build/autoconf/codeset.m4)dnl builtin(include, build/autoconf/altoptions.m4)dnl builtin(include, build/autoconf/mozprog.m4)dnl builtin(include, build/autoconf/mozheader.m4)dnl builtin(include, build/autoconf/mozcommonheader.m4)dnl
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1040,32 +1040,40 @@ pref("services.sync.prefs.sync.security. pref("services.sync.prefs.sync.signon.rememberSignons", true); pref("services.sync.prefs.sync.spellchecker.dictionary", true); pref("services.sync.prefs.sync.xpinstall.whitelist.required", true); #endif // Disable the error console pref("devtools.errorconsole.enabled", false); +// Enable the developer toolbar +pref("devtools.toolbar.enabled", false); + // Enable the Inspector pref("devtools.inspector.enabled", true); pref("devtools.inspector.htmlHeight", 112); pref("devtools.inspector.htmlPanelOpen", false); pref("devtools.inspector.sidebarOpen", false); pref("devtools.inspector.activeSidebar", "ruleview"); // Enable the Layout View pref("devtools.layoutview.enabled", false); pref("devtools.layoutview.open", false); // Enable the Debugger pref("devtools.debugger.enabled", false); +pref("devtools.debugger.remote-enabled", false); +pref("devtools.debugger.remote-host", "localhost"); +pref("devtools.debugger.remote-port", 6000); // The default Debugger UI height pref("devtools.debugger.ui.height", 250); +pref("devtools.debugger.ui.remote-win.width", 900); +pref("devtools.debugger.ui.remote-win.height", 400); // Enable the style inspector pref("devtools.styleinspector.enabled", true); // Enable the Tilt inspector pref("devtools.tilt.enabled", true); pref("devtools.tilt.intro_transition", true); pref("devtools.tilt.outro_transition", true); @@ -1078,18 +1086,24 @@ pref("devtools.scratchpad.enabled", true // Enable the Style Editor. pref("devtools.styleeditor.enabled", true); pref("devtools.styleeditor.transitions", true); // Enable tools for Chrome development. pref("devtools.chrome.enabled", false); -// Disable the GCLI enhanced command line. -pref("devtools.gcli.enable", false); +// Display the introductory text +pref("devtools.gcli.hideIntro", false); + +// How eager are we to show help: never=1, sometimes=2, always=3 +pref("devtools.gcli.eagerHelper", 2); + +// Do we allow the 'pref set' command +pref("devtools.gcli.allowSet", false); // The last Web Console height. This is initially 0 which means that the Web // Console will use the default height next time it shows. // Change to -1 if you do not want the Web Console to remember its last height. pref("devtools.hud.height", 0); // Remember the Web Console position. Possible values: // above - above the web page,
--- a/browser/base/content/browser-appmenu.inc +++ b/browser/base/content/browser-appmenu.inc @@ -171,34 +171,45 @@ <menuitem id="appmenu_printSetup" label="&printSetupCmd.label;" command="cmd_pageSetup"/> </menupopup> </splitmenu> <menuseparator class="appmenu-menuseparator"/> <menu id="appmenu_webDeveloper" label="&appMenuWebDeveloper.label;"> - <menupopup id="appmenu_webDeveloper_popup" - onpopupshowing="onWebDeveloperMenuShowing();"> + <menupopup id="appmenu_webDeveloper_popup"> + <menuitem id="appmenu_devToolbar" + type="checkbox" + autocheck="false" + hidden="true" + label="&devToolbarMenu.label;" + command="Tools:DevToolbar" + key="key_devToolbar"/> <menuitem id="appmenu_webConsole" label="&webConsoleCmd.label;" type="checkbox" command="Tools:WebConsole" key="key_webConsole"/> <menuitem id="appmenu_pageInspect" hidden="true" label="&inspectMenu.label;" type="checkbox" command="Tools:Inspect" key="key_inspect"/> <menuitem id="appmenu_debugger" hidden="true" + type="checkbox" label="&debuggerMenu.label;" key="key_debugger" command="Tools:Debugger"/> + <menuitem id="appmenu_remoteDebugger" + hidden="true" + label="&remoteDebuggerMenu.label;" + command="Tools:RemoteDebugger"/> <menuitem id="appmenu_scratchpad" hidden="true" label="&scratchpad.label;" key="key_scratchpad" command="Tools:Scratchpad"/> <menuitem id="appmenu_styleeditor" hidden="true" label="&styleeditor.label;"
--- a/browser/base/content/browser-fullZoom.js +++ b/browser/base/content/browser-fullZoom.js @@ -220,17 +220,17 @@ var FullZoom = { * @param aBrowser * (optional) browser object displaying the document */ onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) { if (!aURI || (aIsTabSwitch && !this.siteSpecific)) return; // Avoid the cps roundtrip and apply the default/global pref. - if (isBlankPageURL(aURI.spec)) { + if (aURI.spec == "about:blank") { this._applyPrefToSetting(undefined, aBrowser); return; } let browser = aBrowser || gBrowser.selectedBrowser; // Media documents should always start at 1, and are not affected by prefs. if (!aIsTabSwitch && browser.contentDocument.mozSyntheticDocument) {
--- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -526,36 +526,48 @@ accesskey="&syncSyncNowItem.accesskey;" observes="sync-syncnow-state" oncommand="gSyncUI.doSync(event);"/> #endif <menuseparator id="devToolsSeparator"/> <menu id="webDeveloperMenu" label="&webDeveloperMenu.label;" accesskey="&webDeveloperMenu.accesskey;"> - <menupopup id="menuWebDeveloperPopup" - onpopupshowing="onWebDeveloperMenuShowing();"> + <menupopup id="menuWebDeveloperPopup"> + <menuitem id="menu_devToolbar" + type="checkbox" + autocheck="false" + hidden="true" + label="&devToolbarMenu.label;" + accesskey="&devToolbarMenu.accesskey;" + key="key_devToolbar" + command="Tools:DevToolbar"/> <menuitem id="webConsole" type="checkbox" label="&webConsoleCmd.label;" accesskey="&webConsoleCmd.accesskey;" key="key_webConsole" command="Tools:WebConsole"/> <menuitem id="menu_pageinspect" type="checkbox" hidden="true" label="&inspectMenu.label;" accesskey="&inspectMenu.accesskey;" key="key_inspect" command="Tools:Inspect"/> <menuitem id="menu_debugger" hidden="true" + type="checkbox" label="&debuggerMenu.label;" key="key_debugger" command="Tools:Debugger"/> + <menuitem id="menu_remoteDebugger" + hidden="true" + label="&remoteDebuggerMenu.label;" + command="Tools:RemoteDebugger"/> <menuitem id="menu_scratchpad" hidden="true" label="&scratchpad.label;" accesskey="&scratchpad.accesskey;" key="key_scratchpad" command="Tools:Scratchpad"/> <menuitem id="menu_styleeditor" hidden="true"
--- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -121,19 +121,21 @@ <command id="cmd_fullZoomReduce" oncommand="FullZoom.reduce()"/> <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/> <command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/> <command id="cmd_fullZoomToggle" oncommand="ZoomManager.toggleZoom();"/> <command id="Browser:OpenLocation" oncommand="openLocation();"/> <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/> <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/> + <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true"/> <command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/> <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();" disabled="true"/> <command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true"/> + <command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true"/> <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true"/> <command id="Tools:StyleEditor" oncommand="StyleEditor.openChrome();" disabled="true"/> <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/> <command id="Tools:Sanitize" oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/> <command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/> <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/> <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/> @@ -250,16 +252,23 @@ #ifdef XP_GNOME <key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/> <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/> #else <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/> #endif <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/> <key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift" disabled="true"/> + <key id="key_devToolbar" key="&devToolbar.commandkey;" command="Tools:DevToolbar" +#ifdef XP_MACOSX + modifiers="accel,alt" +#else + modifiers="accel,shift" +#endif + /> <key id="key_webConsole" key="&webConsoleCmd.commandkey;" oncommand="HUDConsoleUI.toggleHUD();" #ifdef XP_MACOSX modifiers="accel,alt" #else modifiers="accel,shift" #endif /> <key id="key_debugger" key="&debuggerMenu.commandkey;" command="Tools:Debugger"
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -174,16 +174,22 @@ XPCOMUtils.defineLazyGetter(this, "Popup return new tmp.PopupNotifications(gBrowser, document.getElementById("notification-popup"), document.getElementById("notification-popup-box")); } catch (ex) { Cu.reportError(ex); } }); +XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() { + let tmp = {}; + Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp); + return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar")); +}); + XPCOMUtils.defineLazyGetter(this, "InspectorUI", function() { let tmp = {}; Cu.import("resource:///modules/inspector.jsm", tmp); return new tmp.InspectorUI(window); }); XPCOMUtils.defineLazyGetter(this, "DebuggerUI", function() { let tmp = {}; @@ -1691,34 +1697,56 @@ function delayedStartup(isLoadingBlank, TabView.init(); setUrlAndSearchBarWidthForConditionalForwardButton(); window.addEventListener("resize", function resizeHandler(event) { if (event.target == window) setUrlAndSearchBarWidthForConditionalForwardButton(); }); + // Enable developer toolbar? + let devToolbarEnabled = gPrefService.getBoolPref("devtools.toolbar.enabled"); + if (devToolbarEnabled) { + document.getElementById("menu_devToolbar").hidden = false; + document.getElementById("Tools:DevToolbar").removeAttribute("disabled"); +#ifdef MENUBAR_CAN_AUTOHIDE + document.getElementById("appmenu_devToolbar").hidden = false; +#endif + } + // Enable Inspector? let enabled = gPrefService.getBoolPref("devtools.inspector.enabled"); if (enabled) { document.getElementById("menu_pageinspect").hidden = false; document.getElementById("Tools:Inspect").removeAttribute("disabled"); #ifdef MENUBAR_CAN_AUTOHIDE document.getElementById("appmenu_pageInspect").hidden = false; #endif + document.getElementById("developer-toolbar-inspector").hidden = false; } // Enable Debugger? let enabled = gPrefService.getBoolPref("devtools.debugger.enabled"); if (enabled) { document.getElementById("menu_debugger").hidden = false; document.getElementById("Tools:Debugger").removeAttribute("disabled"); #ifdef MENUBAR_CAN_AUTOHIDE document.getElementById("appmenu_debugger").hidden = false; #endif + document.getElementById("developer-toolbar-debugger").hidden = false; + } + + // Enable Remote Debugger? + let enabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled"); + if (enabled) { + document.getElementById("menu_remoteDebugger").hidden = false; + document.getElementById("Tools:RemoteDebugger").removeAttribute("disabled"); +#ifdef MENUBAR_CAN_AUTOHIDE + document.getElementById("appmenu_remoteDebugger").hidden = false; +#endif } // Enable Error Console? // XXX Temporarily always-enabled, see bug 601201 let consoleEnabled = true || gPrefService.getBoolPref("devtools.errorconsole.enabled"); if (consoleEnabled) { document.getElementById("javascriptConsole").hidden = false; document.getElementById("key_errorConsole").removeAttribute("disabled"); @@ -9276,20 +9304,16 @@ var StyleEditor = { args.wrappedJSObject = args; let chromeWindow = Services.ww.openWindow(null, CHROME_URL, "_blank", CHROME_WINDOW_FLAGS, args); chromeWindow.focus(); return chromeWindow; } }; -function onWebDeveloperMenuShowing() { - document.getElementById("Tools:WebConsole").setAttribute("checked", HUDConsoleUI.getOpenHUD() != null); -} - XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () { #ifdef XP_WIN // Only show resizers on Windows 2000 and XP let sysInfo = Components.classes["@mozilla.org/system-info;1"] .getService(Components.interfaces.nsIPropertyBag2); return parseFloat(sysInfo.getProperty("version")) < 6; #else
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -1034,16 +1034,70 @@ <!-- registered tools go here --> </hbox> #ifndef XP_MACOSX <toolbarbutton id="highlighter-closebutton" oncommand="InspectorUI.closeInspectorUI(false);" tooltiptext="&inspectCloseButton.tooltiptext;"/> #endif </toolbar> + + <panel id="gcli-tooltip" + type="arrow" + noautofocus="true" + noautohide="true" + class="gcli-panel"> + <iframe id="gcli-tooltip-frame" + src="chrome://browser/content/devtools/gcliblank.xhtml" + flex="1"/> + </panel> + <panel id="gcli-output" + type="arrow" + noautofocus="true" + noautohide="true" + class="gcli-panel"> + <iframe id="gcli-output-frame" + src="chrome://browser/content/devtools/gcliblank.xhtml" + flex="1"/> + </panel> + + <toolbar id="developer-toolbar" + class="devtools-toolbar" + hidden="true"> +#ifdef XP_MACOSX + <toolbarbutton id="developer-toolbar-closebutton" + oncommand="DeveloperToolbar.hide();" + tooltiptext="&devToolbarCloseButton.tooltiptext;"/> +#endif + <stack class="gclitoolbar-stack-node" flex="1"> + <description class="gclitoolbar-prompt">»</description> + <description class="gclitoolbar-complete-node"/> + <textbox class="gclitoolbar-input-node" rows="1"/> + </stack> + <toolbarbutton id="developer-toolbar-webconsole" + label="&webConsoleButton.label;" + class="devtools-toolbarbutton" + command="Tools:WebConsole"/> + <toolbarbutton id="developer-toolbar-inspector" + label="&inspectorButton.label;" + class="devtools-toolbarbutton" + hidden="true" + command="Tools:Inspect"/> + <toolbarbutton id="developer-toolbar-debugger" + label="&scriptsButton.label;" + class="devtools-toolbarbutton" + hidden="true" + command="Tools:Debugger"/> +#ifndef XP_MACOSX + <toolbarbutton id="developer-toolbar-closebutton" + oncommand="DeveloperToolbar.hide();" + tooltiptext="&devToolbarCloseButton.tooltiptext;"/> +#endif + </toolbar> + <toolbar id="addon-bar" toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;" collapsed="true" class="toolbar-primary chromeclass-toolbar" context="toolbar-context-menu" toolboxid="navigator-toolbox" mode="icons" iconsize="small" defaulticonsize="small" lockiconsize="true" defaultset="addonbar-closebutton,spring,status-bar"
--- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -245,16 +245,17 @@ endif test_bug435035.html \ test_bug462673.html \ page_style_sample.html \ feed_tab.html \ plugin_unknown.html \ plugin_test.html \ plugin_test2.html \ plugin_test3.html \ + plugin_alternate_content.html \ plugin_both.html \ plugin_both2.html \ plugin_clickToPlayAllow.html \ plugin_clickToPlayDeny.html \ alltabslistener.html \ zoom_test.html \ dummy_page.html \ browser_tabMatchesInAwesomebar.js \
--- a/browser/base/content/test/browser_pluginnotification.js +++ b/browser/base/content/test/browser_pluginnotification.js @@ -402,10 +402,26 @@ function test13c() { } // Tests that the plugin's "activated" property is true for working plugins with click-to-play disabled. function test14() { var plugin = gTestBrowser.contentDocument.getElementById("test"); var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); ok(objLoadingContent.activated, "Test 14, Plugin should be activated"); + var plugin = get_test_plugin(); + plugin.disabled = false; + plugin.blocklisted = false; + Services.perms.removeAll(); + Services.prefs.setBoolPref("plugins.click_to_play", true); + prepareTest(test15, gTestRoot + "plugin_alternate_content.html"); +} + +// Tests that the overlay is shown instead of alternate content when +// plugins are click to play +function test15() { + var plugin = gTestBrowser.contentDocument.getElementById("test"); + var doc = gTestBrowser.contentDocument; + var mainBox = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); + ok(mainBox, "Test 15, Plugin with id=" + plugin.id + " overlay should exist"); + finishTest(); }
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/plugin_alternate_content.html @@ -0,0 +1,9 @@ +<!-- bug 739575 --> +<html> +<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> +</head> +<body> +<object id="test" type="application/x-test" style="height: 200px; width:200px"> +<p><a href="about:blank">you should not see this link when plugins are click-to-play</a></p> +</object> +</body></html>
--- a/browser/devtools/debugger/DebuggerUI.jsm +++ b/browser/devtools/debugger/DebuggerUI.jsm @@ -40,90 +40,137 @@ * * ***** END LICENSE BLOCK ***** */ "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; +const DBG_XUL = "chrome://browser/content/debugger.xul"; +const REMOTE_PROFILE_NAME = "_remote-debug"; + +Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/FileUtils.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); let EXPORTED_SYMBOLS = ["DebuggerUI"]; /** * Provides a simple mechanism of managing debugger instances per tab. * * @param nsIDOMWindow aWindow * The chrome window for which the DebuggerUI instance is created. */ function DebuggerUI(aWindow) { this.chromeWindow = aWindow; } DebuggerUI.prototype = { + /** + * Called by the DebuggerPane to update the Debugger toggle switches with the + * debugger state. + */ + refreshCommand: function DUI_refreshCommand() { + let selectedTab = this.chromeWindow.getBrowser().selectedTab; + let command = this.chromeWindow.document.getElementById("Tools:Debugger"); + + if (this.getDebugger(selectedTab) != null) { + command.setAttribute("checked", "true"); + } else { + command.removeAttribute("checked"); + } + }, /** * Starts a debugger for the current tab, or stops it if already started. * @return DebuggerPane if the debugger is started, null if it's stopped. */ toggleDebugger: function DUI_toggleDebugger() { let tab = this.chromeWindow.gBrowser.selectedTab; if (tab._scriptDebugger) { tab._scriptDebugger.close(); return null; } - return new DebuggerPane(tab); + return new DebuggerPane(this, tab); + }, + + /** + * Starts a remote debugger in a new process, or stops it if already started. + * @see DebuggerProcess.constructor + * @return DebuggerProcess if the debugger is started, null if it's stopped. + */ + toggleRemoteDebugger: function DUI_toggleRemoteDebugger(aOnClose, aOnRun) { + let win = this.chromeWindow; + + if (win._remoteDebugger) { + win._remoteDebugger.close(); + return null; + } + return new DebuggerProcess(win, aOnClose, aOnRun); }, /** * Get the debugger for a specified tab. * @return DebuggerPane if a debugger exists for the tab, null otherwise */ getDebugger: function DUI_getDebugger(aTab) { - return aTab._scriptDebugger; + return '_scriptDebugger' in aTab ? aTab._scriptDebugger : null; }, /** * Get the preferences associated with the debugger frontend. * @return object */ get preferences() { - return DebuggerUIPreferences; + return DebuggerPreferences; } }; /** * Creates a pane that will host the debugger. * * @param XULElement aTab * The tab in which to create the debugger. */ -function DebuggerPane(aTab) { +function DebuggerPane(aDebuggerUI, aTab) { + this._globalUI = aDebuggerUI; this._tab = aTab; + this._initServer(); this._create(); } DebuggerPane.prototype = { /** + * Initializes the debugger server. + */ + _initServer: function DP__initServer() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + }, + + /** * Creates and initializes the widgets containing the debugger UI. */ _create: function DP__create() { this._tab._scriptDebugger = this; let gBrowser = this._tab.linkedBrowser.getTabBrowser(); let ownerDocument = gBrowser.parentNode.ownerDocument; this._splitter = ownerDocument.createElement("splitter"); this._splitter.setAttribute("class", "hud-splitter"); this._frame = ownerDocument.createElement("iframe"); - this._frame.height = DebuggerUIPreferences.height; + this._frame.height = DebuggerPreferences.height; this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser); this._nbox.appendChild(this._splitter); this._nbox.appendChild(this._frame); this.close = this.close.bind(this); let self = this; @@ -134,39 +181,43 @@ DebuggerPane.prototype = { // Bind shortcuts for accessing the breakpoint methods in the debugger. let bkp = self.debuggerWindow.DebuggerController.Breakpoints; self.addBreakpoint = bkp.addBreakpoint; self.removeBreakpoint = bkp.removeBreakpoint; self.getBreakpoint = bkp.getBreakpoint; }, true); - this._frame.setAttribute("src", "chrome://browser/content/debugger.xul"); + this._frame.setAttribute("src", DBG_XUL); + + this._globalUI.refreshCommand(); }, /** * Closes the debugger, removing child nodes and event listeners. */ close: function DP_close() { if (!this._tab) { return; } - this._tab._scriptDebugger = null; + delete this._tab._scriptDebugger; this._tab = null; - DebuggerUIPreferences.height = this._frame.height; + DebuggerPreferences.height = this._frame.height; this._frame.removeEventListener("Debugger:Close", this.close, true); this._frame.removeEventListener("unload", this.close, true); this._nbox.removeChild(this._splitter); this._nbox.removeChild(this._frame); this._splitter = null; this._frame = null; this._nbox = null; + + this._globalUI.refreshCommand(); }, /** * Gets the debugger content window. * @return nsIDOMWindow if a debugger window exists, null otherwise */ get debuggerWindow() { return this._frame ? this._frame.contentWindow : null; @@ -181,19 +232,122 @@ DebuggerPane.prototype = { if (debuggerWindow) { return debuggerWindow.DebuggerController.Breakpoints.store; } return null; } }; /** - * Various debugger UI preferences (currently just the pane height). + * Creates a process that will hold the remote debugger. + * + * @param function aOnClose + * Optional, a function called when the process exits. + * @param function aOnRun + * Optional, a function called when the process starts running. + * @param nsIDOMWindow aWindow + * The chrome window for which the remote debugger instance is created. */ -let DebuggerUIPreferences = { +function DebuggerProcess(aWindow, aOnClose, aOnRun) { + this._win = aWindow; + this._closeCallback = aOnClose; + this._runCallback = aOnRun; + this._initProfile(); + this._create(); +} + +DebuggerProcess.prototype = { + + /** + * Initializes the debugger server. + */ + _initServer: function RDP__initServer() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + DebuggerServer.closeListener(); + DebuggerServer.openListener(DebuggerPreferences.remotePort, false); + }, + + /** + * Initializes a profile for the remote debugger process. + */ + _initProfile: function RDP__initProfile() { + let profileService = Cc["@mozilla.org/toolkit/profile-service;1"] + .createInstance(Ci.nsIToolkitProfileService); + + let dbgProfileName; + try { + dbgProfileName = profileService.selectedProfile.name + REMOTE_PROFILE_NAME; + } catch(e) { + dbgProfileName = REMOTE_PROFILE_NAME; + Cu.reportError(e); + } + + this._dbgProfile = profileService.createProfile(null, null, dbgProfileName); + profileService.flush(); + }, + + /** + * Creates and initializes the profile & process for the remote debugger. + */ + _create: function RDP__create() { + this._win._remoteDebugger = this; + + let file = FileUtils.getFile("CurProcD", + [Services.appinfo.OS == "WINNT" ? "firefox.exe" + : "firefox-bin"]); + + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + let args = [ + "-no-remote", "-P", this._dbgProfile.name, + "-chrome", DBG_XUL, + "-width", DebuggerPreferences.remoteWinWidth, + "-height", DebuggerPreferences.remoteWinHeight]; + + process.runwAsync(args, args.length, { observe: this.close.bind(this) }); + this._dbgProcess = process; + + if (typeof this._runCallback === "function") { + this._runCallback.call({}, this); + } + }, + + /** + * Closes the remote debugger, removing the profile and killing the process. + */ + close: function RDP_close() { + if (!this._win) { + return; + } + delete this._win._remoteDebugger; + this._win = null; + + if (this._dbgProcess.isRunning) { + this._dbgProcess.kill(); + } + if (this._dbgProfile) { + this._dbgProfile.remove(false); + } + if (typeof this._closeCallback === "function") { + this._closeCallback.call({}, this); + } + + this._dbgProcess = null; + this._dbgProfile = null; + } +}; + +/** + * Various debugger preferences. + */ +let DebuggerPreferences = { /** * Gets the preferred height of the debugger pane. * @return number */ get height() { if (this._height === undefined) { this._height = Services.prefs.getIntPref("devtools.debugger.ui.height"); @@ -205,8 +359,40 @@ let DebuggerUIPreferences = { * Sets the preferred height of the debugger pane. * @param number value */ set height(value) { Services.prefs.setIntPref("devtools.debugger.ui.height", value); this._height = value; } }; + +/** + * Gets the preferred width of the remote debugger window. + * @return number + */ +XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinWidth", function() { + return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.width"); +}); + +/** + * Gets the preferred height of the remote debugger window. + * @return number + */ +XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinHeight", function() { + return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.height"); +}); + +/** + * Gets the preferred default remote debugging host. + * @return string + */ +XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteHost", function() { + return Services.prefs.getCharPref("devtools.debugger.remote-host"); +}); + +/** + * Gets the preferred default remote debugging port. + * @return number + */ +XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remotePort", function() { + return Services.prefs.getIntPref("devtools.debugger.remote-port"); +});
--- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -109,29 +109,28 @@ let DebuggerController = { DebuggerView.Properties.destroy(); DebuggerController.SourceScripts.disconnect(); DebuggerController.StackFrames.disconnect(); DebuggerController.ThreadState.disconnect(); this.dispatchEvent("Debugger:Unloaded"); this._disconnect(); + this._isRemote && this._quitApp(); }, /** * Initializes a debugger client and connects it to the debugger server, * wiring event handlers as necessary. */ _connect: function DC__connect() { - if (!DebuggerServer.initialized) { - DebuggerServer.init(); - DebuggerServer.addBrowserActors(); - } + let transport = + this._isRemote ? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort) + : DebuggerServer.connectPipe(); - let transport = DebuggerServer.connectPipe(); let client = this.client = new DebuggerClient(transport); client.addListener("tabNavigated", this._onTabNavigated); client.addListener("tabDetached", this._onTabDetached); client.connect(function(aType, aTraits) { client.listTabs(function(aResponse) { let tab = aResponse.tabs[aResponse.selected]; @@ -216,16 +215,41 @@ let DebuggerController = { }); }); }.bind(this)); }.bind(this)); }, /** + * Returns true if this is a remote debugger instance. + * @return boolean + */ + get _isRemote() { + return !window.parent.content; + }, + + /** + * Attempts to quit the current process if allowed. + */ + _quitApp: function DC__quitApp() { + let canceled = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + + Services.obs.notifyObservers(canceled, "quit-application-requested", null); + + // Somebody canceled our quit request. + if (canceled.data) { + return; + } + + Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit); + }, + + /** * Convenience method, dispatching a custom event. * * @param string aType * The name of the event. * @param string aDetail * The data passed when initializing the event. */ dispatchEvent: function DC_dispatchEvent(aType, aDetail) { @@ -465,52 +489,71 @@ StackFrames.prototype = { let thisVar = localScope.addVar("this"); thisVar.setGrip({ type: frame.this.type, class: frame.this.class }); this._addExpander(thisVar, frame.this); } + if (frame.environment) { + // Add nodes for every argument. + let variables = frame.environment.bindings.arguments; + for each (let variable in variables) { + let name = Object.getOwnPropertyNames(variable)[0]; + let paramVar = localScope.addVar(name); + let paramVal = variable[name].value; + paramVar.setGrip(paramVal); + this._addExpander(paramVar, paramVal); + } + + // Add nodes for every other variable in scope. + variables = frame.environment.bindings.variables; + for (let variable in variables) { + let paramVar = localScope.addVar(variable); + let paramVal = variables[variable].value; + paramVar.setGrip(paramVal); + this._addExpander(paramVar, paramVal); + } + + // If we already found 'arguments', we are done here. + if ("arguments" in frame.environment.bindings.variables) { + // Signal that variables have been fetched. + DebuggerController.dispatchEvent("Debugger:FetchedVariables"); + return; + } + } + + // Sometimes in call frames with arguments we don't get 'arguments' in the + // environment (bug 746601) and we have to construct it manually. Note, that + // in this case arguments.callee will be absent, even in the cases where it + // shouldn't be. if (frame.arguments && frame.arguments.length > 0) { // Add "arguments". let argsVar = localScope.addVar("arguments"); argsVar.setGrip({ type: "object", class: "Arguments" }); this._addExpander(argsVar, frame.arguments); - // Add variables for every argument. - let objClient = this.activeThread.pauseGrip(frame.callee); - objClient.getSignature(function SF_getSignature(aResponse) { - for (let i = 0, l = aResponse.parameters.length; i < l; i++) { - let param = aResponse.parameters[i]; - let paramVar = localScope.addVar(param); - let paramVal = frame.arguments[i]; + // Signal that variables have been fetched. + DebuggerController.dispatchEvent("Debugger:FetchedVariables"); + } - paramVar.setGrip(paramVal); - this._addExpander(paramVar, paramVal); - } - - // Signal that call parameters have been fetched. - DebuggerController.dispatchEvent("Debugger:FetchedParameters"); - - }.bind(this)); - } }, /** - * Adds a onexpand callback for a variable, lazily handling the addition of + * Adds an 'onexpand' callback for a variable, lazily handling the addition of * new properties. */ _addExpander: function SF__addExpander(aVar, aObject) { // No need for expansion for null and undefined values, but we do need them // for frame.arguments which is a regular array. - if (!aObject || typeof aObject !== "object" || + if (!aVar || !aObject || typeof aObject !== "object" || (aObject.type !== "object" && !Array.isArray(aObject))) { return; } // Force the twisty to show up. aVar.forceShowArrow(); aVar.onexpand = this._addVarProperties.bind(this, aVar, aObject); }, @@ -681,26 +724,27 @@ SourceScripts.prototype = { this.activeThread.removeListener("scriptsadded", this._onScriptsAdded); this.activeThread.removeListener("scriptscleared", this._onScriptsCleared); }, /** * Handler for the debugger client's unsolicited newScript notification. */ _onNewScript: function SS__onNewScript(aNotification, aPacket) { - this._addScript({ url: aPacket.url, startLine: aPacket.startLine }); + this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true); }, /** * Handler for the thread client's scriptsadded notification. */ _onScriptsAdded: function SS__onScriptsAdded() { for each (let script in this.activeThread.cachedScripts) { - this._addScript(script); + this._addScript(script, false); } + DebuggerView.Scripts.commitScripts(); }, /** * Handler for the thread client's scriptscleared notification. */ _onScriptsCleared: function SS__onScriptsCleared() { DebuggerView.Scripts.empty(); }, @@ -743,51 +787,70 @@ SourceScripts.prototype = { let q = aUrl.indexOf('?'); if (q > -1) { return aUrl.slice(0, q); } return aUrl; }, /** + * Gets the prePath for a script URL. + * + * @param string aUrl + * The script url. + * @return string + * The script prePath if the url is valid, null otherwise. + */ + _getScriptPrePath: function SS__getScriptDomain(aUrl) { + try { + return Services.io.newURI(aUrl, null, null).prePath + "/"; + } catch (e) { + } + return null; + }, + + /** * Gets a unique, simplified label from a script url. * ex: a). ici://some.address.com/random/subrandom/ * b). ni://another.address.org/random/subrandom/page.html * c). san://interesting.address.gro/random/script.js * d). si://interesting.address.moc/random/another/script.js * => * a). subrandom/ * b). page.html * c). script.js * d). another/script.js * * @param string aUrl * The script url. * @param string aHref * The content location href to be used. If unspecified, it will - * defalult to debugged panrent window location. + * default to the script url prepath. * @return string * The simplified label. */ _getScriptLabel: function SS__getScriptLabel(aUrl, aHref) { let url = this._trimUrlQuery(aUrl); if (this._labelsCache[url]) { return this._labelsCache[url]; } - let href = aHref || window.parent.content.location.href; + let content = window.parent.content; + let domain = content ? content.location.href : this._getScriptPrePath(aUrl); + + let href = aHref || domain; let pathElements = url.split("/"); let label = pathElements.pop() || (pathElements.pop() + "/"); - // if the label as a leaf name is alreay present in the scripts list + // If the label as a leaf name is already present in the scripts list. if (DebuggerView.Scripts.containsLabel(label)) { label = url.replace(href.substring(0, href.lastIndexOf("/") + 1), ""); - // if the path/to/script is exactly the same, we're in different domains + // If the path/to/script is exactly the same, we're in different domains. if (DebuggerView.Scripts.containsLabel(label)) { label = url; } } return this._labelsCache[url] = label; }, @@ -795,25 +858,26 @@ SourceScripts.prototype = { * Clears the labels cache, populated by SS_getScriptLabel. * This should be done every time the content location changes. */ _clearLabelsCache: function SS__clearLabelsCache() { this._labelsCache = {}; }, /** - * Add the specified script to the list and display it in the editor if the - * editor is empty. + * Add the specified script to the list. + * + * @param object aScript + * The script object coming from the active thread. + * @param boolean aForceFlag + * True to force the script to be immediately added. */ - _addScript: function SS__addScript(aScript) { - DebuggerView.Scripts.addScript(this._getScriptLabel(aScript.url), aScript); - - if (DebuggerView.editor.getCharCount() == 0) { - this.showScript(aScript); - } + _addScript: function SS__addScript(aScript, aForceFlag) { + DebuggerView.Scripts.addScript( + this._getScriptLabel(aScript.url), aScript, aForceFlag); }, /** * Load the editor with the script text if available, otherwise fire an event * to load and display the script text. * * @param object aScript * The script object coming from the active thread. @@ -871,17 +935,17 @@ SourceScripts.prototype = { url: aScript.url }); }, /** * Handles notifications to load a source script from the cache or from a * local file. * - * XXX: Tt may be better to use nsITraceableChannel to get to the sources + * XXX: It may be better to use nsITraceableChannel to get to the sources * without relying on caching when we can (not for eval, etc.): * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/ */ _onLoadSource: function SS__onLoadSource(aEvent) { let url = aEvent.detail.url; let options = aEvent.detail.options; let self = this; @@ -962,17 +1026,17 @@ SourceScripts.prototype = { * Log an error message in the error console when a script fails to load. * * @param string aUrl * The URL of the source script. * @param string aStatus * The failure status code. */ _logError: function SS__logError(aUrl, aStatus) { - Components.utils.reportError(L10N.getFormatStr("loadingError", [aUrl, aStatus])); + Cu.reportError(L10N.getFormatStr("loadingError", [aUrl, aStatus])); }, }; /** * Handles all the breakpoints in the current debugger. */ function Breakpoints() { this._onEditorBreakpointChange = this._onEditorBreakpointChange.bind(this); @@ -1254,16 +1318,37 @@ let L10N = { } }; XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() { return Services.strings.createBundle(DBG_STRINGS_URI); }); /** + * Shortcuts for accessing various debugger preferences. + */ +let Prefs = {}; + +/** + * Gets the preferred default remote debugging host. + * @return string + */ +XPCOMUtils.defineLazyGetter(Prefs, "remoteHost", function() { + return Services.prefs.getCharPref("devtools.debugger.remote-host"); +}); + +/** + * Gets the preferred default remote debugging port. + * @return number + */ +XPCOMUtils.defineLazyGetter(Prefs, "remotePort", function() { + return Services.prefs.getIntPref("devtools.debugger.remote-port"); +}); + +/** * Preliminary setup for the DebuggerController object. */ DebuggerController.init(); DebuggerController.ThreadState = new ThreadState(); DebuggerController.StackFrames = new StackFrames(); DebuggerController.SourceScripts = new SourceScripts(); DebuggerController.Breakpoints = new Breakpoints();
--- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -85,53 +85,72 @@ let DebuggerView = { } }; /** * Functions handling the scripts UI. */ function ScriptsView() { this._onScriptsChange = this._onScriptsChange.bind(this); + this._onScriptsSearch = this._onScriptsSearch.bind(this); } ScriptsView.prototype = { /** * Removes all elements from the scripts container, leaving it empty. */ empty: function DVS_empty() { while (this._scripts.firstChild) { this._scripts.removeChild(this._scripts.firstChild); } }, /** + * Removes the input in the searchbox and unhides all the scripts. + */ + clearSearch: function DVS_clearSearch() { + this._searchbox.value = ""; + this._onScriptsSearch({}); + }, + + /** * Checks whether the script with the specified URL is among the scripts * known to the debugger and shown in the list. * * @param string aUrl * The script URL. * @return boolean */ contains: function DVS_contains(aUrl) { + if (this._tmpScripts.some(function(element) { + return element.script.url == aUrl; + })) { + return true; + } if (this._scripts.getElementsByAttribute("value", aUrl).length > 0) { return true; } return false; }, /** * Checks whether the script with the specified label is among the scripts * known to the debugger and shown in the list. * * @param string aLabel * The script label. * @return boolean */ containsLabel: function DVS_containsLabel(aLabel) { + if (this._tmpScripts.some(function(element) { + return element.label == aLabel; + })) { + return true; + } if (this._scripts.getElementsByAttribute("label", aLabel).length > 0) { return true; } return false; }, /** * Selects the script with the specified URL from the list. @@ -167,80 +186,258 @@ ScriptsView.prototype = { * @return string | null */ get selected() { return this._scripts.selectedItem ? this._scripts.selectedItem.value : null; }, /** + * Returns the list of labels in the scripts container. + * @return array + */ + get scriptLabels() { + let labels = []; + for (let i = 0, l = this._scripts.itemCount; i < l; i++) { + labels.push(this._scripts.getItemAtIndex(i).label); + } + return labels; + }, + + /** * Returns the list of URIs for scripts in the page. * @return array */ get scriptLocations() { let locations = []; for (let i = 0, l = this._scripts.itemCount; i < l; i++) { locations.push(this._scripts.getItemAtIndex(i).value); } return locations; }, /** - * Adds a script to the scripts container. - * If the script already exists (was previously added), null is returned. - * Otherwise, the newly created element is returned. + * Gets the number of visible (hidden=false) scripts in the container. + * @return number + */ + get visibleItemsCount() { + let count = 0; + for (let i = 0, l = this._scripts.itemCount; i < l; i++) { + count += this._scripts.getItemAtIndex(i).hidden ? 0 : 1; + } + return count; + }, + + /** + * Prepares a script to be added to the scripts container. This allows + * for a large number of scripts to be batched up before being + * alphabetically sorted and added in the container. + * @see ScriptsView.commitScripts + * + * If aForceFlag is true, the script will be immediately inserted at the + * necessary position in the container so that all the scripts remain sorted. + * This can be much slower than batching up multiple scripts. + * + * @param string aLabel + * The simplified script location to be shown. + * @param string aScript + * The source script. + * @param boolean aForceFlag + * True to force the script to be immediately added. + */ + addScript: function DVS_addScript(aLabel, aScript, aForceFlag) { + // Batch the script to be added later. + if (!aForceFlag) { + this._tmpScripts.push({ label: aLabel, script: aScript }); + return; + } + + // Find the target position in the menulist and insert the script there. + for (let i = 0, l = this._scripts.itemCount; i < l; i++) { + if (this._scripts.getItemAtIndex(i).label > aLabel) { + this._createScriptElement(aLabel, aScript, i); + return; + } + } + // The script is alphabetically the last one. + this._createScriptElement(aLabel, aScript, -1, true); + }, + + /** + * Adds all the prepared scripts to the scripts container. + * If a script already exists (was previously added), nothing happens. + */ + commitScripts: function DVS_commitScripts() { + let newScripts = this._tmpScripts; + this._tmpScripts = []; + + if (!newScripts || !newScripts.length) { + return; + } + newScripts.sort(function(a, b) { + return a.label.toLowerCase() > b.label.toLowerCase(); + }); + + for (let i = 0, l = newScripts.length; i < l; i++) { + let item = newScripts[i]; + this._createScriptElement(item.label, item.script, -1, true); + } + }, + + /** + * Creates a custom script element and adds it to the scripts container. + * If the script with the specified label already exists, nothing happens. * * @param string aLabel * The simplified script location to be shown. * @param string aScript * The source script. - * @return object - * The newly created html node representing the added script. + * @param number aIndex + * The index where to insert to new script in the container. + * Pass -1 to append the script at the end. + * @param boolean aSelectIfEmptyFlag + * True to set the newly created script as the currently selected item + * if there are no other existing scripts in the container. */ - addScript: function DVS_addScript(aLabel, aScript) { + _createScriptElement: function DVS__createScriptElement( + aLabel, aScript, aIndex, aSelectIfEmptyFlag) + { // Make sure we don't duplicate anything. - if (this.containsLabel(aLabel)) { - return null; + if (aLabel == "null" || this.containsLabel(aLabel)) { + return; } - let script = this._scripts.appendItem(aLabel, aScript.url); - script.setAttribute("tooltiptext", aScript.url); - script.setUserData("sourceScript", aScript, null); + let scriptItem = + aIndex == -1 ? this._scripts.appendItem(aLabel, aScript.url) + : this._scripts.insertItemAt(aIndex, aLabel, aScript.url); - this._scripts.selectedItem = script; - return script; + scriptItem.setAttribute("tooltiptext", aScript.url); + scriptItem.setUserData("sourceScript", aScript, null); + + if (this._scripts.itemCount == 1 && aSelectIfEmptyFlag) { + this._scripts.selectedItem = scriptItem; + } }, /** - * The cached click listener for the scripts container. + * The click listener for the scripts container. */ _onScriptsChange: function DVS__onScriptsChange() { let script = this._scripts.selectedItem.getUserData("sourceScript"); + this._preferredScript = script; DebuggerController.SourceScripts.showScript(script); }, /** - * The cached scripts container. + * The search listener for the scripts search box. + */ + _onScriptsSearch: function DVS__onScriptsSearch(e) { + let editor = DebuggerView.editor; + let scripts = this._scripts; + let rawValue = this._searchbox.value.toLowerCase(); + + let rawLength = rawValue.length; + let lastColon = rawValue.lastIndexOf(":"); + let lastAt = rawValue.lastIndexOf("@"); + + let fileEnd = lastColon != -1 ? lastColon : lastAt != -1 ? lastAt : rawLength; + let lineEnd = lastAt != -1 ? lastAt : rawLength; + + let file = rawValue.slice(0, fileEnd); + let line = window.parseInt(rawValue.slice(fileEnd + 1, lineEnd)) || -1; + let token = rawValue.slice(lineEnd + 1); + + // Presume we won't find anything. + scripts.selectedItem = this._preferredScript; + + // If we're not searching for a file anymore, unhide all the scripts. + if (!file) { + for (let i = 0, l = scripts.itemCount; i < l; i++) { + scripts.getItemAtIndex(i).hidden = false; + } + } else { + for (let i = 0, l = scripts.itemCount, found = false; i < l; i++) { + let item = scripts.getItemAtIndex(i); + let target = item.value.toLowerCase(); + + // Search is not case sensitive, and is tied to the url not the label. + if (target.match(file)) { + item.hidden = false; + + if (!found) { + found = true; + scripts.selectedItem = item; + } + } + // Hide what doesn't match our search. + else { + item.hidden = true; + } + } + } + if (line > -1) { + editor.setCaretPosition(line - 1); + } + if (token) { + let offset = editor.find(token, { ignoreCase: true }); + if (offset > -1) { + editor.setCaretPosition(0); + editor.setCaretOffset(offset); + } + } + }, + + /** + * The keyup listener for the scripts search box. + */ + _onScriptsKeyUp: function DVS__onScriptsKeyUp(e) { + if (e.keyCode === e.DOM_VK_ESCAPE) { + DebuggerView.editor.focus(); + return; + } + + if (e.keyCode === e.DOM_VK_RETURN || e.keyCode === e.DOM_VK_ENTER) { + let editor = DebuggerView.editor; + let offset = editor.findNext(true); + if (offset > -1) { + editor.setCaretPosition(0); + editor.setCaretOffset(offset); + } + } + }, + + /** + * The cached scripts container and search box. */ _scripts: null, + _searchbox: null, /** * Initialization function, called when the debugger is initialized. */ initialize: function DVS_initialize() { this._scripts = document.getElementById("scripts"); + this._searchbox = document.getElementById("scripts-search"); this._scripts.addEventListener("select", this._onScriptsChange, false); + this._searchbox.addEventListener("select", this._onScriptsSearch, false); + this._searchbox.addEventListener("input", this._onScriptsSearch, false); + this._searchbox.addEventListener("keyup", this._onScriptsKeyUp, false); + this.commitScripts(); }, /** * Destruction function, called when the debugger is shut down. */ destroy: function DVS_destroy() { this._scripts.removeEventListener("select", this._onScriptsChange, false); + this._searchbox.removeEventListener("select", this._onScriptsSearch, false); + this._searchbox.removeEventListener("input", this._onScriptsSearch, false); + this._searchbox.removeEventListener("keyup", this._onScriptsKeyUp, false); this._scripts = null; + this._searchbox = null; } }; /** * Functions handling the html stackframes UI. */ function StackFramesView() { this._onFramesScroll = this._onFramesScroll.bind(this); @@ -272,16 +469,18 @@ StackFramesView.prototype = { else if (aState == "attached") { status.textContent = L10N.getStr("runningState"); resume.label = L10N.getStr("pauseLabel"); } // No valid state parameter. else { status.textContent = ""; } + + DebuggerView.Scripts.clearSearch(); }, /** * Removes all elements from the stackframes container, leaving it empty. */ empty: function DVF_empty() { while (this._frames.firstChild) { this._frames.removeChild(this._frames.firstChild); @@ -295,17 +494,17 @@ StackFramesView.prototype = { emptyText: function DVF_emptyText() { // Make sure the container is empty first. this.empty(); let item = document.createElement("div"); // The empty node should look grayed out to avoid confusion. item.className = "empty list-item"; - item.appendChild(document.createTextNode(L10N.getStr("emptyText"))); + item.appendChild(document.createTextNode(L10N.getStr("emptyStackText"))); this._frames.appendChild(item); }, /** * Adds a frame to the stackframes container. * If the frame already exists (was previously added), null is returned. * Otherwise, the newly created element is returned. @@ -376,17 +575,17 @@ StackFramesView.prototype = { /** * Deselects a frame from the stackframe container. * * @param number aDepth * The frame depth specified by the debugger. */ unhighlightFrame: function DVF_unhighlightFrame(aDepth) { - this.highlightFrame(aDepth, true) + this.highlightFrame(aDepth, true); }, /** * Gets the current dirty state. * * @return boolean value * True if should load more frames. */
--- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -76,16 +76,18 @@ <div id="body" class="vbox flex"> <xul:toolbar id="dbg-toolbar"> <xul:button id="close">&debuggerUI.closeButton;</xul:button> <xul:button id="resume"/> <xul:button id="step-over">&debuggerUI.stepOverButton;</xul:button> <xul:button id="step-in">&debuggerUI.stepInButton;</xul:button> <xul:button id="step-out">&debuggerUI.stepOutButton;</xul:button> <xul:menulist id="scripts"/> + <xul:textbox id="scripts-search" type="search" + emptytext="&debuggerUI.emptyFilterText;"/> </xul:toolbar> <div id="dbg-content" class="hbox flex"> <div id="stack" class="vbox"> <div class="title unselectable">&debuggerUI.stackTitle;</div> <div id="stackframes" class="vbox flex"></div> </div> <div id="script" class="vbox flex"> <div class="title unselectable">&debuggerUI.scriptTitle;</div>
--- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -41,16 +41,17 @@ topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ relativesrcdir = browser/devtools/debugger/test include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk _BROWSER_TEST_FILES = \ + browser_dbg_createRemote.js \ browser_dbg_debuggerstatement.js \ browser_dbg_listtabs.js \ browser_dbg_tabactor-01.js \ browser_dbg_tabactor-02.js \ browser_dbg_contextactor-01.js \ browser_dbg_contextactor-02.js \ testactors.js \ browser_dbg_nav-01.js \ @@ -65,16 +66,19 @@ include $(topsrcdir)/config/rules.mk browser_dbg_panesize.js \ browser_dbg_stack-01.js \ browser_dbg_stack-02.js \ browser_dbg_stack-03.js \ browser_dbg_stack-04.js \ browser_dbg_stack-05.js \ browser_dbg_location-changes.js \ browser_dbg_script-switching.js \ + browser_dbg_scripts-sorting.js \ + browser_dbg_scripts-searching-01.js \ + browser_dbg_scripts-searching-02.js \ browser_dbg_pause-resume.js \ browser_dbg_update-editor-mode.js \ $(warning browser_dbg_select-line.js temporarily disabled due to oranges, see bug 726609) \ browser_dbg_clean-exit.js \ browser_dbg_bug723069_editor-breakpoints.js \ browser_dbg_bug731394_editor-contextmenu.js \ browser_dbg_displayName.js \ browser_dbg_iframes.js \
--- a/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js +++ b/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js @@ -18,47 +18,54 @@ let gBreakpoints = null; function test() { let tempScope = {}; Cu.import("resource:///modules/source-editor.jsm", tempScope); let SourceEditor = tempScope.SourceEditor; let scriptShown = false; let framesAdded = false; + let resumed = false; + let testStarted = false; debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; + resumed = true; + gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { framesAdded = true; - runTest(); + executeSoon(startTest); }); - gDebuggee.firstCall(); + executeSoon(function() { + gDebuggee.firstCall(); + }); }); - window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { - let url = aEvent.detail.url; - if (url.indexOf("-02.js") != -1) { - scriptShown = true; - window.removeEventListener(aEvent.type, _onEvent); - runTest(); - } - }); + function onScriptShown(aEvent) + { + scriptShown = aEvent.detail.url.indexOf("-02.js") != -1; + executeSoon(startTest); + } - function runTest() + window.addEventListener("Debugger:ScriptShown", onScriptShown); + + function startTest() { - if (scriptShown && framesAdded) { - Services.tm.currentThread.dispatch({ run: onScriptShown }, 0); + if (scriptShown && framesAdded && resumed && !testStarted) { + window.removeEventListener("Debugger:ScriptShown", onScriptShown); + testStarted = true; + Services.tm.currentThread.dispatch({ run: performTest }, 0); } } - function onScriptShown() + function performTest() { gScripts = gDebugger.DebuggerView.Scripts; is(gDebugger.DebuggerController.activeThread.state, "paused", "Should only be getting stack frames while paused."); is(gScripts._scripts.itemCount, 2, "Found the expected number of scripts.");
--- a/browser/devtools/debugger/test/browser_dbg_bug731394_editor-contextmenu.js +++ b/browser/devtools/debugger/test/browser_dbg_bug731394_editor-contextmenu.js @@ -16,47 +16,53 @@ function test() { let tempScope = {}; Cu.import("resource:///modules/source-editor.jsm", tempScope); let SourceEditor = tempScope.SourceEditor; let contextMenu = null; let scriptShown = false; let framesAdded = false; + let resumed = false; + let testStarted = false; debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; + resumed = true; gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { framesAdded = true; - runTest(); + executeSoon(startTest); }); - gDebuggee.firstCall(); + + executeSoon(function() { + gDebuggee.firstCall(); + }); }); - window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { - let url = aEvent.detail.url; - if (url.indexOf("-02.js") != -1) { - scriptShown = true; - window.removeEventListener(aEvent.type, _onEvent); - runTest(); - } - }); + function onScriptShown(aEvent) { + scriptShown = aEvent.detail.url.indexOf("-02.js") != -1; + executeSoon(startTest); + } + + window.addEventListener("Debugger:ScriptShown", onScriptShown); - function runTest() + function startTest() { - if (scriptShown && framesAdded) { - Services.tm.currentThread.dispatch({ run: onScriptShown }, 0); + if (scriptShown && framesAdded && resumed && !testStarted) { + testStarted = true; + window.removeEventListener("Debugger:ScriptShown", onScriptShown); + Services.tm.currentThread.dispatch({ run: performTest }, 0); } } - function onScriptShown() + function performTest() { let scripts = gDebugger.DebuggerView.Scripts._scripts; is(gDebugger.DebuggerController.activeThread.state, "paused", "Should only be getting stack frames while paused."); is(scripts.itemCount, 2, "Found the expected number of scripts.");
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_createRemote.js @@ -0,0 +1,86 @@ +/* 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 gProcess = null; +var gTab = null; +var gDebuggee = null; + +function test() { + remote_debug_tab_pane(STACK_URL, aOnClosing, function(aTab, aDebuggee, aProcess) { + gTab = aTab; + gDebuggee = aDebuggee; + gProcess = aProcess; + + testSimpleCall(); + }); +} + +function testSimpleCall() { + Services.tm.currentThread.dispatch({ run: function() { + + ok(gProcess._dbgProcess, + "The remote debugger process wasn't created properly!"); + ok(gProcess._dbgProcess.isRunning, + "The remote debugger process isn't running!"); + is(typeof gProcess._dbgProcess.pid, "number", + "The remote debugger process doesn't have a pid (?!)"); + + info("process location: " + gProcess._dbgProcess.location); + info("process pid: " + gProcess._dbgProcess.pid); + info("process name: " + gProcess._dbgProcess.processName); + info("process sig: " + gProcess._dbgProcess.processSignature); + + ok(gProcess._dbgProfile, + "The remote debugger profile wasn't created properly!"); + ok(gProcess._dbgProfile.localDir, + "The remote debugger profile doesn't have a localDir..."); + ok(gProcess._dbgProfile.rootDir, + "The remote debugger profile doesn't have a rootDir..."); + ok(gProcess._dbgProfile.name, + "The remote debugger profile doesn't have a name..."); + + info("profile localDir: " + gProcess._dbgProfile.localDir); + info("profile rootDir: " + gProcess._dbgProfile.rootDir); + info("profile name: " + gProcess._dbgProfile.name); + + let profileService = Cc["@mozilla.org/toolkit/profile-service;1"] + .createInstance(Ci.nsIToolkitProfileService); + + let profile = profileService.getProfileByName(gProcess._dbgProfile.name); + + ok(profile, + "The remote debugger profile wasn't *actually* created properly!"); + is(profile.localDir.path, gProcess._dbgProfile.localDir.path, + "The remote debugger profile doesn't have the correct localDir!"); + is(profile.rootDir.path, gProcess._dbgProfile.rootDir.path, + "The remote debugger profile doesn't have the correct rootDir!"); + + DebuggerUI.toggleRemoteDebugger(); + }}, 0); +} + +function aOnClosing() { + ok(!gProcess._dbgProcess.isRunning, + "The remote debugger process isn't closed as it should be!"); + is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256), + "The remote debugger process didn't die cleanly."); + + info("process exit value: " + gProcess._dbgProcess.exitValue); + + info("profile localDir: " + gProcess._dbgProfile.localDir.path); + info("profile rootDir: " + gProcess._dbgProfile.rootDir.path); + info("profile name: " + gProcess._dbgProfile.name); + + executeSoon(function() { + finish(); + }); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gProcess = null; + gTab = null; + gDebuggee = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js @@ -80,38 +80,38 @@ function testSimpleCall() { gDebuggee.simpleCall(); } function resumeAndFinish() { gDebugger.DebuggerController.activeThread.resume(function() { let vs = gDebugger.DebuggerView.Scripts; let ss = gDebugger.DebuggerController.SourceScripts; - ss._onScriptsCleared(); + vs.empty(); + vs._scripts.removeEventListener("select", vs._onScriptsChange, false); is(ss._trimUrlQuery("a/b/c.d?test=1&random=4"), "a/b/c.d", "Trimming the url query isn't done properly."); let urls = [ { href: "ici://some.address.com/random/", leaf: "subrandom/" }, { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" }, { href: "san://interesting.address.gro/random/", leaf: "script.js" }, { href: "si://interesting.address.moc/random/", leaf: "script.js" }, { href: "si://interesting.address.moc/random/", leaf: "x/script.js" }, { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" }, { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" }, { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" } ]; - vs._scripts.removeEventListener("select", vs._onScriptsChange, false); - urls.forEach(function(url) { executeSoon(function() { let loc = url.href + url.leaf; vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }); + vs.commitScripts(); }); }); executeSoon(function() { for (let i = 0; i < vs._scripts.itemCount; i++) { let lab = vs._scripts.getItemAtIndex(i).getAttribute("label"); let loc = urls[i].href + urls[i].leaf;
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js @@ -22,20 +22,20 @@ function test() testFrameParameters(); }); } function testFrameParameters() { dump("Started testFrameParameters!\n"); - gDebugger.addEventListener("Debugger:FetchedParameters", function test() { - dump("Entered Debugger:FetchedParameters!\n"); + gDebugger.addEventListener("Debugger:FetchedVariables", function test() { + dump("Entered Debugger:FetchedVariables!\n"); - gDebugger.removeEventListener("Debugger:FetchedParameters", test, false); + gDebugger.removeEventListener("Debugger:FetchedVariables", test, false); Services.tm.currentThread.dispatch({ run: function() { dump("After currentThread.dispatch!\n"); var frames = gDebugger.DebuggerView.StackFrames._frames, childNodes = frames.childNodes, localScope = gDebugger.DebuggerView.Properties.localScope, localNodes = localScope.querySelector(".details").childNodes; @@ -47,43 +47,52 @@ function testFrameParameters() dump("localNodes - " + localNodes.constructor + "\n"); is(gDebugger.DebuggerController.activeThread.state, "paused", "Should only be getting stack frames while paused."); is(frames.querySelectorAll(".dbg-stackframe").length, 3, "Should have three frames."); - is(localNodes.length, 8, + is(localNodes.length, 11, "The localScope should contain all the created variable elements."); is(localNodes[0].querySelector(".info").textContent, "[object Proxy]", "Should have the right property value for 'this'."); - is(localNodes[1].querySelector(".info").textContent, "[object Arguments]", - "Should have the right property value for 'arguments'."); - - is(localNodes[2].querySelector(".info").textContent, "[object Object]", + is(localNodes[1].querySelector(".info").textContent, "[object Object]", "Should have the right property value for 'aArg'."); - is(localNodes[3].querySelector(".info").textContent, '"beta"', + is(localNodes[2].querySelector(".info").textContent, '"beta"', "Should have the right property value for 'bArg'."); - is(localNodes[4].querySelector(".info").textContent, "3", + is(localNodes[3].querySelector(".info").textContent, "3", "Should have the right property value for 'cArg'."); - is(localNodes[5].querySelector(".info").textContent, "false", + is(localNodes[4].querySelector(".info").textContent, "false", "Should have the right property value for 'dArg'."); - is(localNodes[6].querySelector(".info").textContent, "null", + is(localNodes[5].querySelector(".info").textContent, "null", "Should have the right property value for 'eArg'."); - is(localNodes[7].querySelector(".info").textContent, "undefined", + is(localNodes[6].querySelector(".info").textContent, "undefined", "Should have the right property value for 'fArg'."); + is(localNodes[7].querySelector(".info").textContent, "1", + "Should have the right property value for 'a'."); + + is(localNodes[8].querySelector(".info").textContent, "[object Object]", + "Should have the right property value for 'b'."); + + is(localNodes[9].querySelector(".info").textContent, "[object Object]", + "Should have the right property value for 'c'."); + + is(localNodes[10].querySelector(".info").textContent, "[object Arguments]", + "Should have the right property value for 'arguments'."); + resumeAndFinish(); }}, 0); }, false); EventUtils.sendMouseEvent({ type: "click" }, content.document.querySelector("button"), content.window); }
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js @@ -22,20 +22,20 @@ function test() testFrameParameters(); }); } function testFrameParameters() { dump("Started testFrameParameters!\n"); - gDebugger.addEventListener("Debugger:FetchedParameters", function test() { - dump("Entered Debugger:FetchedParameters!\n"); + gDebugger.addEventListener("Debugger:FetchedVariables", function test() { + dump("Entered Debugger:FetchedVariables!\n"); - gDebugger.removeEventListener("Debugger:FetchedParameters", test, false); + gDebugger.removeEventListener("Debugger:FetchedVariables", test, false); Services.tm.currentThread.dispatch({ run: function() { dump("After currentThread.dispatch!\n"); var frames = gDebugger.DebuggerView.StackFrames._frames, localScope = gDebugger.DebuggerView.Properties.localScope, localNodes = localScope.querySelector(".details").childNodes; @@ -45,82 +45,98 @@ function testFrameParameters() dump("localNodes - " + localNodes.constructor + "\n"); is(gDebugger.DebuggerController.activeThread.state, "paused", "Should only be getting stack frames while paused."); is(frames.querySelectorAll(".dbg-stackframe").length, 3, "Should have three frames."); - is(localNodes.length, 8, + is(localNodes.length, 11, "The localScope should contain all the created variable elements."); is(localNodes[0].querySelector(".info").textContent, "[object Proxy]", "Should have the right property value for 'this'."); - // Expand the __proto__ and arguments tree nodes. This causes their - // properties to be retrieved and displayed. + // Expand the '__proto__', 'arguments' and 'a' tree nodes. This causes + // their properties to be retrieved and displayed. localNodes[0].expand(); - localNodes[1].expand(); + localNodes[9].expand(); + localNodes[10].expand(); // Poll every few milliseconds until the properties are retrieved. // It's important to set the timer in the chrome window, because the // content window timers are disabled while the debuggee is paused. let count = 0; let intervalID = window.setInterval(function(){ if (++count > 50) { ok(false, "Timed out while polling for the properties."); resumeAndFinish(); } - if (!localNodes[0].fetched || !localNodes[1].fetched) { + if (!localNodes[0].fetched || + !localNodes[9].fetched || + !localNodes[10].fetched) { return; } window.clearInterval(intervalID); is(localNodes[0].querySelector(".property > .title > .key") .textContent, "__proto__ ", "Should have the right property name for __proto__."); ok(localNodes[0].querySelector(".property > .title > .value") .textContent.search(/object/) != -1, "__proto__ should be an object."); - is(localNodes[1].querySelector(".info").textContent, "[object Arguments]", + is(localNodes[9].querySelector(".info").textContent, "[object Object]", + "Should have the right property value for 'c'."); + + is(localNodes[9].querySelectorAll(".property > .title > .key")[1] + .textContent, "a", + "Should have the right property name for 'a'."); + + is(localNodes[9].querySelectorAll(".property > .title > .value")[1] + .textContent, 1, + "Should have the right value for 'c.a'."); + + is(localNodes[10].querySelector(".info").textContent, + "[object Arguments]", "Should have the right property value for 'arguments'."); - is(localNodes[1].querySelector(".property > .title > .key") + is(localNodes[10].querySelector(".property > .title > .key") .textContent, "length", - "Should have the right property name for length."); + "Should have the right property name for 'length'."); - is(localNodes[1].querySelector(".property > .title > .value") + is(localNodes[10].querySelector(".property > .title > .value") .textContent, 5, "Should have the right argument length."); resumeAndFinish(); }, 100); }}, 0); }, false); EventUtils.sendMouseEvent({ type: "click" }, content.document.querySelector("button"), content.window); } function resumeAndFinish() { - gDebugger.DebuggerController.activeThread.addOneTimeListener("framescleared", function() { + let thread = gDebugger.DebuggerController.activeThread; + thread.addOneTimeListener("framescleared", function() { Services.tm.currentThread.dispatch({ run: function() { var frames = gDebugger.DebuggerView.StackFrames._frames; is(frames.querySelectorAll(".dbg-stackframe").length, 0, "Should have no frames."); closeDebuggerAndFinish(gTab); }}, 0); }); - gDebugger.DebuggerController.activeThread.resume(); + thread.resume(); } registerCleanupFunction(function() { removeTab(gTab); gPane = null; gTab = null; gDebugger = null; });
--- a/browser/devtools/debugger/test/browser_dbg_script-switching.js +++ b/browser/devtools/debugger/test/browser_dbg_script-switching.js @@ -3,57 +3,59 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Make sure that switching the displayed script in the UI works as advertised. */ const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html"; -let tempScope = {}; -Cu.import("resource:///modules/source-editor.jsm", tempScope); -let SourceEditor = tempScope.SourceEditor; - var gPane = null; var gTab = null; var gDebuggee = null; var gDebugger = null; var gScripts = null; function test() { let scriptShown = false; let framesAdded = false; + let resumed = false; + let testStarted = false; debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; + resumed = true; gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { framesAdded = true; - runTest(); + executeSoon(startTest); }); - gDebuggee.firstCall(); + executeSoon(function() { + gDebuggee.firstCall(); + }); }); - window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { - let url = aEvent.detail.url; - if (url.indexOf("-02.js") != -1) { - scriptShown = true; - window.removeEventListener(aEvent.type, _onEvent); - runTest(); - } - }); + function onScriptShown(aEvent) + { + scriptShown = aEvent.detail.url.indexOf("-02.js") != -1; + executeSoon(startTest); + } - function runTest() + window.addEventListener("Debugger:ScriptShown", onScriptShown); + + function startTest() { - if (scriptShown && framesAdded) { + if (scriptShown && framesAdded && resumed && !testStarted) { + window.removeEventListener("Debugger:ScriptShown", onScriptShown); + testStarted = true; Services.tm.currentThread.dispatch({ run: testScriptsDisplay }, 0); } } } function testScriptsDisplay() { gScripts = gDebugger.DebuggerView.Scripts._scripts; @@ -74,18 +76,16 @@ function testScriptsDisplay() { ok(gDebugger.DebuggerView.Scripts.contains(EXAMPLE_URL + label2), "Second script url is incorrect."); ok(gDebugger.DebuggerView.Scripts.containsLabel( label1), "First script label is incorrect."); ok(gDebugger.DebuggerView.Scripts.containsLabel( label2), "Second script label is incorrect."); - dump("Debugger editor text:\n" + gDebugger.editor.getText() + "\n"); - ok(gDebugger.editor.getText().search(/debugger/) != -1, "The correct script was loaded initially."); is(gDebugger.editor.getDebugLocation(), 5, "editor debugger location is correct."); window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { let url = aEvent.detail.url; @@ -95,18 +95,16 @@ function testScriptsDisplay() { } }); gDebugger.DebuggerView.Scripts.selectScript(EXAMPLE_URL + label1); } function testSwitchPaused() { - dump("Debugger editor text:\n" + gDebugger.editor.getText() + "\n"); - ok(gDebugger.editor.getText().search(/debugger/) == -1, "The second script is no longer displayed."); ok(gDebugger.editor.getText().search(/firstCall/) != -1, "The first script is displayed."); is(gDebugger.editor.getDebugLocation(), -1, "editor debugger location has been cleared."); @@ -122,16 +120,18 @@ function testSwitchPaused() gDebugger.DebuggerView.Scripts.selectScript(EXAMPLE_URL + "test-script-switching-02.js"); }); } function testSwitchRunning() { + dump("Debugger editor text:\n" + gDebugger.editor.getText() + "\n"); + ok(gDebugger.editor.getText().search(/debugger/) != -1, "The second script is displayed again."); ok(gDebugger.editor.getText().search(/firstCall/) == -1, "The first script is no longer displayed."); is(gDebugger.editor.getDebugLocation(), -1, "editor debugger location is still -1.");
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js @@ -0,0 +1,188 @@ +/* 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; +var gEditor = null; +var gScripts = null; +var gSearchBox = null; +var gMenulist = null; + +function test() +{ + let scriptShown = false; + let framesAdded = false; + + debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.debuggerWindow; + + gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { + framesAdded = true; + runTest(); + }); + + gDebuggee.simpleCall(); + }); + + window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { + window.removeEventListener(aEvent.type, _onEvent); + scriptShown = true; + runTest(); + }); + + function runTest() + { + if (scriptShown && framesAdded) { + Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0); + } + } +} + +function testScriptSearching() { + gDebugger.DebuggerController.activeThread.resume(function() { + gEditor = gDebugger.DebuggerView.editor; + gScripts = gDebugger.DebuggerView.Scripts; + gSearchBox = gScripts._searchbox; + gMenulist = gScripts._scripts; + + write(":12"); + ok(gEditor.getCaretPosition().line == 11 && + gEditor.getCaretPosition().col == 0, + "The editor didn't jump to the correct line."); + + write("@debugger"); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44, + "The editor didn't jump to the correct token. (1)"); + + EventUtils.sendKey("RETURN"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't jump to the correct token. (2)"); + + EventUtils.sendKey("ENTER"); + ok(gEditor.getCaretPosition().line == 12 && + gEditor.getCaretPosition().col == 8, + "The editor didn't jump to the correct token. (3)"); + + EventUtils.sendKey("ENTER"); + ok(gEditor.getCaretPosition().line == 19 && + gEditor.getCaretPosition().col == 4, + "The editor didn't jump to the correct token. (4)"); + + EventUtils.sendKey("RETURN"); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44, + "The editor didn't jump to the correct token. (5)"); + + + write(":bogus@debugger;"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't jump to the correct token. (7)"); + + write(":13@debugger;"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't jump to the correct token. (7)"); + + write(":@debugger;"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't jump to the correct token. (8)"); + + write("::@debugger;"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't jump to the correct token. (9)"); + + write(":::@debugger;"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't jump to the correct token. (10)"); + + + write(":i am not a number"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't remain at the correct token. (11)"); + + write("@__i do not exist__"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't remain at the correct token. (12)"); + + + write(":1:2:3:a:b:c:::12"); + ok(gEditor.getCaretPosition().line == 11 && + gEditor.getCaretPosition().col == 0, + "The editor didn't jump to the correct line. (13)"); + + write("@don't@find@me@instead@find@debugger"); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44, + "The editor didn't jump to the correct token. (14)"); + + EventUtils.sendKey("RETURN"); + ok(gEditor.getCaretPosition().line == 8 && + gEditor.getCaretPosition().col == 2, + "The editor didn't jump to the correct token. (15)"); + + EventUtils.sendKey("ENTER"); + ok(gEditor.getCaretPosition().line == 12 && + gEditor.getCaretPosition().col == 8, + "The editor didn't jump to the correct token. (16)"); + + EventUtils.sendKey("RETURN"); + ok(gEditor.getCaretPosition().line == 19 && + gEditor.getCaretPosition().col == 4, + "The editor didn't jump to the correct token. (17)"); + + EventUtils.sendKey("ENTER"); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44, + "The editor didn't jump to the correct token. (18)"); + + + clear(); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44, + "The editor didn't remain at the correct token. (19)"); + is(gScripts.visibleItemsCount, 1, + "Not all the scripts are shown after the search. (20)"); + + closeDebuggerAndFinish(gTab); + }); +} + +function clear() { + gSearchBox.focus(); + gSearchBox.value = ""; +} + +function write(text) { + clear(); + + for (let i = 0; i < text.length; i++) { + EventUtils.sendChar(text[i]); + } + dump("editor caret position: " + gEditor.getCaretPosition().toSource() + "\n"); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; + gEditor = null; + gScripts = null; + gSearchBox = null; + gMenulist = null; +});
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js @@ -0,0 +1,146 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html"; + +var gPane = null; +var gTab = null; +var gDebuggee = null; +var gDebugger = null; +var gEditor = null; +var gScripts = null; +var gSearchBox = null; +var gMenulist = null; + +function test() +{ + let scriptShown = false; + let framesAdded = false; + + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.debuggerWindow; + + gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { + framesAdded = true; + runTest(); + }); + + gDebuggee.firstCall(); + }); + + window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { + let url = aEvent.detail.url; + if (url.indexOf("-02.js") != -1) { + scriptShown = true; + window.removeEventListener(aEvent.type, _onEvent); + runTest(); + } + }); + + function runTest() + { + if (scriptShown && framesAdded) { + Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0); + } + } +} + +function testScriptSearching() { + gDebugger.DebuggerController.activeThread.resume(function() { + gEditor = gDebugger.DebuggerView.editor; + gScripts = gDebugger.DebuggerView.Scripts; + gSearchBox = gScripts._searchbox; + gMenulist = gScripts._scripts; + + firstSearch(); + }); +} + +function firstSearch() { + window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { + dump("Current script url:\n" + aEvent.detail.url + "\n"); + dump("Debugger editor text:\n" + gEditor.getText() + "\n"); + + let url = aEvent.detail.url; + if (url.indexOf("-01.js") != -1) { + window.removeEventListener(aEvent.type, _onEvent); + + executeSoon(function() { + dump("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n"); + ok(gEditor.getCaretPosition().line == 4 && + gEditor.getCaretPosition().col == 0, + "The editor didn't jump to the correct line. (1)"); + is(gScripts.visibleItemsCount, 1, + "Not all the correct scripts are shown after the search. (1)"); + + secondSearch(); + }); + } + }); + write(".*-01\.js:5"); +} + +function secondSearch() { + window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { + dump("Current script url:\n" + aEvent.detail.url + "\n"); + dump("Debugger editor text:\n" + gEditor.getText() + "\n"); + + let url = aEvent.detail.url; + if (url.indexOf("-02.js") != -1) { + window.removeEventListener(aEvent.type, _onEvent); + + executeSoon(function() { + dump("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n"); + ok(gEditor.getCaretPosition().line == 5 && + gEditor.getCaretPosition().col == 8, + "The editor didn't jump to the correct line. (2)"); + is(gScripts.visibleItemsCount, 1, + "Not all the correct scripts are shown after the search. (2)"); + + finalCheck(); + }); + } + }); + write(".*-02\.js@debugger;"); +} + +function finalCheck() { + clear(); + ok(gEditor.getCaretPosition().line == 5 && + gEditor.getCaretPosition().col == 8, + "The editor didn't remain at the correct token. (3)"); + is(gScripts.visibleItemsCount, 2, + "Not all the scripts are shown after the search. (3)"); + + closeDebuggerAndFinish(gTab); +} + +function clear() { + gSearchBox.focus(); + gSearchBox.value = ""; +} + +function write(text) { + clear(); + + for (let i = 0; i < text.length; i++) { + EventUtils.sendChar(text[i]); + } + dump("editor caret position: " + gEditor.getCaretPosition().toSource() + "\n"); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; + gEditor = null; + gScripts = null; + gSearchBox = null; + gMenulist = null; +});
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_scripts-sorting.js @@ -0,0 +1,124 @@ +/* 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; + +function test() { + debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.debuggerWindow; + + testSimpleCall(); + }); +} + +function testSimpleCall() { + gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { + Services.tm.currentThread.dispatch({ run: function() { + resumeAndFinish(); + }}, 0); + }); + + gDebuggee.simpleCall(); +} + +function resumeAndFinish() { + gDebugger.DebuggerController.activeThread.resume(function() { + checkScriptsOrder(); + addScriptsAndCheckOrder(1, function() { + addScriptsAndCheckOrder(2, function() { + addScriptsAndCheckOrder(3, function() { + closeDebuggerAndFinish(gTab); + }); + }); + }); + }); +} + +function addScriptsAndCheckOrder(method, callback) { + let vs = gDebugger.DebuggerView.Scripts; + let ss = gDebugger.DebuggerController.SourceScripts; + vs.empty(); + vs._scripts.removeEventListener("select", vs._onScriptsChange, false); + + let urls = [ + { href: "ici://some.address.com/random/", leaf: "subrandom/" }, + { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" }, + { href: "san://interesting.address.gro/random/", leaf: "script.js" }, + { href: "si://interesting.address.moc/random/", leaf: "script.js" }, + { href: "si://interesting.address.moc/random/", leaf: "x/script.js" }, + { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" }, + { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" }, + { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" } + ]; + + urls.sort(function(a, b) { + return Math.random() - 0.5; + }); + + switch (method) { + case 1: + urls.forEach(function(url) { + let loc = url.href + url.leaf; + vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }); + }); + vs.commitScripts(); + break; + + case 2: + urls.forEach(function(url) { + let loc = url.href + url.leaf; + vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true); + }); + break; + + case 3: + let i = 0 + for (; i < urls.length / 2; i++) { + let url = urls[i]; + let loc = url.href + url.leaf; + vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }); + } + vs.commitScripts(); + + for (; i < urls.length; i++) { + let url = urls[i]; + let loc = url.href + url.leaf; + vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true); + } + break; + } + + executeSoon(function() { + checkScriptsOrder(method); + callback(); + }); +} + +function checkScriptsOrder(method) { + let labels = gDebugger.DebuggerView.Scripts.scriptLabels; + let sorted = labels.reduce(function(prev, curr, index, array) { + return array[index - 1] < array[index]; + }); + + ok(sorted, + "Using method " + method + ", " + + "the scripts weren't in the correct order: " + labels.toSource()); + + return sorted; +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js +++ b/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js @@ -17,42 +17,48 @@ var gTab = null; var gDebuggee = null; var gDebugger = null; var gScripts = null; function test() { let scriptShown = false; let framesAdded = false; + let testStarted = false; + let resumed = false; debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; + resumed = true; gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { framesAdded = true; - runTest(); + executeSoon(startTest); }); - gDebuggee.firstCall(); + + executeSoon(function() { + gDebuggee.firstCall(); + }); }); - window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) { - let url = aEvent.detail.url; - if (url.indexOf("editor-mode") != -1) { - scriptShown = true; - window.removeEventListener(aEvent.type, _onEvent); - runTest(); - } - }); + function onScriptShown(aEvent) { + scriptShown = aEvent.detail.url.indexOf("test-editor-mode") != -1; + executeSoon(startTest); + } - function runTest() + window.addEventListener("Debugger:ScriptShown", onScriptShown); + + function startTest() { - if (scriptShown && framesAdded) { + if (scriptShown && framesAdded && resumed && !testStarted) { + window.removeEventListener("Debugger:ScriptShown", onScriptShown); + testStarted = true; Services.tm.currentThread.dispatch({ run: testScriptsDisplay }, 0); } } } function testScriptsDisplay() { gScripts = gDebugger.DebuggerView.Scripts._scripts;
--- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -87,22 +87,35 @@ function attach_thread_actor_for_url(aCl }); }); } function debug_tab_pane(aURL, aOnDebugging) { let tab = addTab(aURL, function() { gBrowser.selectedTab = gTab; - let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject; let pane = DebuggerUI.toggleDebugger(); pane._frame.addEventListener("Debugger:Connecting", function dbgConnected() { pane._frame.removeEventListener("Debugger:Connecting", dbgConnected, true); // Wait for the initial resume... pane.debuggerWindow.gClient.addOneTimeListener("resumed", function() { aOnDebugging(tab, debuggee, pane); }); }, true); }); } + +function remote_debug_tab_pane(aURL, aOnClosing, aOnDebugging) +{ + let tab = addTab(aURL, function() { + gBrowser.selectedTab = gTab; + let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject; + + DebuggerUI.toggleRemoteDebugger(aOnClosing, function dbgRan(process) { + + // Wait for the remote debugging process to start... + aOnDebugging(tab, debuggee, process); + }); + }); +}
--- a/browser/devtools/highlighter/inspector.jsm +++ b/browser/devtools/highlighter/inspector.jsm @@ -393,17 +393,17 @@ InspectorUI.prototype = { /** * Is the inspector UI open? Simply check if the toolbar is visible or not. * * @returns boolean */ get isInspectorOpen() { - return this.toolbar && !this.toolbar.hidden && this.highlighter; + return !!(this.toolbar && !this.toolbar.hidden && this.highlighter); }, /** * Return the default selection element for the inspected document. */ get defaultSelection() { let doc = this.win.document; @@ -642,17 +642,17 @@ InspectorUI.prototype = { this.breadcrumbs.destroy(); this.breadcrumbs = null; } delete this._currentInspector; if (!aKeepInspector) this.store.deleteInspector(this.winID); - this.inspectMenuitem.setAttribute("checked", false); + this.inspectMenuitem.removeAttribute("checked"); this.browser = this.win = null; // null out references to browser and window this.winID = null; this.selection = null; this.closing = false; this.isDirty = false; delete this.treePanel; delete this.stylePanel;
--- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -12,8 +12,10 @@ browser.jar: * content/browser/devtools/layoutview/view.xhtml (layoutview/view.xhtml) content/browser/devtools/layoutview/view.css (layoutview/view.css) content/browser/orion.js (sourceeditor/orion/orion.js) * content/browser/source-editor-overlay.xul (sourceeditor/source-editor-overlay.xul) * content/browser/debugger.xul (debugger/debugger.xul) content/browser/debugger.css (debugger/debugger.css) content/browser/debugger-controller.js (debugger/debugger-controller.js) content/browser/debugger-view.js (debugger/debugger-view.js) + content/browser/devtools/gcli.css (webconsole/gcli.css) + content/browser/devtools/gcliblank.xhtml (webconsole/gcliblank.xhtml)
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/DeveloperToolbar.jsm @@ -0,0 +1,476 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Firefox Developer Toolbar. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dave Camp <dcamp@mozilla.com> (Original Author) + * Joe Walker <jwalker@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +"use strict"; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +let EXPORTED_SYMBOLS = [ "DeveloperToolbar", "loadCommands" ]; + +XPCOMUtils.defineLazyGetter(this, "gcli", function () { + let obj = {}; + Components.utils.import("resource:///modules/gcli.jsm", obj); + return obj.gcli; +}); + +let console = gcli._internal.console; + +/** + * Load the various Command JSMs. + * Should be called when the developer toolbar first opens. + */ +function loadCommands() +{ + Components.utils.import("resource:///modules/GcliCommands.jsm", {}); + Components.utils.import("resource:///modules/GcliTiltCommands.jsm", {}); +} + + + +let commandsLoaded = false; + +/** + * A component to manage the global developer toolbar, which contains a GCLI + * and buttons for various developer tools. + * @param aChromeWindow The browser window to which this toolbar is attached + * @param aToolbarElement See browser.xul:<toolbar id="developer-toolbar"> + */ +function DeveloperToolbar(aChromeWindow, aToolbarElement) +{ + if (!commandsLoaded) { + loadCommands(); + commandsLoaded = true; + } + + this._chromeWindow = aChromeWindow; + + this._element = aToolbarElement; + this._element.hidden = true; + this._doc = this._element.ownerDocument; + + this._command = this._doc.getElementById("Tools:DevToolbar"); + + aChromeWindow.getBrowser().tabContainer.addEventListener("TabSelect", this, false); +} + +/** + * Inspector notifications dispatched through the nsIObserverService + */ +const NOTIFICATIONS = { + /** DeveloperToolbar.show() has been called */ + SHOW: "developer-toolbar-show", + + /** DeveloperToolbar.hide() has been called */ + HIDE: "developer-toolbar-hide" +}; + +/** + * Attach notification constants to the object prototype so tests etc can + * use them without needing to import anything + */ +DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS; + +/** + * Is the toolbar open? + */ +Object.defineProperty(DeveloperToolbar.prototype, 'visible', { + get: function DT_visible() { + return !this._element.hidden; + }, + enumerable: true +}); + +/** + * Called from browser.xul in response to menu-click or keyboard shortcut to + * toggle the toolbar + */ +DeveloperToolbar.prototype.toggle = function DT_toggle() +{ + if (this.visible) { + this.hide(); + } else { + this.show(); + this._input.focus(); + } +}; + +/** + * Even if the user has not clicked on 'Got it' in the intro, we only show it + * once per session. + * Warning this is slightly messed up because this.DeveloperToolbar is not the + * same as this.DeveloperToolbar when in browser.js context. + */ +DeveloperToolbar.introShownThisSession = false; + +/** + * Show the developer toolbar + */ +DeveloperToolbar.prototype.show = function DT_show() +{ + this._command.setAttribute("checked", "true"); + + this._input = this._doc.querySelector(".gclitoolbar-input-node"); + + this.tooltipPanel = new TooltipPanel(this._doc, this._input); + this.outputPanel = new OutputPanel(this._doc, this._input); + + let contentDocument = this._chromeWindow.getBrowser().contentDocument; + + this.display = gcli._internal.createDisplay({ + contentDocument: contentDocument, + chromeDocument: this._doc, + chromeWindow: this._chromeWindow, + + hintElement: this.tooltipPanel.hintElement, + inputElement: this._input, + completeElement: this._doc.querySelector(".gclitoolbar-complete-node"), + backgroundElement: this._doc.querySelector(".gclitoolbar-stack-node"), + outputDocument: this.outputPanel.document, + + environment: { + chromeDocument: this._doc, + contentDocument: contentDocument + }, + + tooltipClass: 'gcliterm-tooltip', + eval: null, + scratchpad: null + }); + + this.display.onVisibilityChange.add(this.outputPanel._visibilityChanged, this.outputPanel); + this.display.onVisibilityChange.add(this.tooltipPanel._visibilityChanged, this.tooltipPanel); + this.display.onOutput.add(this.outputPanel._outputChanged, this.outputPanel); + + this._element.hidden = false; + this._notify(NOTIFICATIONS.SHOW); + + if (!DeveloperToolbar.introShownThisSession) { + this.display.maybeShowIntro(); + DeveloperToolbar.introShownThisSession = true; + } +}; + +/** + * Hide the developer toolbar + */ +DeveloperToolbar.prototype.hide = function DT_hide() +{ + this._command.setAttribute("checked", "false"); + + this.display.onVisibilityChange.remove(this.outputPanel._visibilityChanged, this.outputPanel); + this.display.onVisibilityChange.remove(this.tooltipPanel._visibilityChanged, this.tooltipPanel); + this.display.onOutput.remove(this.outputPanel._outputChanged, this.outputPanel); + this.display.destroy(); + + // We could "delete this.display" etc if we have hard-to-track-down memory + // leaks as a belt-and-braces approach, however this prevents our DOM node + // hunter from looking in all the nooks and crannies, so it's better if we + // can be leak-free without + + this.outputPanel.remove(); + delete this.outputPanel; + + this.tooltipPanel.remove(); + delete this.tooltipPanel; + + this._element.hidden = true; + this._notify(NOTIFICATIONS.HIDE); +}; + +/** + * Utility for sending notifications + * @param aTopic a NOTIFICATION constant + */ +DeveloperToolbar.prototype._notify = function DT_notify(aTopic) +{ + let data = { toolbar: this }; + data.wrappedJSObject = data; + Services.obs.notifyObservers(data, aTopic, null); +}; + +/** + * Update various parts of the UI when the current tab changes + * @param aEvent + */ +DeveloperToolbar.prototype.handleEvent = function DT_handleEvent(aEvent) +{ + if (aEvent.type == "TabSelect") { + this._chromeWindow.HUDConsoleUI.refreshCommand(); + this._chromeWindow.DebuggerUI.refreshCommand(); + + if (this.visible) { + let contentDocument = this._chromeWindow.getBrowser().contentDocument; + + this.display.reattach({ + contentDocument: contentDocument, + chromeWindow: this._chromeWindow, + environment: { + chromeDocument: this._doc, + contentDocument: contentDocument + }, + }); + } + } +}; + +/** + * Add class="gcli-panel-inner-arrowcontent" to a panel's + * |<xul:box class="panel-inner-arrowcontent">| so we can alter the styling + * without complex CSS expressions. + * @param aPanel The panel to affect + */ +function getContentBox(aPanel) +{ + let container = aPanel.ownerDocument.getAnonymousElementByAttribute( + aPanel, "anonid", "container"); + return container.querySelector(".panel-inner-arrowcontent"); +} + +/** + * Helper function to calculate the sum of the vertical padding and margins + * between a nested node |aNode| and an ancestor |aRoot|. Iff all of the + * children of aRoot are 'only-childs' until you get to aNode then to avoid + * scroll-bars, the 'correct' height of aRoot is verticalSpacing + aNode.height. + * @param aNode The child node whose height is known. + * @param aRoot The parent height whose height we can affect. + * @return The sum of the vertical padding/margins in between aNode and aRoot. + */ +function getVerticalSpacing(aNode, aRoot) +{ + let win = aNode.ownerDocument.defaultView; + + function pxToNum(styles, property) { + return parseInt(styles.getPropertyValue(property).replace(/px$/, ''), 10); + } + + let vertSpacing = 0; + do { + let styles = win.getComputedStyle(aNode); + vertSpacing += pxToNum(styles, "padding-top"); + vertSpacing += pxToNum(styles, "padding-bottom"); + vertSpacing += pxToNum(styles, "margin-top"); + vertSpacing += pxToNum(styles, "margin-bottom"); + vertSpacing += pxToNum(styles, "border-top-width"); + vertSpacing += pxToNum(styles, "border-bottom-width"); + + let prev = aNode.previousSibling; + while (prev != null) { + vertSpacing += prev.clientHeight; + prev = prev.previousSibling; + } + + let next = aNode.nextSibling; + while (next != null) { + vertSpacing += next.clientHeight; + next = next.nextSibling; + } + + aNode = aNode.parentNode; + } while (aNode !== aRoot); + + return vertSpacing + 9; +} + +/** + * Panel to handle command line output. + * @param aChromeDoc document from which we can pull the parts we need. + * @param aInput the input element that should get focus. + */ +function OutputPanel(aChromeDoc, aInput) +{ + this._input = aInput; + this._panel = aChromeDoc.getElementById("gcli-output"); + this._frame = aChromeDoc.getElementById("gcli-output-frame"); + this._anchor = aChromeDoc.getElementById("developer-toolbar"); + + this._content = getContentBox(this._panel); + this._content.classList.add("gcli-panel-inner-arrowcontent"); + + this.document = this._frame.contentDocument; + this.document.body.classList.add("gclichrome-output"); + + this._div = this.document.querySelector("div"); + this._div.classList.add('gcli-row-out'); + this._div.setAttribute('aria-live', 'assertive'); + + this.displayedOutput = undefined; +} + +/** + * Display the OutputPanel. + */ +OutputPanel.prototype.show = function OP_show() +{ + this._panel.ownerDocument.defaultView.setTimeout(function() { + this._resize(); + }.bind(this), 0); + + this._resize(); + this._panel.openPopup(this._anchor, "before_end", -300, 0, false, false, null); + + this._input.focus(); +}; + +/** + * Internal helper to set the height of the output panel to fit the available + * content; + */ +OutputPanel.prototype._resize = function CLP_resize() +{ + let vertSpacing = getVerticalSpacing(this._content, this._panel); + let idealHeight = this.document.body.scrollHeight + vertSpacing; + this._panel.sizeTo(400, Math.min(idealHeight, 500)); +}; + +/** + * Called by GCLI when a command is executed. + */ +OutputPanel.prototype._outputChanged = function OP_outputChanged(aEvent) +{ + if (aEvent.output.hidden) { + return; + } + + this.remove(); + + this.displayedOutput = aEvent.output; + this.update(); + + this.displayedOutput.onChange.add(this.update, this); + this.displayedOutput.onClose.add(this.remove, this); +}; + +/** + * Called when displayed Output says it's changed or from outputChanged, which + * happens when there is a new displayed Output. + */ +OutputPanel.prototype.update = function OP_update() +{ + if (this.displayedOutput.data == null) { + while (this._div.hasChildNodes()) { + this._div.removeChild(this._div.firstChild); + } + } else { + this.displayedOutput.toDom(this._div); + this.show(); + } +}; + +/** + * Detach listeners from the currently displayed Output. + */ +OutputPanel.prototype.remove = function OP_remove() +{ + this._panel.hidePopup(); + + if (this.displayedOutput) { + this.displayedOutput.onChange.remove(this.update, this); + this.displayedOutput.onClose.remove(this.remove, this); + delete this.displayedOutput; + } +}; + +/** + * Called by GCLI to indicate that we should show or hide one either the + * tooltip panel or the output panel. + */ +OutputPanel.prototype._visibilityChanged = function OP_visibilityChanged(aEvent) +{ + if (aEvent.outputVisible === true) { + // this.show is called by _outputChanged + } else { + this._panel.hidePopup(); + } +}; + + +/** + * Panel to handle tooltips. + * @param aChromeDoc document from which we can pull the parts we need. + * @param aInput the input element that should get focus. + */ +function TooltipPanel(aChromeDoc, aInput) +{ + this._input = aInput; + this._panel = aChromeDoc.getElementById("gcli-tooltip"); + this._frame = aChromeDoc.getElementById("gcli-tooltip-frame"); + this._anchor = aChromeDoc.getElementById("developer-toolbar"); + + this._content = getContentBox(this._panel); + this._content.classList.add("gcli-panel-inner-arrowcontent"); + + this.document = this._frame.contentDocument; + this.document.body.classList.add("gclichrome-tooltip"); + + this.hintElement = this.document.querySelector("div"); +} + +/** + * Display the TooltipPanel. + */ +TooltipPanel.prototype.show = function TP_show() +{ + let vertSpacing = getVerticalSpacing(this._content, this._panel); + let idealHeight = this.document.body.scrollHeight + vertSpacing; + this._panel.sizeTo(350, Math.min(idealHeight, 500)); + this._panel.openPopup(this._anchor, "before_start", 0, 0, false, false, null); + + this._input.focus(); +}; + +/** + * Hide the TooltipPanel. + */ +TooltipPanel.prototype.remove = function TP_remove() +{ + this._panel.hidePopup(); +}; + +/** + * Called by GCLI to indicate that we should show or hide one either the + * tooltip panel or the output panel. + */ +TooltipPanel.prototype._visibilityChanged = function TP_visibilityChanged(aEvent) +{ + if (aEvent.tooltipVisible === true) { + this.show(); + } else { + this._panel.hidePopup(); + } +};
--- a/browser/devtools/shared/Templater.jsm +++ b/browser/devtools/shared/Templater.jsm @@ -37,43 +37,64 @@ * ***** END LICENSE BLOCK ***** */ var EXPORTED_SYMBOLS = [ "Templater", "template" ]; Components.utils.import("resource://gre/modules/Services.jsm"); const Node = Components.interfaces.nsIDOMNode; +/** + * For full documentation, see: + * https://github.com/mozilla/domtemplate/blob/master/README.md + */ + // WARNING: do not 'use_strict' without reading the notes in _envEval(); /** * Begin a new templating process. * @param node A DOM element or string referring to an element's id * @param data Data to use in filling out the template * @param options Options to customize the template processing. One of: * - allowEval: boolean (default false) Basic template interpolations are - * either property paths (e.g. ${a.b.c.d}), however if allowEval=true then we - * allow arbitrary JavaScript + * either property paths (e.g. ${a.b.c.d}), or if allowEval=true then we + * allow arbitrary JavaScript + * - stack: string or array of strings (default empty array) The template + * engine maintains a stack of tasks to help debug where it is. This allows + * this stack to be prefixed with a template name + * - blankNullUndefined: By default DOMTemplate exports null and undefined + * values using the strings 'null' and 'undefined', which can be helpful for + * debugging, but can introduce unnecessary extra logic in a template to + * convert null/undefined to ''. By setting blankNullUndefined:true, this + * conversion is handled by DOMTemplate */ function template(node, data, options) { var template = new Templater(options || {}); template.processNode(node, data); return template; } /** * Construct a Templater object. Use template() in preference to this ctor. * @deprecated Use template(node, data, options); */ function Templater(options) { if (options == null) { options = { allowEval: true }; } this.options = options; - this.stack = []; + if (options.stack && Array.isArray(options.stack)) { + this.stack = options.stack; + } + else if (typeof options.stack === 'string') { + this.stack = [ options.stack ]; + } + else { + this.stack = []; + } } /** * Cached regex used to find ${...} sections in some text. * Performance note: This regex uses ( and ) to capture the 'script' for * further processing. Not all of the uses of this regex use this feature so * if use of the capturing group is a performance drain then we should split * this regex in two. @@ -85,17 +106,17 @@ Templater.prototype._templateRegion = /\ * See Templater._processTextNode() for details. */ Templater.prototype._splitSpecial = /\uF001|\uF002/; /** * Cached regex used to detect if a script is capable of being interpreted * using Template._property() or if we need to use Template._envEval() */ -Templater.prototype._isPropertyScript = /^[a-zA-Z0-9.]*$/; +Templater.prototype._isPropertyScript = /^[_a-zA-Z0-9.]*$/; /** * Recursive function to walk the tree processing the attributes as it goes. * @param node the node to process. If you pass a string in instead of a DOM * element, it is assumed to be an id for use with document.getElementById() * @param data the data to use for node processing. */ Templater.prototype.processNode = function(node, data) { @@ -148,17 +169,21 @@ Templater.prototype.processNode = functi var capture = node.hasAttribute('capture' + name.substring(2)); node.addEventListener(name.substring(2), func, capture); if (capture) { node.removeAttribute('capture' + name.substring(2)); } } else { // Replace references in all other attributes var newValue = value.replace(this._templateRegion, function(path) { - return this._envEval(path.slice(2, -1), data, value); + var insert = this._envEval(path.slice(2, -1), data, value); + if (this.options.blankNullUndefined && insert == null) { + insert = ''; + } + return insert; }.bind(this)); // Remove '_' prefix of attribute names so the DOM won't try // to use them before we've processed the template if (name.charAt(0) === '_') { node.removeAttribute(name); node.setAttribute(name.substring(1), newValue); } else if (value !== newValue) { attrs[i].value = newValue; @@ -172,17 +197,17 @@ Templater.prototype.processNode = functi // Loop through our children calling processNode. First clone them, so the // set of nodes that we visit will be unaffected by additions or removals. var childNodes = Array.prototype.slice.call(node.childNodes); for (var j = 0; j < childNodes.length; j++) { this.processNode(childNodes[j], data); } - if (node.nodeType === Node.TEXT_NODE) { + if (node.nodeType === 3 /*Node.TEXT_NODE*/) { this._processTextNode(node, data); } } finally { delete data.__element; this.stack.pop(); } }; @@ -342,40 +367,51 @@ Templater.prototype._processTextNode = f parts.forEach(function(part) { if (part === null || part === undefined || part === '') { return; } if (part.charAt(0) === '$') { part = this._envEval(part.slice(1), data, node.data); } this._handleAsync(part, node, function(reply, siblingNode) { - reply = this._toNode(reply, siblingNode.ownerDocument); - siblingNode.parentNode.insertBefore(reply, siblingNode); + var doc = siblingNode.ownerDocument; + if (reply == null) { + reply = this.options.blankNullUndefined ? '' : '' + reply; + } + if (typeof reply.cloneNode === 'function') { + // i.e. if (reply instanceof Element) { ... + reply = this._maybeImportNode(reply, doc); + siblingNode.parentNode.insertBefore(reply, siblingNode); + } else if (typeof reply.item === 'function' && reply.length) { + // if thing is a NodeList, then import the children + for (var i = 0; i < reply.length; i++) { + var child = this._maybeImportNode(reply.item(i), doc); + siblingNode.parentNode.insertBefore(child, siblingNode); + } + } + else { + // if thing isn't a DOM element then wrap its string value in one + reply = doc.createTextNode(reply.toString()); + siblingNode.parentNode.insertBefore(reply, siblingNode); + } + }.bind(this)); }, this); node.parentNode.removeChild(node); } }; /** - * Helper to convert a 'thing' to a DOM Node. - * This is (obviously) a no-op for DOM Elements (which are detected using - * 'typeof thing.cloneNode !== "function"' (is there a better way that will - * work in all environments, including a .jsm?) - * Non DOM elements are converted to a string and wrapped in a TextNode. + * Return node or a import of node, if it's not in the given document + * @param node The node that we want to be properly owned + * @param doc The document that the given node should belong to + * @return A node that belongs to the given document */ -Templater.prototype._toNode = function(thing, document) { - if (thing == null) { - thing = '' + thing; - } - // if thing isn't a DOM element then wrap its string value in one - if (typeof thing.cloneNode !== 'function') { - thing = document.createTextNode(thing.toString()); - } - return thing; +Templater.prototype._maybeImportNode = function(node, doc) { + return node.ownerDocument === doc ? node : doc.importNode(node, true); }; /** * A function to handle the fact that some nodes can be promises, so we check * and resolve if needed using a marker node to keep our place before calling * an inserter function. * @param thing The object which could be real data or a promise of real data * we use it directly if it's not a promise, or resolve it if it is. @@ -424,38 +460,38 @@ Templater.prototype._stripBraces = funct * a string to be cut into an array using <tt>split('.')</tt> * @param data the data to use for node processing * @param newValue (optional) If defined, this value will replace the * original value for the data at the path specified. * @return The value pointed to by <tt>path</tt> before any * <tt>newValue</tt> is applied. */ Templater.prototype._property = function(path, data, newValue) { - this.stack.push(path); try { if (typeof path === 'string') { path = path.split('.'); } var value = data[path[0]]; if (path.length === 1) { if (newValue !== undefined) { data[path[0]] = newValue; } if (typeof value === 'function') { return value.bind(data); } return value; } if (!value) { - this._handleError('Can\'t find path=' + path); + this._handleError('"' + path[0] + '" is undefined'); return null; } return this._property(path.slice(1), value, newValue); - } finally { - this.stack.pop(); + } catch (ex) { + this._handleError('Path error with \'' + path + '\'', ex); + return '${' + path + '}'; } }; /** * Like eval, but that creates a context of the variables in <tt>env</tt> in * which the script is evaluated. * WARNING: This script uses 'with' which is generally regarded to be evil. * The alternative is to create a Function at runtime that takes X parameters @@ -464,47 +500,45 @@ Templater.prototype._property = function * @param script The string to be evaluated. * @param data The environment in which to eval the script. * @param frame Optional debugging string in case of failure. * @return The return value of the script, or the error message if the script * execution failed. */ Templater.prototype._envEval = function(script, data, frame) { try { - this.stack.push(frame); + this.stack.push(frame.replace(/\s+/g, ' ')); if (this._isPropertyScript.test(script)) { return this._property(script, data); } else { if (!this.options.allowEval) { this._handleError('allowEval is not set, however \'' + script + '\'' + ' can not be resolved using a simple property path.'); return '${' + script + '}'; } with (data) { return eval(script); } } } catch (ex) { - this._handleError('Template error evaluating \'' + script + '\'' + - ' environment=' + Object.keys(data).join(', '), ex); + this._handleError('Template error evaluating \'' + script + '\'', ex); return '${' + script + '}'; } finally { this.stack.pop(); } }; /** * A generic way of reporting errors, for easy overloading in different * environments. * @param message the error message to report. * @param ex optional associated exception. */ Templater.prototype._handleError = function(message, ex) { - this._logError(message); - this._logError('In: ' + this.stack.join(' > ')); + this._logError(message + ' (In: ' + this.stack.join(' > ') + ')'); if (ex) { this._logError(ex); } }; /** * A generic way of reporting errors, for easy overloading in different
--- a/browser/devtools/shared/test/Makefile.in +++ b/browser/devtools/shared/test/Makefile.in @@ -44,20 +44,30 @@ VPATH = @srcdir@ relativesrcdir = browser/devtools/shared/test include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk _BROWSER_TEST_FILES = \ browser_promise_basic.js \ browser_templater_basic.js \ + browser_toolbar_basic.js \ + browser_gcli_commands.js \ + browser_gcli_inspect.js \ + browser_gcli_integrate.js \ + browser_gcli_require.js \ + browser_gcli_web.js \ + browser_gcli_break.js \ head.js \ $(NULL) _BROWSER_TEST_PAGES = \ browser_templater_basic.html \ + browser_toolbar_basic.html \ + browser_gcli_inspect.html \ + browser_gcli_break.html \ $(NULL) libs:: $(_BROWSER_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) libs:: $(_BROWSER_TEST_PAGES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
rename from browser/devtools/webconsole/test/browser_gcli_break.html rename to browser/devtools/shared/test/browser_gcli_break.html --- a/browser/devtools/webconsole/test/browser_gcli_break.html +++ b/browser/devtools/shared/test/browser_gcli_break.html @@ -1,19 +1,19 @@ <!DOCTYPE HTML> <html> - <head> + <head> <meta charset="utf-8"> - <title>Browser GCLI break command test</title> + <title>Browser GCLI break command test</title> <!-- Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ --> <script type="text/javascript"> function firstCall() { eval("window.line0 = Error().lineNumber; secondCall();"); } function secondCall() { eval("debugger;"); } </script> - </head> - <body> - </body> + </head> + <body> + </body> </html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_gcli_break.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the break command works as it should + +const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_gcli_break.html"; + +function test() { + DeveloperToolbarTest.test(TEST_URI, function(browser, tab) { + testBreakCommands(); + }); +} + +function testBreakCommands() { + DeveloperToolbarTest.checkInputStatus({ + typed: "brea", + directTabText: "k", + status: "ERROR" + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "break", + status: "ERROR" + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "break add", + status: "ERROR" + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "break add line", + emptyParameters: [ " <file>", " <line>" ], + status: "ERROR" + }); + + let pane = DebuggerUI.toggleDebugger(); + pane._frame.addEventListener("Debugger:Connecting", function dbgConnected() { + pane._frame.removeEventListener("Debugger:Connecting", dbgConnected, true); + + // Wait for the initial resume. + let client = pane.debuggerWindow.gClient; + client.addOneTimeListener("resumed", function() { + client.activeThread.addOneTimeListener("framesadded", function() { + DeveloperToolbarTest.checkInputStatus({ + typed: "break add line " + TEST_URI + " " + content.wrappedJSObject.line0, + status: "VALID" + }); + DeveloperToolbarTest.exec({ + args: { + type: 'line', + file: TEST_URI, + line: content.wrappedJSObject.line0 + }, + completed: false + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "break list", + status: "VALID" + }); + DeveloperToolbarTest.exec(); + + client.activeThread.resume(function() { + DeveloperToolbarTest.checkInputStatus({ + typed: "break del 0", + status: "VALID" + }); + DeveloperToolbarTest.exec({ + args: { breakid: 0 }, + completed: false + }); + + finish(); + }); + }); + + // Trigger newScript notifications using eval. + content.wrappedJSObject.firstCall(); + }); + }, true); +}
rename from browser/devtools/webconsole/test/browser_gcli_commands.js rename to browser/devtools/shared/test/browser_gcli_commands.js --- a/browser/devtools/webconsole/test/browser_gcli_commands.js +++ b/browser/devtools/shared/test/browser_gcli_commands.js @@ -1,80 +1,55 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -// For more information on GCLI see: -// - https://github.com/mozilla/gcli/blob/master/docs/index.md -// - https://wiki.mozilla.org/DevTools/Features/GCLI - -let tmp = {}; -Components.utils.import("resource:///modules/gcli.jsm", tmp); -let gcli = tmp.gcli; +// Test various GCLI commands -let hud; -let gcliterm; +let imported = {}; +Components.utils.import("resource:///modules/HUDService.jsm", imported); -registerCleanupFunction(function() { - gcliterm = undefined; - hud = undefined; - Services.prefs.clearUserPref("devtools.gcli.enable"); -}); +const TEST_URI = "data:text/html;charset=utf-8,gcli-commands"; function test() { - Services.prefs.setBoolPref("devtools.gcli.enable", true); - addTab("http://example.com/browser/browser/devtools/webconsole/test/browser_gcli_inspect.html"); - browser.addEventListener("DOMContentLoaded", onLoad, false); -} - -function onLoad() { - browser.removeEventListener("DOMContentLoaded", onLoad, false); + DeveloperToolbarTest.test(TEST_URI, function(browser, tab) { + testEcho(); + testConsoleClear(); + testConsoleOpenClose(tab); - openConsole(); - - hud = HUDService.getHudByWindow(content); - gcliterm = hud.gcliterm; - - testEcho(); - - // gcli._internal.console.error("Command Tests Completed"); + imported = undefined; + finish(); + }); } function testEcho() { - let nodes = exec("echo message"); - is(nodes.length, 2, "after echo"); - is(nodes[0].textContent, "echo message", "output 0"); - is(nodes[1].textContent.trim(), "message", "output 1"); - - testConsoleClear(); + DeveloperToolbarTest.exec({ + typed: "echo message", + args: { message: "message" }, + outputMatch: /^message$/, + }); } function testConsoleClear() { - let nodes = exec("console clear"); - is(nodes.length, 1, "after console clear 1"); - - executeSoon(function() { - let nodes = hud.outputNode.querySelectorAll("richlistitem"); - is(nodes.length, 0, "after console clear 2"); - - testConsoleClose(); + DeveloperToolbarTest.exec({ + typed: "console clear", + args: {}, + blankOutput: true, }); } -function testConsoleClose() { - ok(hud.hudId in HUDService.hudReferences, "console open"); - - exec("console close"); - - ok(!(hud.hudId in HUDService.hudReferences), "console closed"); - - finishTest(); -} +function testConsoleOpenClose(tab) { + DeveloperToolbarTest.exec({ + typed: "console open", + args: {}, + blankOutput: true, + }); -function exec(command) { - gcliterm.clearOutput(); - let nodes = hud.outputNode.querySelectorAll("richlistitem"); - is(nodes.length, 0, "setup - " + command); + let hud = imported.HUDService.getHudByWindow(content); + ok(hud.hudId in imported.HUDService.hudReferences, "console open"); - gcliterm.opts.console.inputter.setInput(command); - gcliterm.opts.requisition.exec(); + DeveloperToolbarTest.exec({ + typed: "console close", + args: {}, + blankOutput: true, + }); - return hud.outputNode.querySelectorAll("richlistitem"); + ok(!(hud.hudId in imported.HUDService.hudReferences), "console closed"); }
rename from browser/devtools/webconsole/test/browser_gcli_inspect.html rename to browser/devtools/shared/test/browser_gcli_inspect.html
rename from browser/devtools/webconsole/test/browser_gcli_inspect.js rename to browser/devtools/shared/test/browser_gcli_inspect.js --- a/browser/devtools/webconsole/test/browser_gcli_inspect.js +++ b/browser/devtools/shared/test/browser_gcli_inspect.js @@ -1,95 +1,68 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -// For more information on GCLI see: -// - https://github.com/mozilla/gcli/blob/master/docs/index.md -// - https://wiki.mozilla.org/DevTools/Features/GCLI - // Tests that the inspect command works as it should -let tempScope = {}; -Components.utils.import("resource:///modules/gcli.jsm", tempScope); -let gcli = tempScope.gcli; - -registerCleanupFunction(function() { - gcliterm = undefined; - requisition = undefined; - - Services.prefs.clearUserPref("devtools.gcli.enable"); -}); +const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_gcli_inspect.html"; function test() { - Services.prefs.setBoolPref("devtools.gcli.enable", true); - addTab("http://example.com/browser/browser/devtools/webconsole/test/browser_gcli_inspect.html"); - browser.addEventListener("DOMContentLoaded", onLoad, false); -} - -let gcliterm; -let requisition; - -function onLoad() { - browser.removeEventListener("DOMContentLoaded", onLoad, false); - - try { - openConsole(); + DeveloperToolbarTest.test(TEST_URI, function(browser, tab) { + testInspect(); - let hud = HUDService.getHudByWindow(content); - gcliterm = hud.gcliterm; - requisition = gcliterm.opts.requisition; - - testSetup(); - testCreateCommands(); - } - catch (ex) { - ok(false, "Caught exception: " + ex) - gcli._internal.console.error("Test Failure", ex); - } - finally { - closeConsole(); - finishTest(); - } + finish(); + }); } -function testSetup() { - ok(gcliterm, "We have a GCLI term"); - ok(requisition, "We have a Requisition"); -} - -function testCreateCommands() { - type("inspec"); - is(gcliterm.completeNode.textContent, " inspect", "Completion for \"inspec\""); - is(requisition.getStatus().toString(), "ERROR", "inspec is ERROR"); +function testInspect() { + DeveloperToolbarTest.checkInputStatus({ + typed: "inspec", + directTabText: "t", + status: "ERROR" + }); - type("inspect"); - is(requisition.getStatus().toString(), "ERROR", "inspect is ERROR"); - - type("inspect h1"); - is(requisition.getStatus().toString(), "ERROR", "inspect h1 is ERROR"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect", + emptyParameters: [ " <node>" ], + status: "ERROR" + }); - type("inspect span"); - is(requisition.getStatus().toString(), "ERROR", "inspect span is ERROR"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect h1", + status: "ERROR" + }); - type("inspect div"); - is(requisition.getStatus().toString(), "VALID", "inspect div is VALID"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect span", + status: "ERROR" + }); - type("inspect .someclass"); - is(requisition.getStatus().toString(), "VALID", "inspect .someclass is VALID"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect div", + status: "VALID" + }); - type("inspect #someid"); - is(requisition.getStatus().toString(), "VALID", "inspect #someid is VALID"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect .someclass", + status: "VALID" + }); - type("inspect button[disabled]"); - is(requisition.getStatus().toString(), "VALID", "inspect button[disabled] is VALID"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect #someid", + status: "VALID" + }); - type("inspect p>strong"); - is(requisition.getStatus().toString(), "VALID", "inspect p>strong is VALID"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect button[disabled]", + status: "VALID" + }); - type("inspect :root"); - is(requisition.getStatus().toString(), "VALID", "inspect :root is VALID"); + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect p>strong", + status: "VALID" + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "inspect :root", + status: "VALID" + }); } - -function type(command) { - gcliterm.inputNode.value = command.slice(0, -1); - gcliterm.inputNode.focus(); - EventUtils.synthesizeKey(command.slice(-1), {}); -}
rename from browser/devtools/webconsole/test/browser_gcli_integrate.js rename to browser/devtools/shared/test/browser_gcli_integrate.js --- a/browser/devtools/webconsole/test/browser_gcli_integrate.js +++ b/browser/devtools/shared/test/browser_gcli_integrate.js @@ -1,106 +1,40 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -// For more information on GCLI see: -// - https://github.com/mozilla/gcli/blob/master/docs/index.md -// - https://wiki.mozilla.org/DevTools/Features/GCLI - // Tests that source URLs in the Web Console can be clicked to display the // standard View Source window. -let tempScope = {}; -Components.utils.import("resource:///modules/gcli.jsm", tempScope); -let gcli = tempScope.gcli; -let require = gcli._internal.require; - -const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html"; - -registerCleanupFunction(function() { - require = undefined; - Services.prefs.clearUserPref("devtools.gcli.enable"); -}); - function test() { - Services.prefs.setBoolPref("devtools.gcli.enable", true); - addTab(TEST_URI); - browser.addEventListener("DOMContentLoaded", onLoad, false); -} - -function onLoad() { - browser.removeEventListener("DOMContentLoaded", onLoad, false); - - try { - openConsole(); - - testCreateCommands(); - testCallCommands(); - testRemoveCommands(); - } - catch (ex) { - gcli._internal.console.error('Test Failure', ex); - ok(false, '' + ex); - } - finally { - closeConsole(); - finishTest(); - } + testCreateCommands(); + testRemoveCommands(); } let tselarr = { name: 'tselarr', params: [ { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } }, { name: 'arr', type: { name: 'array', subtype: 'string' } }, ], exec: function(args, env) { return "flu " + args.num + "-" + args.arr.join("_"); } }; function testCreateCommands() { - let gcli = require("gcli/index"); - gcli.addCommand(tselarr); + let gcliIndex = require("gcli/index"); + gcliIndex.addCommand(tselarr); let canon = require("gcli/canon"); let tselcmd = canon.getCommand("tselarr"); ok(tselcmd != null, "tselarr exists in the canon"); ok(tselcmd instanceof canon.Command, "canon storing commands"); } -function testCallCommands() { - let hud = HUDService.getHudByWindow(content); - let gcliterm = hud.gcliterm; - ok(gcliterm, "We have a GCLI term"); - - // Test successful auto-completion - gcliterm.inputNode.value = "h"; - gcliterm.inputNode.focus(); - EventUtils.synthesizeKey("e", {}); - is(gcliterm.completeNode.textContent, " help", "Completion for \"he\""); - - // Test unsuccessful auto-completion - gcliterm.inputNode.value = "ec"; - gcliterm.inputNode.focus(); - EventUtils.synthesizeKey("d", {}); - is(gcliterm.completeNode.textContent, " ecd", "Completion for \"ecd\""); - - // Test a normal command's life cycle - gcliterm.opts.console.inputter.setInput("echo hello world"); - gcliterm.opts.requisition.exec(); - - let nodes = hud.outputNode.querySelectorAll(".gcliterm-msg-body"); - - is(nodes.length, 1, "Right number of output nodes"); - ok(/hello world/.test(nodes[0].textContent), "the command's output is correct."); - - gcliterm.clearOutput(); -} - function testRemoveCommands() { - let gcli = require("gcli/index"); - gcli.removeCommand(tselarr); + let gcliIndex = require("gcli/index"); + gcliIndex.removeCommand(tselarr); let canon = require("gcli/canon"); let tselcmd = canon.getCommand("tselarr"); ok(tselcmd == null, "tselcmd removed from the canon"); }
rename from browser/devtools/webconsole/test/browser_gcli_require.js rename to browser/devtools/shared/test/browser_gcli_require.js --- a/browser/devtools/webconsole/test/browser_gcli_require.js +++ b/browser/devtools/shared/test/browser_gcli_require.js @@ -1,30 +1,15 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -// For more information on GCLI see: -// - https://github.com/mozilla/gcli/blob/master/docs/index.md -// - https://wiki.mozilla.org/DevTools/Features/GCLI - // Tests that source URLs in the Web Console can be clicked to display the // standard View Source window. -var modules = { gcli: null }; - -Components.utils.import("resource:///modules/gcli.jsm", modules); - -var define, require, console; - function test() { - - define = modules.gcli._internal.define; - require = modules.gcli._internal.require; - console = modules.gcli._internal.console; - define('gclitest/requirable', [], function(require, exports, module) { exports.thing1 = 'thing1'; exports.thing2 = 2; let status = 'initial'; exports.setStatus = function(aStatus) { status = aStatus; }; exports.getStatus = function() { return status; }; }); @@ -38,30 +23,24 @@ function test() { }); testWorking(); testLeakage(); testMultiImport(); testRecursive(); testUncompilable(); - finishTest(); - delete define.modules['gclitest/requirable']; delete define.globalDomain.modules['gclitest/requirable']; delete define.modules['gclitest/unrequirable']; delete define.globalDomain.modules['gclitest/unrequirable']; delete define.modules['gclitest/recurse']; delete define.globalDomain.modules['gclitest/recurse']; - define = null; - require = null; - console = null; - - modules = null; + finish(); } function testWorking() { // There are lots of requirement tests that we could be doing here // The fact that we can get anything at all working is a testament to // require doing what it should - we don't need to test the let requireable = require('gclitest/requirable'); ok('thing1' == requireable.thing1, 'thing1 was required');
rename from browser/devtools/webconsole/test/browser_gcli_web.js rename to browser/devtools/shared/test/browser_gcli_web.js --- a/browser/devtools/webconsole/test/browser_gcli_web.js +++ b/browser/devtools/shared/test/browser_gcli_web.js @@ -14,17 +14,17 @@ * *********************************** WARNING *********************************** * * Do not edit this file without understanding where it comes from, * Your changes are likely to be overwritten without warning. * * This test file is generated using a level 25 wizard spell cast on the * test files that run in the browser as part of GCLI's test suite. - * For details of how to cast the spell, see GCLI's Makefile.dryice.js + * For details of how to cast the spell, see GCLI's gcli.js * * For more information on GCLI see: * - https://github.com/mozilla/gcli/blob/master/docs/index.md * - https://wiki.mozilla.org/DevTools/Features/GCLI * * The original source for this file is: * https://github.com/mozilla/gcli/ * @@ -36,67 +36,97 @@ * * * * * */ /////////////////////////////////////////////////////////////////////////////// - -var obj = {}; -Components.utils.import("resource:///modules/gcli.jsm", obj); - -var define = obj.gcli._internal.define; -var console = obj.gcli._internal.console; -var Node = Components.interfaces.nsIDOMNode; /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gclitest/index', ['require', 'exports', 'module' , 'gclitest/suite'], function(require, exports, module) { var examiner = require('gclitest/suite').examiner; + // A minimum fake dom to get us through the JS tests + var fakeWindow = { + isFake: true, + document: { title: 'Fake DOM' } + }; + fakeWindow.window = fakeWindow; + examiner.defaultOptions = { + window: fakeWindow, + hideExec: true + }; + /** * A simple proxy to examiner.run, for convenience - this is run from the * top level. + * @param options Lookup of options that customize test running. Includes: + * - window (default=undefined) A reference to the DOM window. If left + * undefined then a reduced set of tests will run. + * - isNode (default=false) Are we running under NodeJS, specifically, are we + * using JSDom, which isn't a 100% complete DOM implementation. + * Some tests are skipped when using NodeJS. + * - display (default=undefined) A reference to a Display implementation. + * A reduced set of tests will run if left undefined + * - detailedResultLog (default=false) do we output a test summary to + * |console.log| on test completion. + * - hideExec (default=false) Set the |hidden| property in calls to + * |requisition.exec()| which prevents the display from becoming messed up, + * however use of hideExec restricts the set of tests that are run */ exports.run = function(options) { examiner.run(options || {}); + + // A better set of default than those specified above, come from the set + // that are passed to run(). + examiner.defaultOptions = { + window: options.window, + display: options.display, + hideExec: options.hideExec + }; }; + }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testTokenize', 'gclitest/testSplit', 'gclitest/testCli', 'gclitest/testExec', 'gclitest/testKeyboard', 'gclitest/testScratchpad', 'gclitest/testHistory', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testJs', 'gclitest/testUtil'], function(require, exports, module) { +define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHistory', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSpell', 'gclitest/testSplit', 'gclitest/testTokenize', 'gclitest/testTooltip', 'gclitest/testTypes', 'gclitest/testUtil'], function(require, exports, module) { // We need to make sure GCLI is initialized before we begin testing it require('gcli/index'); var examiner = require('test/examiner'); // It's tempting to want to unify these strings and make addSuite() do the // call to require(), however that breaks the build system which looks for // the strings passed to require - examiner.addSuite('gclitest/testTokenize', require('gclitest/testTokenize')); - examiner.addSuite('gclitest/testSplit', require('gclitest/testSplit')); examiner.addSuite('gclitest/testCli', require('gclitest/testCli')); + examiner.addSuite('gclitest/testCompletion', require('gclitest/testCompletion')); examiner.addSuite('gclitest/testExec', require('gclitest/testExec')); + examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory')); + examiner.addSuite('gclitest/testJs', require('gclitest/testJs')); examiner.addSuite('gclitest/testKeyboard', require('gclitest/testKeyboard')); - examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad')); - examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory')); examiner.addSuite('gclitest/testRequire', require('gclitest/testRequire')); examiner.addSuite('gclitest/testResource', require('gclitest/testResource')); - examiner.addSuite('gclitest/testJs', require('gclitest/testJs')); + examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad')); + examiner.addSuite('gclitest/testSpell', require('gclitest/testSpell')); + examiner.addSuite('gclitest/testSplit', require('gclitest/testSplit')); + examiner.addSuite('gclitest/testTokenize', require('gclitest/testTokenize')); + examiner.addSuite('gclitest/testTooltip', require('gclitest/testTooltip')); + examiner.addSuite('gclitest/testTypes', require('gclitest/testTypes')); examiner.addSuite('gclitest/testUtil', require('gclitest/testUtil')); exports.examiner = examiner; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause @@ -130,250 +160,385 @@ var stati = { * Add a test suite. Generally used like: * test.addSuite('foo', require('path/to/foo')); */ examiner.addSuite = function(name, suite) { examiner.suites[name] = new Suite(name, suite); }; /** + * When run from an command, there are some options that we can't (and + * shouldn't) specify, so we allow a set of default options, which are merged + * with the specified options in |run()|. + */ +examiner.defaultOptions = {}; + +/** + * Add properties to |options| from |examiner.defaultOptions| when |options| + * does not have a value for a given name. + */ +function mergeDefaultOptions(options) { + Object.keys(examiner.defaultOptions).forEach(function(name) { + if (options[name] == null) { + options[name] = examiner.defaultOptions[name]; + } + }); +} + +/** * Run the tests defined in the test suite synchronously - * @param options How the tests are run. Properties include: - * - window: The browser window object to run the tests against - * - useFakeWindow: Use a test subset and a fake DOM to avoid a real document - * - detailedResultLog: console.log test passes and failures in more detail */ examiner.run = function(options) { - examiner._checkOptions(options); + mergeDefaultOptions(options); Object.keys(examiner.suites).forEach(function(suiteName) { var suite = examiner.suites[suiteName]; suite.run(options); }.bind(this)); if (options.detailedResultLog) { - examiner.log(); + examiner.detailedResultLog(); } else { console.log('Completed test suite'); } return examiner.suites; }; /** - * Check the options object. There should be either useFakeWindow or a window. - * Setup the fake window if requested. - */ -examiner._checkOptions = function(options) { - if (options.useFakeWindow) { - // A minimum fake dom to get us through the JS tests - var doc = { title: 'Fake DOM' }; - var fakeWindow = { - window: { document: doc }, - document: doc - }; - - options.window = fakeWindow; - } - - if (!options.window) { - throw new Error('Tests need either window or useFakeWindow'); - } -}; - -/** * Run all the tests asynchronously */ examiner.runAsync = function(options, callback) { - examiner._checkOptions(options); - this.runAsyncInternal(0, options, callback); + mergeDefaultOptions(options); + this._runAsyncInternal(0, options, callback); }; /** * Run all the test suits asynchronously */ -examiner.runAsyncInternal = function(i, options, callback) { +examiner._runAsyncInternal = function(i, options, callback) { if (i >= Object.keys(examiner.suites).length) { if (typeof callback === 'function') { callback(); } return; } var suiteName = Object.keys(examiner.suites)[i]; examiner.suites[suiteName].runAsync(options, function() { setTimeout(function() { - examiner.runAsyncInternal(i + 1, options, callback); + examiner._runAsyncInternal(i + 1, options, callback); }.bind(this), delay); }.bind(this)); }; /** - * - */ -examiner.reportToText = function() { - return JSON.stringify(examiner.toRemote()); -}; - -/** * Create a JSON object suitable for serialization */ examiner.toRemote = function() { return { suites: Object.keys(examiner.suites).map(function(suiteName) { return examiner.suites[suiteName].toRemote(); - }.bind(this)) + }.bind(this)), + summary: { + checks: this.checks, + status: this.status + } }; }; /** + * The number of checks in this set of test suites is the sum of the checks in + * the test suites. + */ +Object.defineProperty(examiner, 'checks', { + get: function() { + return Object.keys(examiner.suites).reduce(function(current, suiteName) { + return current + examiner.suites[suiteName].checks; + }.bind(this), 0); + }, + enumerable: true +}); + +/** + * The status of this set of test suites is the worst of the statuses of the + * contained test suites. + */ +Object.defineProperty(examiner, 'status', { + get: function() { + return Object.keys(examiner.suites).reduce(function(status, suiteName) { + var suiteStatus = examiner.suites[suiteName].status; + return status.index > suiteStatus.index ? status : suiteStatus; + }.bind(this), stati.notrun); + }, + enumerable: true +}); + +/** * Output a test summary to console.log */ -examiner.log = function() { - var remote = this.toRemote(); - remote.suites.forEach(function(suite) { - console.log(suite.name); - suite.tests.forEach(function(test) { - console.log('- ' + test.name, test.status.name, test.message || ''); - }); - }); +examiner.detailedResultLog = function() { + Object.keys(this.suites).forEach(function(suiteName) { + var suite = examiner.suites[suiteName]; + + console.log(suite.name + ': ' + suite.status.name + ' (funcs=' + + Object.keys(suite.tests).length + + ', checks=' + suite.checks + ')'); + + Object.keys(suite.tests).forEach(function(testName) { + var test = suite.tests[testName]; + if (test.status !== stati.pass || test.failures.length !== 0) { + console.log('- ' + test.name + ': ' + test.status.name); + test.failures.forEach(function(failure) { + console.log(' - ' + failure.message); + if (failure.expected) { + console.log(' - Expected: ' + failure.expected); + console.log(' - Actual: ' + failure.actual); + } + }.bind(this)); + } + }.bind(this)); + }.bind(this)); + + console.log(); + console.log('Summary: ' + this.status.name + ' (' + this.checks + ' checks)'); }; /** * Used by assert to record a failure against the current test + * @param failure A set of properties describing the failure. Properties include: + * - message (string, required) A message describing the test + * - expected (optional) The expected data + * - actual (optional) The actual data */ -examiner.recordError = function(message) { +examiner.recordFailure = function(failure) { if (!currentTest) { - console.error('No currentTest for ' + message); + console.error('No currentTest for ' + failure.message); return; } currentTest.status = stati.fail; - - if (Array.isArray(message)) { - currentTest.messages.push.apply(currentTest.messages, message); + currentTest.failures.push(failure); +}; + +/** + * Used by assert to record a check pass + */ +examiner.recordPass = function() { + if (!currentTest) { + console.error('No currentTest'); + return; } - else { - currentTest.messages.push(message); - } + + currentTest.checks++; +}; + +/** + * When we want to note something alongside a test + */ +examiner.log = function(message) { + currentTest.failures.push({ message: message }); }; /** * A suite is a group of tests */ function Suite(suiteName, suite) { - this.name = suiteName; + this.name = suiteName.replace(/gclitest\//, ''); this.suite = suite; this.tests = {}; Object.keys(suite).forEach(function(testName) { if (testName !== 'setup' && testName !== 'shutdown') { var test = new Test(this, testName, suite[testName]); this.tests[testName] = test; } }.bind(this)); } /** * Run all the tests in this suite synchronously */ Suite.prototype.run = function(options) { - if (typeof this.suite.setup == "function") { - this.suite.setup(options); + if (!this._setup(options)) { + return; } Object.keys(this.tests).forEach(function(testName) { var test = this.tests[testName]; test.run(options); }.bind(this)); - if (typeof this.suite.shutdown == "function") { - this.suite.shutdown(options); - } + this._shutdown(options); }; /** * Run all the tests in this suite asynchronously */ Suite.prototype.runAsync = function(options, callback) { - if (typeof this.suite.setup == "function") { - this.suite.setup(options); + if (!this._setup(options)) { + if (typeof callback === 'function') { + callback(); + } + return; } - this.runAsyncInternal(0, options, function() { - if (typeof this.suite.shutdown == "function") { - this.suite.shutdown(options); - } + this._runAsyncInternal(0, options, function() { + this._shutdown(options); if (typeof callback === 'function') { callback(); } }.bind(this)); }; /** * Function used by the async runners that can handle async recursion. */ -Suite.prototype.runAsyncInternal = function(i, options, callback) { +Suite.prototype._runAsyncInternal = function(i, options, callback) { if (i >= Object.keys(this.tests).length) { if (typeof callback === 'function') { callback(); } return; } var testName = Object.keys(this.tests)[i]; this.tests[testName].runAsync(options, function() { setTimeout(function() { - this.runAsyncInternal(i + 1, options, callback); + this._runAsyncInternal(i + 1, options, callback); }.bind(this), delay); }.bind(this)); }; /** * Create a JSON object suitable for serialization */ Suite.prototype.toRemote = function() { return { name: this.name, tests: Object.keys(this.tests).map(function(testName) { return this.tests[testName].toRemote(); }.bind(this)) }; }; +/** + * The number of checks in this suite is the sum of the checks in the contained + * tests. + */ +Object.defineProperty(Suite.prototype, 'checks', { + get: function() { + return Object.keys(this.tests).reduce(function(prevChecks, testName) { + return prevChecks + this.tests[testName].checks; + }.bind(this), 0); + }, + enumerable: true +}); + +/** + * The status of a test suite is the worst of the statuses of the contained + * tests. + */ +Object.defineProperty(Suite.prototype, 'status', { + get: function() { + return Object.keys(this.tests).reduce(function(prevStatus, testName) { + var suiteStatus = this.tests[testName].status; + return prevStatus.index > suiteStatus.index ? prevStatus : suiteStatus; + }.bind(this), stati.notrun); + }, + enumerable: true +}); + +/** + * Defensively setup the test suite + */ +Suite.prototype._setup = function(options) { + if (typeof this.suite.setup !== 'function') { + return true; + } + + try { + this.suite.setup(options); + return true; + } + catch (ex) { + this._logToAllTests(stati.notrun, '' + ex); + console.error(ex); + if (ex.stack) { + console.error(ex.stack); + } + return false; + } +}; + +/** + * Defensively shutdown the test suite + */ +Suite.prototype._shutdown = function(options) { + if (typeof this.suite.shutdown !== 'function') { + return true; + } + + try { + this.suite.shutdown(options); + return true; + } + catch (ex) { + this._logToAllTests(stati.fail, '' + ex); + console.error(ex); + if (ex.stack) { + console.error(ex.stack); + } + return false; + } +}; + +/** + * Something has gone wrong that affects all tests in this Suite + */ +Suite.prototype._logToAllTests = function(status, message) { + Object.keys(this.tests).forEach(function(testName) { + var test = this.tests[testName]; + test.status = status; + test.failures.push({ message: message }); + }.bind(this)); +}; + /** * A test represents data about a single test function */ function Test(suite, name, func) { this.suite = suite; this.name = name; this.func = func; this.title = name.replace(/^test/, '').replace(/([A-Z])/g, ' $1'); - this.messages = []; + this.failures = []; this.status = stati.notrun; + this.checks = 0; } /** * Run just a single test */ Test.prototype.run = function(options) { currentTest = this; this.status = stati.executing; - this.messages = []; + this.failures = []; + this.checks = 0; try { this.func.apply(this.suite, [ options ]); } catch (ex) { this.status = stati.fail; - this.messages.push('' + ex); + this.failures.push({ message: '' + ex }); console.error(ex); if (ex.stack) { console.error(ex.stack); } } if (this.status === stati.executing) { this.status = stati.pass; @@ -397,28 +562,1885 @@ Test.prototype.runAsync = function(optio /** * Create a JSON object suitable for serialization */ Test.prototype.toRemote = function() { return { name: this.name, title: this.title, status: this.status, - messages: this.messages + failures: this.failures, + checks: this.checks }; }; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ +define('gclitest/testCli', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gclitest/commands', 'test/assert'], function(require, exports, module) { + + +var Requisition = require('gcli/cli').Requisition; +var Status = require('gcli/types').Status; +var commands = require('gclitest/commands'); + +var test = require('test/assert'); + +exports.setup = function() { + commands.setup(); +}; + +exports.shutdown = function() { + commands.shutdown(); +}; + + +var assign1; +var assign2; +var assignC; +var requ; +var debug = false; +var status; +var statuses; + +function update(input) { + if (!requ) { + requ = new Requisition(); + } + requ.update(input.typed); + + if (debug) { + console.log('####### TEST: typed="' + input.typed + + '" cur=' + input.cursor.start + + ' cli=', requ); + } + + status = requ.getStatus(); + assignC = requ.getAssignmentAt(input.cursor.start); + statuses = requ.getInputStatusMarkup(input.cursor.start).map(function(s) { + return Array(s.string.length + 1).join(s.status.toString()[0]); + }).join(''); + + if (requ.commandAssignment.value) { + assign1 = requ.getAssignment(0); + assign2 = requ.getAssignment(1); + } + else { + assign1 = undefined; + assign2 = undefined; + } +} + +function verifyPredictionsContains(name, predictions) { + return predictions.every(function(prediction) { + return name === prediction.name; + }, this); +} + + +exports.testBlank = function() { + update({ typed: '', cursor: { start: 0, end: 0 } }); + test.is( '', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.is(undefined, requ.commandAssignment.value); + + update({ typed: ' ', cursor: { start: 1, end: 1 } }); + test.is( 'V', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.is(undefined, requ.commandAssignment.value); + + update({ typed: ' ', cursor: { start: 0, end: 0 } }); + test.is( 'V', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.is(undefined, requ.commandAssignment.value); +}; + +exports.testIncompleteMultiMatch = function() { + update({ typed: 't', cursor: { start: 1, end: 1 } }); + test.is( 'I', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.ok(assignC.getPredictions().length > 0); + verifyPredictionsContains('tsv', assignC.getPredictions()); + verifyPredictionsContains('tsr', assignC.getPredictions()); + test.is(undefined, requ.commandAssignment.value); +}; + +exports.testIncompleteSingleMatch = function() { + update({ typed: 'tselar', cursor: { start: 6, end: 6 } }); + test.is( 'IIIIII', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.is(1, assignC.getPredictions().length); + test.is('tselarr', assignC.getPredictions()[0].name); + test.is(undefined, requ.commandAssignment.value); +}; + +exports.testTsv = function() { + update({ typed: 'tsv', cursor: { start: 3, end: 3 } }); + test.is( 'VVV', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.is('tsv', requ.commandAssignment.value.name); + + update({ typed: 'tsv ', cursor: { start: 4, end: 4 } }); + test.is( 'VVVV', statuses); + test.is(Status.ERROR, status); + test.is(0, assignC.paramIndex); + test.is('tsv', requ.commandAssignment.value.name); + + update({ typed: 'tsv ', cursor: { start: 2, end: 2 } }); + test.is( 'VVVV', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.is('tsv', requ.commandAssignment.value.name); + + update({ typed: 'tsv o', cursor: { start: 5, end: 5 } }); + test.is( 'VVVVI', statuses); + test.is(Status.ERROR, status); + test.is(0, assignC.paramIndex); + test.ok(assignC.getPredictions().length >= 2); + test.is(commands.option1, assignC.getPredictions()[0].value); + test.is(commands.option2, assignC.getPredictions()[1].value); + test.is('tsv', requ.commandAssignment.value.name); + test.is('o', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsv option', cursor: { start: 10, end: 10 } }); + test.is( 'VVVVIIIIII', statuses); + test.is(Status.ERROR, status); + test.is(0, assignC.paramIndex); + test.ok(assignC.getPredictions().length >= 2); + test.is(commands.option1, assignC.getPredictions()[0].value); + test.is(commands.option2, assignC.getPredictions()[1].value); + test.is('tsv', requ.commandAssignment.value.name); + test.is('option', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsv option', cursor: { start: 1, end: 1 } }); + test.is( 'VVVVEEEEEE', statuses); + test.is(Status.ERROR, status); + test.is(-1, assignC.paramIndex); + test.is('tsv', requ.commandAssignment.value.name); + test.is('option', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsv option ', cursor: { start: 11, end: 11 } }); + test.is( 'VVVVEEEEEEV', statuses); + test.is(Status.ERROR, status); + test.is(1, assignC.paramIndex); + test.is(0, assignC.getPredictions().length); + test.is('tsv', requ.commandAssignment.value.name); + test.is('option', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsv option1', cursor: { start: 11, end: 11 } }); + test.is( 'VVVVVVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsv', requ.commandAssignment.value.name); + test.is('option1', assign1.arg.text); + test.is(commands.option1, assign1.value); + test.is(0, assignC.paramIndex); + + update({ typed: 'tsv option1 ', cursor: { start: 12, end: 12 } }); + test.is( 'VVVVVVVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsv', requ.commandAssignment.value.name); + test.is('option1', assign1.arg.text); + test.is(commands.option1, assign1.value); + test.is(1, assignC.paramIndex); + + update({ typed: 'tsv option1 6', cursor: { start: 13, end: 13 } }); + test.is( 'VVVVVVVVVVVVV', statuses); + test.is(Status.VALID, status); + test.is('tsv', requ.commandAssignment.value.name); + test.is('option1', assign1.arg.text); + test.is(commands.option1, assign1.value); + test.is('6', assign2.arg.text); + test.is('6', assign2.value); + test.is('string', typeof assign2.value); + test.is(1, assignC.paramIndex); + + update({ typed: 'tsv option2 6', cursor: { start: 13, end: 13 } }); + test.is( 'VVVVVVVVVVVVV', statuses); + test.is(Status.VALID, status); + test.is('tsv', requ.commandAssignment.value.name); + test.is('option2', assign1.arg.text); + test.is(commands.option2, assign1.value); + test.is('6', assign2.arg.text); + test.is(6, assign2.value); + test.is('number', typeof assign2.value); + test.is(1, assignC.paramIndex); +}; + +exports.testInvalid = function() { + update({ typed: 'zxjq', cursor: { start: 4, end: 4 } }); + test.is( 'EEEE', statuses); + test.is('zxjq', requ.commandAssignment.arg.text); + test.is('', requ._unassigned.arg.text); + test.is(-1, assignC.paramIndex); + + update({ typed: 'zxjq ', cursor: { start: 5, end: 5 } }); + test.is( 'EEEEV', statuses); + test.is('zxjq', requ.commandAssignment.arg.text); + test.is('', requ._unassigned.arg.text); + test.is(-1, assignC.paramIndex); + + update({ typed: 'zxjq one', cursor: { start: 8, end: 8 } }); + test.is( 'EEEEVEEE', statuses); + test.is('zxjq', requ.commandAssignment.arg.text); + test.is('one', requ._unassigned.arg.text); +}; + +exports.testSingleString = function() { + update({ typed: 'tsr', cursor: { start: 3, end: 3 } }); + test.is( 'VVV', statuses); + test.is(Status.ERROR, status); + test.is('tsr', requ.commandAssignment.value.name); + test.ok(assign1.arg.isBlank()); + test.is(undefined, assign1.value); + test.is(undefined, assign2); + + update({ typed: 'tsr ', cursor: { start: 4, end: 4 } }); + test.is( 'VVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsr', requ.commandAssignment.value.name); + test.ok(assign1.arg.isBlank()); + test.is(undefined, assign1.value); + test.is(undefined, assign2); + + update({ typed: 'tsr h', cursor: { start: 5, end: 5 } }); + test.is( 'VVVVV', statuses); + test.is(Status.VALID, status); + test.is('tsr', requ.commandAssignment.value.name); + test.is('h', assign1.arg.text); + test.is('h', assign1.value); + + update({ typed: 'tsr "h h"', cursor: { start: 9, end: 9 } }); + test.is( 'VVVVVVVVV', statuses); + test.is(Status.VALID, status); + test.is('tsr', requ.commandAssignment.value.name); + test.is('h h', assign1.arg.text); + test.is('h h', assign1.value); + + update({ typed: 'tsr h h h', cursor: { start: 9, end: 9 } }); + test.is( 'VVVVVVVVV', statuses); + test.is('tsr', requ.commandAssignment.value.name); + test.is('h h h', assign1.arg.text); + test.is('h h h', assign1.value); +}; + +exports.testSingleNumber = function() { + update({ typed: 'tsu', cursor: { start: 3, end: 3 } }); + test.is( 'VVV', statuses); + test.is(Status.ERROR, status); + test.is('tsu', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsu ', cursor: { start: 4, end: 4 } }); + test.is( 'VVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsu', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsu 1', cursor: { start: 5, end: 5 } }); + test.is( 'VVVVV', statuses); + test.is(Status.VALID, status); + test.is('tsu', requ.commandAssignment.value.name); + test.is('1', assign1.arg.text); + test.is(1, assign1.value); + test.is('number', typeof assign1.value); + + update({ typed: 'tsu x', cursor: { start: 5, end: 5 } }); + test.is( 'VVVVE', statuses); + test.is(Status.ERROR, status); + test.is('tsu', requ.commandAssignment.value.name); + test.is('x', assign1.arg.text); + test.is(undefined, assign1.value); +}; + +exports.testElement = function(options) { + update({ typed: 'tse', cursor: { start: 3, end: 3 } }); + test.is( 'VVV', statuses); + test.is(Status.ERROR, status); + test.is('tse', requ.commandAssignment.value.name); + test.ok(assign1.arg.isBlank()); + test.is(undefined, assign1.value); + + update({ typed: 'tse :root', cursor: { start: 9, end: 9 } }); + test.is( 'VVVVVVVVV', statuses); + test.is(Status.VALID, status); + test.is('tse', requ.commandAssignment.value.name); + test.is(':root', assign1.arg.text); + if (!options.window.isFake) { + test.is(options.window.document.documentElement, assign1.value); + } + + if (!options.window.isFake) { + var inputElement = options.window.document.getElementById('gcli-input'); + if (inputElement) { + update({ typed: 'tse #gcli-input', cursor: { start: 15, end: 15 } }); + test.is( 'VVVVVVVVVVVVVVV', statuses); + test.is(Status.VALID, status); + test.is('tse', requ.commandAssignment.value.name); + test.is('#gcli-input', assign1.arg.text); + test.is(inputElement, assign1.value); + } + else { + test.log('Skipping test that assumes gcli on the web'); + } + } + + update({ typed: 'tse #gcli-nomatch', cursor: { start: 17, end: 17 } }); + // This is somewhat debatable because this input can't be corrected simply + // by typing so it's and error rather than incomplete, however without + // digging into the CSS engine we can't tell that so we default to incomplete + test.is( 'VVVVIIIIIIIIIIIII', statuses); + test.is(Status.ERROR, status); + test.is('tse', requ.commandAssignment.value.name); + test.is('#gcli-nomatch', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tse #', cursor: { start: 5, end: 5 } }); + test.is( 'VVVVE', statuses); + test.is(Status.ERROR, status); + test.is('tse', requ.commandAssignment.value.name); + test.is('#', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tse .', cursor: { start: 5, end: 5 } }); + test.is( 'VVVVE', statuses); + test.is(Status.ERROR, status); + test.is('tse', requ.commandAssignment.value.name); + test.is('.', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tse *', cursor: { start: 5, end: 5 } }); + test.is( 'VVVVE', statuses); + test.is(Status.ERROR, status); + test.is('tse', requ.commandAssignment.value.name); + test.is('*', assign1.arg.text); + test.is(undefined, assign1.value); +}; + +exports.testNestedCommand = function() { + update({ typed: 'tsn', cursor: { start: 3, end: 3 } }); + test.is( 'III', statuses); + test.is(Status.ERROR, status); + test.is('tsn', requ.commandAssignment.arg.text); + test.is(undefined, assign1); + + update({ typed: 'tsn ', cursor: { start: 4, end: 4 } }); + test.is( 'IIIV', statuses); + test.is(Status.ERROR, status); + test.is('tsn', requ.commandAssignment.arg.text); + test.is(undefined, assign1); + + update({ typed: 'tsn x', cursor: { start: 5, end: 5 } }); + // Commented out while we try out fuzzy matching + // test.is( 'EEEVE', statuses); + test.is(Status.ERROR, status); + test.is('tsn x', requ.commandAssignment.arg.text); + test.is(undefined, assign1); + + update({ typed: 'tsn dif', cursor: { start: 7, end: 7 } }); + test.is( 'VVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsn dif', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsn dif ', cursor: { start: 8, end: 8 } }); + test.is( 'VVVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsn dif', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsn dif x', cursor: { start: 9, end: 9 } }); + test.is( 'VVVVVVVVV', statuses); + test.is(Status.VALID, status); + test.is('tsn dif', requ.commandAssignment.value.name); + test.is('x', assign1.arg.text); + test.is('x', assign1.value); + + update({ typed: 'tsn ext', cursor: { start: 7, end: 7 } }); + test.is( 'VVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsn ext', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsn exte', cursor: { start: 8, end: 8 } }); + test.is( 'VVVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsn exte', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsn exten', cursor: { start: 9, end: 9 } }); + test.is( 'VVVVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsn exten', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'tsn extend', cursor: { start: 10, end: 10 } }); + test.is( 'VVVVVVVVVV', statuses); + test.is(Status.ERROR, status); + test.is('tsn extend', requ.commandAssignment.value.name); + test.is('', assign1.arg.text); + test.is(undefined, assign1.value); + + update({ typed: 'ts ', cursor: { start: 3, end: 3 } }); + test.is( 'EEV', statuses); + test.is(Status.ERROR, status); + test.is('ts', requ.commandAssignment.arg.text); + test.is(undefined, assign1); +}; + +// From Bug 664203 +exports.testDeeplyNested = function() { + update({ typed: 'tsn deep down nested cmd', cursor: { start: 24, end: 24 } }); + test.is( 'VVVVVVVVVVVVVVVVVVVVVVVV', statuses); + test.is(Status.VALID, status); + test.is('tsn deep down nested cmd', requ.commandAssignment.value.name); + test.is(undefined, assign1); + + update({ typed: 'tsn deep down nested', cursor: { start: 20, end: 20 } }); + test.is( 'IIIVIIIIVIIIIVIIIIII', statuses); + test.is(Status.ERROR, status); + test.is('tsn deep down nested', requ.commandAssignment.value.name); + test.is(undefined, assign1); +}; + + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/commands', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/util', 'gcli/types/selection', 'gcli/types/basic', 'gcli/types'], function(require, exports, module) { +var commands = exports; + + +var canon = require('gcli/canon'); +var util = require('gcli/util'); + +var SelectionType = require('gcli/types/selection').SelectionType; +var DeferredType = require('gcli/types/basic').DeferredType; +var types = require('gcli/types'); + +/** + * Registration and de-registration. + */ +commands.setup = function() { + // setup/shutdown need to register/unregister types, however that means we + // need to re-initialize commands.option1 and commands.option2 with the + // actual types + commands.option1.type = types.getType('string'); + commands.option2.type = types.getType('number'); + + types.registerType(commands.optionType); + types.registerType(commands.optionValue); + + canon.addCommand(commands.tsv); + canon.addCommand(commands.tsr); + canon.addCommand(commands.tse); + canon.addCommand(commands.tsj); + canon.addCommand(commands.tsb); + canon.addCommand(commands.tss); + canon.addCommand(commands.tsu); + canon.addCommand(commands.tsn); + canon.addCommand(commands.tsnDif); + canon.addCommand(commands.tsnExt); + canon.addCommand(commands.tsnExte); + canon.addCommand(commands.tsnExten); + canon.addCommand(commands.tsnExtend); + canon.addCommand(commands.tsnDeep); + canon.addCommand(commands.tsnDeepDown); + canon.addCommand(commands.tsnDeepDownNested); + canon.addCommand(commands.tsnDeepDownNestedCmd); + canon.addCommand(commands.tselarr); + canon.addCommand(commands.tsm); + canon.addCommand(commands.tsg); +}; + +commands.shutdown = function() { + canon.removeCommand(commands.tsv); + canon.removeCommand(commands.tsr); + canon.removeCommand(commands.tse); + canon.removeCommand(commands.tsj); + canon.removeCommand(commands.tsb); + canon.removeCommand(commands.tss); + canon.removeCommand(commands.tsu); + canon.removeCommand(commands.tsn); + canon.removeCommand(commands.tsnDif); + canon.removeCommand(commands.tsnExt); + canon.removeCommand(commands.tsnExte); + canon.removeCommand(commands.tsnExten); + canon.removeCommand(commands.tsnExtend); + canon.removeCommand(commands.tsnDeep); + canon.removeCommand(commands.tsnDeepDown); + canon.removeCommand(commands.tsnDeepDownNested); + canon.removeCommand(commands.tsnDeepDownNestedCmd); + canon.removeCommand(commands.tselarr); + canon.removeCommand(commands.tsm); + canon.removeCommand(commands.tsg); + + types.deregisterType(commands.optionType); + types.deregisterType(commands.optionValue); +}; + + +commands.option1 = { type: types.getType('string') }; +commands.option2 = { type: types.getType('number') }; + +var lastOption = undefined; + +commands.optionType = new SelectionType({ + name: 'optionType', + lookup: [ + { name: 'option1', value: commands.option1 }, + { name: 'option2', value: commands.option2 } + ], + noMatch: function() { + lastOption = undefined; + }, + stringify: function(option) { + lastOption = option; + return SelectionType.prototype.stringify.call(this, option); + }, + parse: function(arg) { + var conversion = SelectionType.prototype.parse.call(this, arg); + lastOption = conversion.value; + return conversion; + } +}); + +commands.optionValue = new DeferredType({ + name: 'optionValue', + defer: function() { + if (lastOption && lastOption.type) { + return lastOption.type; + } + else { + return types.getType('blank'); + } + } +}); + +commands.onCommandExec = util.createEvent('commands.onCommandExec'); + +function createExec(name) { + return function(args, context) { + var data = { + command: commands[name], + args: args, + context: context + }; + commands.onCommandExec(data); + return data; + }; +} + +commands.tsv = { + name: 'tsv', + params: [ + { name: 'optionType', type: 'optionType' }, + { name: 'optionValue', type: 'optionValue' } + ], + exec: createExec('tsv') +}; + +commands.tsr = { + name: 'tsr', + params: [ { name: 'text', type: 'string' } ], + exec: createExec('tsr') +}; + +commands.tse = { + name: 'tse', + params: [ { name: 'node', type: 'node' } ], + exec: createExec('tse') +}; + +commands.tsj = { + name: 'tsj', + params: [ { name: 'javascript', type: 'javascript' } ], + exec: createExec('tsj') +}; + +commands.tsb = { + name: 'tsb', + params: [ { name: 'toggle', type: 'boolean' } ], + exec: createExec('tsb') +}; + +commands.tss = { + name: 'tss', + exec: createExec('tss') +}; + +commands.tsu = { + name: 'tsu', + params: [ { name: 'num', type: { name: 'number', max: 10, min: -5, step: 3 } } ], + exec: createExec('tsu') +}; + +commands.tsn = { + name: 'tsn' +}; + +commands.tsnDif = { + name: 'tsn dif', + params: [ { name: 'text', type: 'string' } ], + exec: createExec('tsnDif') +}; + +commands.tsnExt = { + name: 'tsn ext', + params: [ { name: 'text', type: 'string' } ], + exec: createExec('tsnExt') +}; + +commands.tsnExte = { + name: 'tsn exte', + params: [ { name: 'text', type: 'string' } ], + exec: createExec('') +}; + +commands.tsnExten = { + name: 'tsn exten', + params: [ { name: 'text', type: 'string' } ], + exec: createExec('tsnExte') +}; + +commands.tsnExtend = { + name: 'tsn extend', + params: [ { name: 'text', type: 'string' } ], + exec: createExec('tsnExtend') +}; + +commands.tsnDeep = { + name: 'tsn deep', +}; + +commands.tsnDeepDown = { + name: 'tsn deep down', +}; + +commands.tsnDeepDownNested = { + name: 'tsn deep down nested', +}; + +commands.tsnDeepDownNestedCmd = { + name: 'tsn deep down nested cmd', + exec: createExec('tsnDeepDownNestedCmd') +}; + +commands.tselarr = { + name: 'tselarr', + params: [ + { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } }, + { name: 'arr', type: { name: 'array', subtype: 'string' } }, + ], + exec: createExec('tselarr') +}; + +commands.tsm = { + name: 'tsm', + description: 'a 3-param test selection|string|number', + params: [ + { name: 'abc', type: { name: 'selection', data: [ 'a', 'b', 'c' ] } }, + { name: 'txt', type: 'string' }, + { name: 'num', type: { name: 'number', max: 42, min: 0 } }, + ], + exec: createExec('tsm') +}; + +commands.tsg = { + name: 'tsg', + description: 'a param group test', + params: [ + { name: 'solo', type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] } }, + { + group: 'First', + params: [ + { name: 'txt1', type: 'string', defaultValue: null }, + { name: 'bool', type: 'boolean' } + ] + }, + { + group: 'Second', + params: [ + { name: 'txt2', type: 'string', defaultValue: 'd' }, + { name: 'num', type: { name: 'number', min: 40 }, defaultValue: 42 } + ] + } + ], + exec: createExec('tsg') +}; + + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('test/assert', ['require', 'exports', 'module' ], function(require, exports, module) { + + exports.ok = ok; + exports.is = is; + exports.log = info; + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testCompletion', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands'], function(require, exports, module) { + + +var test = require('test/assert'); +var commands = require('gclitest/commands'); + + +exports.setup = function() { + commands.setup(); +}; + +exports.shutdown = function() { + commands.shutdown(); +}; + + +function type(typed, tests, options) { + var inputter = options.display.inputter; + var completer = options.display.completer; + + inputter.setInput(typed); + + if (tests.cursor) { + inputter.setCursor({ start: tests.cursor, end: tests.cursor }); + } + + if (tests.emptyParameters == null) { + tests.emptyParameters = []; + } + + var realParams = completer.emptyParameters; + test.is(tests.emptyParameters.length, realParams.length, + 'emptyParameters.length for \'' + typed + '\''); + + if (realParams.length === tests.emptyParameters.length) { + for (var i = 0; i < realParams.length; i++) { + test.is(tests.emptyParameters[i], realParams[i].replace(/\u00a0/g, ' '), + 'emptyParameters[' + i + '] for \'' + typed + '\''); + } + } + + if (tests.directTabText) { + test.is(tests.directTabText, completer.directTabText, + 'directTabText for \'' + typed + '\''); + } + else { + test.is('', completer.directTabText, + 'directTabText for \'' + typed + '\''); + } + + if (tests.arrowTabText) { + test.is(' \u00a0\u21E5 ' + tests.arrowTabText, + completer.arrowTabText, + 'arrowTabText for \'' + typed + '\''); + } + else { + test.is('', completer.arrowTabText, + 'arrowTabText for \'' + typed + '\''); + } +} + +exports.testActivate = function(options) { + if (!options.display) { + test.log('No display. Skipping activate tests'); + return; + } + + type('', { }, options); + + type(' ', { }, options); + + type('tsr', { + emptyParameters: [ ' <text>' ] + }, options); + + type('tsr ', { + emptyParameters: [ '<text>' ] + }, options); + + type('tsr b', { }, options); + + type('tsb', { + emptyParameters: [ ' [toggle]' ] + }, options); + + type('tsm', { + emptyParameters: [ ' <abc>', ' <txt>', ' <num>' ] + }, options); + + type('tsm ', { + emptyParameters: [ ' <txt>', ' <num>' ], + directTabText: 'a' + }, options); + + type('tsm a', { + emptyParameters: [ ' <txt>', ' <num>' ] + }, options); + + type('tsm a ', { + emptyParameters: [ '<txt>', ' <num>' ] + }, options); + + type('tsm a ', { + emptyParameters: [ '<txt>', ' <num>' ] + }, options); + + type('tsm a d', { + emptyParameters: [ ' <num>' ] + }, options); + + type('tsm a "d d"', { + emptyParameters: [ ' <num>' ] + }, options); + + type('tsm a "d ', { + emptyParameters: [ ' <num>' ] + }, options); + + type('tsm a "d d" ', { + emptyParameters: [ '<num>' ] + }, options); + + type('tsm a "d d ', { + emptyParameters: [ ' <num>' ] + }, options); + + type('tsm d r', { + emptyParameters: [ ' <num>' ] + }, options); + + type('tsm a d ', { + emptyParameters: [ '<num>' ] + }, options); + + type('tsm a d 4', { }, options); + + type('tsg', { + emptyParameters: [ ' <solo>' ] + }, options); + + type('tsg ', { + directTabText: 'aaa' + }, options); + + type('tsg a', { + directTabText: 'aa' + }, options); + + type('tsg b', { + directTabText: 'bb' + }, options); + + type('tsg d', { }, options); + + type('tsg aa', { + directTabText: 'a' + }, options); + + type('tsg aaa', { }, options); + + type('tsg aaa ', { }, options); + + type('tsg aaa d', { }, options); + + type('tsg aaa dddddd', { }, options); + + type('tsg aaa dddddd ', { }, options); + + type('tsg aaa "d', { }, options); + + type('tsg aaa "d d', { }, options); + + type('tsg aaa "d d"', { }, options); + + type('tsn ex ', { }, options); + + type('selarr', { + arrowTabText: 'tselarr' + }, options); + + type('tselar 1', { }, options); + + type('tselar 1', { + cursor: 7 + }, options); + + type('tselar 1', { + cursor: 6, + arrowTabText: 'tselarr' + }, options); + + type('tselar 1', { + cursor: 5, + arrowTabText: 'tselarr' + }, options); +}; + + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testExec', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/canon', 'gclitest/commands', 'gcli/types/node', 'test/assert'], function(require, exports, module) { + + +var Requisition = require('gcli/cli').Requisition; +var canon = require('gcli/canon'); +var commands = require('gclitest/commands'); +var nodetype = require('gcli/types/node'); + +var test = require('test/assert'); + +var actualExec; +var actualOutput; +var hideExec = false; + +exports.setup = function() { + commands.setup(); + commands.onCommandExec.add(commandExeced); + canon.commandOutputManager.onOutput.add(commandOutputed); +}; + +exports.shutdown = function() { + commands.shutdown(); + commands.onCommandExec.remove(commandExeced); + canon.commandOutputManager.onOutput.remove(commandOutputed); +}; + +function commandExeced(ev) { + actualExec = ev; +} + +function commandOutputed(ev) { + actualOutput = ev.output; +} + +function exec(command, expectedArgs) { + var environment = {}; + + var requisition = new Requisition(environment); + var outputObject = requisition.exec({ typed: command, hidden: hideExec }); + + test.is(command.indexOf(actualExec.command.name), 0, 'Command name: ' + command); + + test.is(command, outputObject.typed, 'outputObject.command for: ' + command); + test.ok(outputObject.completed, 'outputObject.completed false for: ' + command); + + if (expectedArgs == null) { + test.ok(false, 'expectedArgs == null for ' + command); + return; + } + if (actualExec.args == null) { + test.ok(false, 'actualExec.args == null for ' + command); + return; + } + + test.is(Object.keys(expectedArgs).length, Object.keys(actualExec.args).length, + 'Arg count: ' + command); + Object.keys(expectedArgs).forEach(function(arg) { + var expectedArg = expectedArgs[arg]; + var actualArg = actualExec.args[arg]; + + if (Array.isArray(expectedArg)) { + if (!Array.isArray(actualArg)) { + test.ok(false, 'actual is not an array. ' + command + '/' + arg); + return; + } + + test.is(expectedArg.length, actualArg.length, + 'Array length: ' + command + '/' + arg); + for (var i = 0; i < expectedArg.length; i++) { + test.is(expectedArg[i], actualArg[i], + 'Member: "' + command + '/' + arg + '/' + i); + } + } + else { + test.is(expectedArg, actualArg, 'Command: "' + command + '" arg: ' + arg); + } + }); + + test.is(environment, actualExec.context.environment, 'Environment'); + + if (!hideExec) { + test.is(false, actualOutput.error, 'output error is false'); + test.is(command, actualOutput.typed, 'command is typed'); + test.ok(typeof actualOutput.canonical === 'string', 'canonical exists'); + + test.is(actualExec.args, actualOutput.args, 'actualExec.args is actualOutput.args'); + } +} + + +exports.testExec = function(options) { + hideExec = options.hideExec; + + exec('tss', {}); + + // Bug 707008 - GCLI deferred types don't work properly + exec('tsv option1 10', { optionType: commands.option1, optionValue: '10' }); + exec('tsv option2 10', { optionType: commands.option2, optionValue: 10 }); + + exec('tsr fred', { text: 'fred' }); + exec('tsr fred bloggs', { text: 'fred bloggs' }); + exec('tsr "fred bloggs"', { text: 'fred bloggs' }); + + exec('tsb', { toggle: false }); + exec('tsb --toggle', { toggle: true }); + + exec('tsu 10', { num: 10 }); + exec('tsu --num 10', { num: 10 }); + + // Bug 704829 - Enable GCLI Javascript parameters + // The answer to this should be 2 + exec('tsj { 1 + 1 }', { javascript: '1 + 1' }); + + var origDoc = nodetype.getDocument(); + nodetype.setDocument(mockDoc); + exec('tse :root', { node: mockBody }); + nodetype.setDocument(origDoc); + + exec('tsn dif fred', { text: 'fred' }); + exec('tsn exten fred', { text: 'fred' }); + exec('tsn extend fred', { text: 'fred' }); + + exec('tselarr 1', { num: '1', arr: [ ] }); + exec('tselarr 1 a', { num: '1', arr: [ 'a' ] }); + exec('tselarr 1 a b', { num: '1', arr: [ 'a', 'b' ] }); + + exec('tsm a 10 10', { abc: 'a', txt: '10', num: 10 }); + + // Bug 707009 - GCLI doesn't always fill in default parameters properly + exec('tsg aaa', { solo: 'aaa', txt1: null, bool: false, txt2: 'd', num: 42 }); +}; + +var mockBody = { + style: {} +}; + +var mockDoc = { + querySelectorAll: function(css) { + if (css === ':root') { + return { + length: 1, + item: function(i) { + return mockBody; + } + }; + } + throw new Error('mockDoc.querySelectorAll(\'' + css + '\') error'); + } +}; + + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testHistory', ['require', 'exports', 'module' , 'test/assert', 'gcli/history'], function(require, exports, module) { + +var test = require('test/assert'); +var History = require('gcli/history').History; + +exports.setup = function() { +}; + +exports.shutdown = function() { +}; + +exports.testSimpleHistory = function () { + var history = new History({}); + history.add('foo'); + history.add('bar'); + test.is('bar', history.backward()); + test.is('foo', history.backward()); + + // Adding to the history again moves us back to the start of the history. + history.add('quux'); + test.is('quux', history.backward()); + test.is('bar', history.backward()); + test.is('foo', history.backward()); +}; + +exports.testBackwardsPastIndex = function () { + var history = new History({}); + history.add('foo'); + history.add('bar'); + test.is('bar', history.backward()); + test.is('foo', history.backward()); + + // Moving backwards past recorded history just keeps giving you the last + // item. + test.is('foo', history.backward()); +}; + +exports.testForwardsPastIndex = function () { + var history = new History({}); + history.add('foo'); + history.add('bar'); + test.is('bar', history.backward()); + test.is('foo', history.backward()); + + // Going forward through the history again. + test.is('bar', history.forward()); + + // 'Present' time. + test.is('', history.forward()); + + // Going to the 'future' just keeps giving us the empty string. + test.is('', history.forward()); +}; + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testJs', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/types/javascript', 'gcli/canon', 'test/assert'], function(require, exports, module) { + + +var Requisition = require('gcli/cli').Requisition; +var Status = require('gcli/types').Status; +var javascript = require('gcli/types/javascript'); +var canon = require('gcli/canon'); + +var test = require('test/assert'); + +var debug = false; +var requ; + +var assign; +var status; +var statuses; +var tempWindow; + + +exports.setup = function(options) { + tempWindow = javascript.getGlobalObject(); + javascript.setGlobalObject(options.window); + + Object.defineProperty(options.window, 'donteval', { + get: function() { + test.ok(false, 'donteval should not be used'); + return { cant: '', touch: '', 'this': '' }; + }, + enumerable: true, + configurable : true + }); +}; + +exports.shutdown = function(options) { + delete options.window.donteval; + + javascript.setGlobalObject(tempWindow); + tempWindow = undefined; +}; + +function input(typed) { + if (!requ) { + requ = new Requisition(); + } + var cursor = { start: typed.length, end: typed.length }; + requ.update(typed); + + if (debug) { + console.log('####### TEST: typed="' + typed + + '" cur=' + cursor.start + + ' cli=', requ); + } + + status = requ.getStatus(); + statuses = requ.getInputStatusMarkup(cursor.start).map(function(s) { + return Array(s.string.length + 1).join(s.status.toString()[0]); + }).join(''); + + if (requ.commandAssignment.value) { + assign = requ.getAssignment(0); + } + else { + assign = undefined; + } +} + +function predictionsHas(name) { + return assign.getPredictions().some(function(prediction) { + return name === prediction.name; + }, this); +} + +function check(expStatuses, expStatus, expAssign, expPredict) { + test.is('{', requ.commandAssignment.value.name, 'is exec'); + + test.is(expStatuses, statuses, 'unexpected status markup'); + test.is(expStatus.toString(), status.toString(), 'unexpected status'); + test.is(expAssign, assign.value, 'unexpected assignment'); + + if (expPredict != null) { + var contains; + if (Array.isArray(expPredict)) { + expPredict.forEach(function(p) { + contains = predictionsHas(p); + test.ok(contains, 'missing prediction ' + p); + }); + } + else if (typeof expPredict === 'number') { + contains = true; + test.is(assign.getPredictions().length, expPredict, 'prediction count'); + if (assign.getPredictions().length !== expPredict) { + assign.getPredictions().forEach(function(prediction) { + test.log('actual prediction: ', prediction); + }); + } + } + else { + contains = predictionsHas(expPredict); + test.ok(contains, 'missing prediction ' + expPredict); + } + + if (!contains) { + test.log('Predictions: ' + assign.getPredictions().map(function(p) { + return p.name; + }).join(', ')); + } + } +} + +exports.testBasic = function(options) { + if (!canon.getCommand('{')) { + test.log('Skipping exec tests because { is not registered'); + return; + } + + input('{'); + check('V', Status.ERROR, undefined); + + input('{ '); + check('VV', Status.ERROR, undefined); + + input('{ w'); + check('VVI', Status.ERROR, 'w', 'window'); + + input('{ windo'); + check('VVIIIII', Status.ERROR, 'windo', 'window'); + + input('{ window'); + check('VVVVVVVV', Status.VALID, 'window'); + + input('{ window.d'); + check('VVIIIIIIII', Status.ERROR, 'window.d', 'window.document'); + + input('{ window.document.title'); + check('VVVVVVVVVVVVVVVVVVVVVVV', Status.VALID, 'window.document.title', 0); + + input('{ d'); + check('VVI', Status.ERROR, 'd', 'document'); + + input('{ document.title'); + check('VVVVVVVVVVVVVVVV', Status.VALID, 'document.title', 0); + + test.ok('donteval' in options.window, 'donteval exists'); + + input('{ don'); + check('VVIII', Status.ERROR, 'don', 'donteval'); + + input('{ donteval'); + check('VVVVVVVVVV', Status.VALID, 'donteval', 0); + + /* + // This is a controversial test - technically we can tell that it's an error + // because 'donteval.' is a syntax error, however donteval is unsafe so we + // are playing safe by bailing out early. It's enough of a corner case that + // I don't think it warrants fixing + input('{ donteval.'); + check('VVIIIIIIIII', Status.ERROR, 'donteval.', 0); + */ + + input('{ donteval.cant'); + check('VVVVVVVVVVVVVVV', Status.VALID, 'donteval.cant', 0); + + input('{ donteval.xxx'); + check('VVVVVVVVVVVVVV', Status.VALID, 'donteval.xxx', 0); +}; + + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testKeyboard', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/canon', 'gclitest/commands', 'gcli/types/javascript', 'test/assert'], function(require, exports, module) { + + +var Requisition = require('gcli/cli').Requisition; +var canon = require('gcli/canon'); +var commands = require('gclitest/commands'); +var javascript = require('gcli/types/javascript'); + +var test = require('test/assert'); + +var tempWindow; +var inputter; + +exports.setup = function(options) { + tempWindow = javascript.getGlobalObject(); + javascript.setGlobalObject(options.window); + + if (options.display) { + inputter = options.display.inputter; + } + + commands.setup(); +}; + +exports.shutdown = function(options) { + commands.shutdown(); + + inputter = undefined; + javascript.setGlobalObject(tempWindow); + tempWindow = undefined; +}; + +var COMPLETES_TO = 'complete'; +var KEY_UPS_TO = 'keyup'; +var KEY_DOWNS_TO = 'keydown'; + +function check(initial, action, after, choice, cursor, expectedCursor) { + var requisition; + if (inputter) { + requisition = inputter.requisition; + inputter.setInput(initial); + } + else { + requisition = new Requisition(); + requisition.update(initial); + } + + if (cursor == null) { + cursor = initial.length; + } + var assignment = requisition.getAssignmentAt(cursor); + switch (action) { + case COMPLETES_TO: + requisition.complete({ start: cursor, end: cursor }, choice); + break; + + case KEY_UPS_TO: + assignment.increment(); + break; + + case KEY_DOWNS_TO: + assignment.decrement(); + break; + } + + test.is(after, requisition.toString(), + initial + ' + ' + action + ' -> ' + after); + + if (expectedCursor != null) { + if (inputter) { + test.is(expectedCursor, inputter.getInputState().cursor.start, + 'Ending cursor position for \'' + initial + '\''); + } + } +} + +exports.testComplete = function(options) { + if (!inputter) { + test.log('Missing display, reduced checks'); + } + + check('tsela', COMPLETES_TO, 'tselarr ', 0); + check('tsn di', COMPLETES_TO, 'tsn dif ', 0); + check('tsg a', COMPLETES_TO, 'tsg aaa ', 0); + + check('tsn e', COMPLETES_TO, 'tsn extend ', -5); + check('tsn e', COMPLETES_TO, 'tsn ext ', -4); + check('tsn e', COMPLETES_TO, 'tsn exte ', -3); + check('tsn e', COMPLETES_TO, 'tsn exten ', -2); + check('tsn e', COMPLETES_TO, 'tsn extend ', -1); + check('tsn e', COMPLETES_TO, 'tsn ext ', 0); + check('tsn e', COMPLETES_TO, 'tsn exte ', 1); + check('tsn e', COMPLETES_TO, 'tsn exten ', 2); + check('tsn e', COMPLETES_TO, 'tsn extend ', 3); + check('tsn e', COMPLETES_TO, 'tsn ext ', 4); + check('tsn e', COMPLETES_TO, 'tsn exte ', 5); + check('tsn e', COMPLETES_TO, 'tsn exten ', 6); + check('tsn e', COMPLETES_TO, 'tsn extend ', 7); + check('tsn e', COMPLETES_TO, 'tsn ext ', 8); + + if (!canon.getCommand('{')) { + test.log('Skipping exec tests because { is not registered'); + } + else { + check('{ wind', COMPLETES_TO, '{ window', 0); + check('{ window.docum', COMPLETES_TO, '{ window.document', 0); + + // Bug 717228: This fails under node + if (!options.isNode) { + check('{ window.document.titl', COMPLETES_TO, '{ window.document.title ', 0); + } + else { + test.log('Running under Node. Skipping tests due to bug 717228.'); + } + } +}; + +exports.testInternalComplete = function(options) { + // Bug 664377 + // check('tsela 1', COMPLETES_TO, 'tselarr 1', 0, 3, 8); +}; + +exports.testIncrDecr = function() { + check('tsu -70', KEY_UPS_TO, 'tsu -5'); + check('tsu -7', KEY_UPS_TO, 'tsu -5'); + check('tsu -6', KEY_UPS_TO, 'tsu -5'); + check('tsu -5', KEY_UPS_TO, 'tsu -3'); + check('tsu -4', KEY_UPS_TO, 'tsu -3'); + check('tsu -3', KEY_UPS_TO, 'tsu 0'); + check('tsu -2', KEY_UPS_TO, 'tsu 0'); + check('tsu -1', KEY_UPS_TO, 'tsu 0'); + check('tsu 0', KEY_UPS_TO, 'tsu 3'); + check('tsu 1', KEY_UPS_TO, 'tsu 3'); + check('tsu 2', KEY_UPS_TO, 'tsu 3'); + check('tsu 3', KEY_UPS_TO, 'tsu 6'); + check('tsu 4', KEY_UPS_TO, 'tsu 6'); + check('tsu 5', KEY_UPS_TO, 'tsu 6'); + check('tsu 6', KEY_UPS_TO, 'tsu 9'); + check('tsu 7', KEY_UPS_TO, 'tsu 9'); + check('tsu 8', KEY_UPS_TO, 'tsu 9'); + check('tsu 9', KEY_UPS_TO, 'tsu 10'); + check('tsu 10', KEY_UPS_TO, 'tsu 10'); + check('tsu 100', KEY_UPS_TO, 'tsu -5'); + + check('tsu -70', KEY_DOWNS_TO, 'tsu 10'); + check('tsu -7', KEY_DOWNS_TO, 'tsu 10'); + check('tsu -6', KEY_DOWNS_TO, 'tsu 10'); + check('tsu -5', KEY_DOWNS_TO, 'tsu -5'); + check('tsu -4', KEY_DOWNS_TO, 'tsu -5'); + check('tsu -3', KEY_DOWNS_TO, 'tsu -5'); + check('tsu -2', KEY_DOWNS_TO, 'tsu -3'); + check('tsu -1', KEY_DOWNS_TO, 'tsu -3'); + check('tsu 0', KEY_DOWNS_TO, 'tsu -3'); + check('tsu 1', KEY_DOWNS_TO, 'tsu 0'); + check('tsu 2', KEY_DOWNS_TO, 'tsu 0'); + check('tsu 3', KEY_DOWNS_TO, 'tsu 0'); + check('tsu 4', KEY_DOWNS_TO, 'tsu 3'); + check('tsu 5', KEY_DOWNS_TO, 'tsu 3'); + check('tsu 6', KEY_DOWNS_TO, 'tsu 3'); + check('tsu 7', KEY_DOWNS_TO, 'tsu 6'); + check('tsu 8', KEY_DOWNS_TO, 'tsu 6'); + check('tsu 9', KEY_DOWNS_TO, 'tsu 6'); + check('tsu 10', KEY_DOWNS_TO, 'tsu 9'); + check('tsu 100', KEY_DOWNS_TO, 'tsu 10'); + + // Bug 707007 - GCLI increment and decrement operations cycle through + // selection options in the wrong order + check('tselarr 1', KEY_DOWNS_TO, 'tselarr 2'); + check('tselarr 2', KEY_DOWNS_TO, 'tselarr 3'); + check('tselarr 3', KEY_DOWNS_TO, 'tselarr 1'); + + check('tselarr 3', KEY_UPS_TO, 'tselarr 2'); +}; + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testRequire', ['require', 'exports', 'module' , 'test/assert', 'gclitest/requirable'], function(require, exports, module) { + +var test = require('test/assert'); + + +exports.testWorking = function() { + // There are lots of requirement tests that we could be doing here + // The fact that we can get anything at all working is a testament to + // require doing what it should - we don't need to test the + var requireable = require('gclitest/requirable'); + test.is('thing1', requireable.thing1); + test.is(2, requireable.thing2); + test.ok(requireable.thing3 === undefined); +}; + +exports.testDomains = function() { + var requireable = require('gclitest/requirable'); + test.ok(requireable.status === undefined); + requireable.setStatus(null); + test.is(null, requireable.getStatus()); + test.ok(requireable.status === undefined); + requireable.setStatus('42'); + test.is('42', requireable.getStatus()); + test.ok(requireable.status === undefined); + + if (define.Domain) { + var domain = new define.Domain(); + var requireable2 = domain.require('gclitest/requirable'); + test.is(undefined, requireable2.status); + test.is('initial', requireable2.getStatus()); + requireable2.setStatus(999); + test.is(999, requireable2.getStatus()); + test.is(undefined, requireable2.status); + + test.is('42', requireable.getStatus()); + test.is(undefined, requireable.status); + } +}; + +exports.testLeakage = function() { + var requireable = require('gclitest/requirable'); + test.ok(requireable.setup === undefined); + test.ok(requireable.shutdown === undefined); + test.ok(requireable.testWorking === undefined); +}; + +exports.testMultiImport = function() { + var r1 = require('gclitest/requirable'); + var r2 = require('gclitest/requirable'); + test.is(r1, r2); +}; + +exports.testUncompilable = function() { + // This test is commented out because it breaks the RequireJS module + // loader and because it causes console output and because testing failure + // cases such as this is something of a luxury + // It's not totally clear how a module loader should perform with unusable + // modules, however at least it should go into a flat spin ... + // GCLI mini_require reports an error as it should + /* + if (define.Domain) { + try { + var unrequireable = require('gclitest/unrequirable'); + t.fail(); + } + catch (ex) { + console.error(ex); + } + } + */ +}; + +exports.testRecursive = function() { + // See Bug 658583 + /* + var recurse = require('gclitest/recurse'); + */ +}; + + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/requirable', ['require', 'exports', 'module' ], function(require, exports, module) { + + exports.thing1 = 'thing1'; + exports.thing2 = 2; + + var status = 'initial'; + exports.setStatus = function(aStatus) { status = aStatus; }; + exports.getStatus = function() { return status; }; + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testResource', ['require', 'exports', 'module' , 'gcli/types/resource', 'gcli/types', 'test/assert'], function(require, exports, module) { + + +var resource = require('gcli/types/resource'); +var types = require('gcli/types'); +var Status = require('gcli/types').Status; + +var test = require('test/assert'); + +var tempDocument; + +exports.setup = function(options) { + tempDocument = resource.getDocument(); + resource.setDocument(options.window.document); +}; + +exports.shutdown = function(options) { + resource.setDocument(tempDocument); + tempDocument = undefined; +}; + +exports.testPredictions = function(options) { + if (options.window.isFake) { + test.log('Skipping resource tests: options.window.isFake = true'); + return; + } + + var resource1 = types.getType('resource'); + var predictions1 = resource1.parseString('').getPredictions(); + test.ok(predictions1.length > 1, 'have resources'); + predictions1.forEach(function(prediction) { + checkPrediction(resource1, prediction); + }); + + var resource2 = types.getType({ name: 'resource', include: 'text/javascript' }); + var predictions2 = resource2.parseString('').getPredictions(); + test.ok(predictions2.length > 1, 'have resources'); + predictions2.forEach(function(prediction) { + checkPrediction(resource2, prediction); + }); + + var resource3 = types.getType({ name: 'resource', include: 'text/css' }); + var predictions3 = resource3.parseString('').getPredictions(); + // jsdom fails to support digging into stylesheets + if (!options.isNode) { + test.ok(predictions3.length >= 1, 'have resources'); + } + else { + test.log('Running under Node. ' + + 'Skipping checks due to jsdom document.stylsheets support.'); + } + predictions3.forEach(function(prediction) { + checkPrediction(resource3, prediction); + }); + + var resource4 = types.getType({ name: 'resource' }); + var predictions4 = resource4.parseString('').getPredictions(); + + test.is(predictions1.length, predictions4.length, 'type spec'); + // Bug 734045 + // test.is(predictions2.length + predictions3.length, predictions4.length, 'split'); +}; + +function checkPrediction(res, prediction) { + var name = prediction.name; + var value = prediction.value; + + var conversion = res.parseString(name); + test.is(conversion.getStatus(), Status.VALID, 'status VALID for ' + name); + test.is(conversion.value, value, 'value for ' + name); + + var strung = res.stringify(value); + test.is(strung, name, 'stringify for ' + name); + + test.is(typeof value.loadContents, 'function', 'resource for ' + name); + test.is(typeof value.element, 'object', 'resource for ' + name); +} + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testScratchpad', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) { + + +var test = require('test/assert'); + +var origScratchpad; + +exports.setup = function(options) { + if (options.display) { + origScratchpad = options.display.inputter.scratchpad; + options.display.inputter.scratchpad = stubScratchpad; + } +}; + +exports.shutdown = function(options) { + if (options.display) { + options.display.inputter.scratchpad = origScratchpad; + } +}; + +var stubScratchpad = { + shouldActivate: function(ev) { + return true; + }, + activatedCount: 0, + linkText: 'scratchpad.linkText' +}; +stubScratchpad.activate = function(value) { + stubScratchpad.activatedCount++; + return true; +}; + + +exports.testActivate = function(options) { + if (!options.display) { + test.log('No display. Skipping scratchpad tests'); + return; + } + + var ev = {}; + stubScratchpad.activatedCount = 0; + options.display.inputter.onKeyUp(ev); + test.is(1, stubScratchpad.activatedCount, 'scratchpad is activated'); +}; + + +}); +/* + * Copyright (c) 2009 Panagiotis Astithas + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +define('gclitest/testSpell', ['require', 'exports', 'module' , 'test/assert', 'gcli/types/spell'], function(require, exports, module) { + +var test = require('test/assert'); +var Speller = require('gcli/types/spell').Speller; + +exports.setup = function() { +}; + +exports.shutdown = function() { +}; + +exports.testSimple = function(options) { + var speller = new Speller(); + speller.train(Object.keys(options.window)); + + test.is(speller.correct('document'), 'document'); + test.is(speller.correct('documen'), 'document'); + test.is(speller.correct('ocument'), 'document'); + test.is(speller.correct('odcument'), 'document'); + + test.is(speller.correct('========='), null); +}; + + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gclitest/testSplit', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands', 'gcli/cli'], function(require, exports, module) { + +var test = require('test/assert'); + +var commands = require('gclitest/commands'); +var Requisition = require('gcli/cli').Requisition; + +exports.setup = function() { + commands.setup(); +}; + +exports.shutdown = function() { + commands.shutdown(); +}; + +exports.testSimple = function() { + var args; + var requ = new Requisition(); + + args = requ._tokenize('s'); + requ._split(args); + test.is(0, args.length); + test.is('s', requ.commandAssignment.arg.text); +}; + +exports.testFlatCommand = function() { + var args; + var requ = new Requisition(); + + args = requ._tokenize('tsv'); + requ._split(args); + test.is(0, args.length); + test.is('tsv', requ.commandAssignment.value.name); + + args = requ._tokenize('tsv a b'); + requ._split(args); + test.is('tsv', requ.commandAssignment.value.name); + test.is(2, args.length); + test.is('a', args[0].text); + test.is('b', args[1].text); +}; + +exports.testJavascript = function() { + var args; + var requ = new Requisition(); + + args = requ._tokenize('{'); + requ._split(args); + test.is(1, args.length); + test.is('', args[0].text); + test.is('', requ.commandAssignment.arg.text); + test.is('{', requ.commandAssignment.value.name); +}; + +// BUG 663081 - add tests for sub commands + +}); +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('gclitest/testTokenize', ['require', 'exports', 'module' , 'test/assert', 'gcli/cli', 'gcli/argument'], function(require, exports, module) { var test = require('test/assert'); var Requisition = require('gcli/cli').Requisition; var Argument = require('gcli/argument').Argument; var ScriptArgument = require('gcli/argument').ScriptArgument; @@ -694,1526 +2716,224 @@ exports.testPathological = function() { }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -define('test/assert', ['require', 'exports', 'module' ], function(require, exports, module) { - - exports.ok = ok; - exports.is = is; - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testSplit', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands', 'gcli/cli'], function(require, exports, module) { +define('gclitest/testTooltip', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands'], function(require, exports, module) { + var test = require('test/assert'); - var commands = require('gclitest/commands'); -var Requisition = require('gcli/cli').Requisition; + exports.setup = function() { commands.setup(); }; exports.shutdown = function() { commands.shutdown(); }; -exports.testSimple = function() { - var args; - var requ = new Requisition(); - - args = requ._tokenize('s'); - requ._split(args); - test.is(0, args.length); - test.is('s', requ.commandAssignment.getArg().text); -}; - -exports.testFlatCommand = function() { - var args; - var requ = new Requisition(); - - args = requ._tokenize('tsv'); - requ._split(args); - test.is(0, args.length); - test.is('tsv', requ.commandAssignment.getValue().name); - - args = requ._tokenize('tsv a b'); - requ._split(args); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is(2, args.length); - test.is('a', args[0].text); - test.is('b', args[1].text); -}; - -exports.testJavascript = function() { - var args; - var requ = new Requisition(); - - args = requ._tokenize('{'); - requ._split(args); - test.is(1, args.length); - test.is('', args[0].text); - test.is('', requ.commandAssignment.getArg().text); - test.is('{', requ.commandAssignment.getValue().name); -}; - -// BUG 663081 - add tests for sub commands - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/commands', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/util', 'gcli/types/basic', 'gcli/types'], function(require, exports, module) { -var commands = exports; - - -var canon = require('gcli/canon'); -var util = require('gcli/util'); - -var SelectionType = require('gcli/types/basic').SelectionType; -var DeferredType = require('gcli/types/basic').DeferredType; -var types = require('gcli/types'); - -/** - * Registration and de-registration. - */ -commands.setup = function() { - commands.option1.type = types.getType('number'); - commands.option2.type = types.getType('boolean'); - - types.registerType(commands.optionType); - types.registerType(commands.optionValue); - - canon.addCommand(commands.tsv); - canon.addCommand(commands.tsr); - canon.addCommand(commands.tse); - canon.addCommand(commands.tsj); - canon.addCommand(commands.tsb); - canon.addCommand(commands.tss); - canon.addCommand(commands.tsu); - canon.addCommand(commands.tsn); - canon.addCommand(commands.tsnDif); - canon.addCommand(commands.tsnExt); - canon.addCommand(commands.tsnExte); - canon.addCommand(commands.tsnExten); - canon.addCommand(commands.tsnExtend); - canon.addCommand(commands.tselarr); - canon.addCommand(commands.tsm); - canon.addCommand(commands.tsg); -}; - -commands.shutdown = function() { - canon.removeCommand(commands.tsv); - canon.removeCommand(commands.tsr); - canon.removeCommand(commands.tse); - canon.removeCommand(commands.tsj); - canon.removeCommand(commands.tsb); - canon.removeCommand(commands.tss); - canon.removeCommand(commands.tsu); - canon.removeCommand(commands.tsn); - canon.removeCommand(commands.tsnDif); - canon.removeCommand(commands.tsnExt); - canon.removeCommand(commands.tsnExte); - canon.removeCommand(commands.tsnExten); - canon.removeCommand(commands.tsnExtend); - canon.removeCommand(commands.tselarr); - canon.removeCommand(commands.tsm); - canon.removeCommand(commands.tsg); - - types.deregisterType(commands.optionType); - types.deregisterType(commands.optionValue); -}; - - -commands.option1 = { type: types.getType('string') }; -commands.option2 = { type: types.getType('number') }; - -commands.optionType = new SelectionType({ - name: 'optionType', - lookup: [ - { name: 'option1', value: commands.option1 }, - { name: 'option2', value: commands.option2 } - ], - noMatch: function() { - this.lastOption = null; - }, - stringify: function(option) { - this.lastOption = option; - return SelectionType.prototype.stringify.call(this, option); - }, - parse: function(arg) { - var conversion = SelectionType.prototype.parse.call(this, arg); - this.lastOption = conversion.value; - return conversion; + +function type(typed, tests, options) { + var inputter = options.display.inputter; + var tooltip = options.display.tooltip; + + inputter.setInput(typed); + if (tests.cursor) { + inputter.setCursor({ start: tests.cursor, end: tests.cursor }); } -}); - -commands.optionValue = new DeferredType({ - name: 'optionValue', - defer: function() { - if (commands.optionType.lastOption) { - return commands.optionType.lastOption.type; + + if (!options.isNode) { + if (tests.important) { + test.ok(tooltip.field.isImportant, 'Important for ' + typed); } else { - return types.getType('blank'); + test.ok(!tooltip.field.isImportant, 'Not important for ' + typed); + } + + if (tests.options) { + var names = tooltip.field.menu.items.map(function(item) { + return item.name.textContent ? item.name.textContent : item.name; + }); + test.is(tests.options.join('|'), names.join('|'), 'Options for ' + typed); + } + + if (tests.error) { + test.is(tests.error, tooltip.errorEle.textContent, 'Error for ' + typed); + } + else { + test.is('', tooltip.errorEle.textContent, 'No error for ' + typed); } } -}); - -commands.commandExec = util.createEvent('commands.commandExec'); - -function createExec(name) { - return function(args, context) { - var data = { - command: commands[name], - args: args, - context: context - }; - commands.commandExec(data); - return data; - }; } -commands.tsv = { - name: 'tsv', - params: [ - { name: 'optionType', type: 'optionType' }, - { name: 'optionValue', type: 'optionValue' } - ], - exec: createExec('tsv') -}; - -commands.tsr = { - name: 'tsr', - params: [ { name: 'text', type: 'string' } ], - exec: createExec('tsr') -}; - -commands.tse = { - name: 'tse', - params: [ { name: 'node', type: 'node' } ], - exec: createExec('tse') -}; - -commands.tsj = { - name: 'tsj', - params: [ { name: 'javascript', type: 'javascript' } ], - exec: createExec('tsj') -}; - -commands.tsb = { - name: 'tsb', - params: [ { name: 'toggle', type: 'boolean' } ], - exec: createExec('tsb') -}; - -commands.tss = { - name: 'tss', - exec: createExec('tss') -}; - -commands.tsu = { - name: 'tsu', - params: [ { name: 'num', type: { name: 'number', max: 10, min: -5, step: 3 } } ], - exec: createExec('tsu') -}; - -commands.tsn = { - name: 'tsn' -}; - -commands.tsnDif = { - name: 'tsn dif', - params: [ { name: 'text', type: 'string' } ], - exec: createExec('tsnDif') -}; - -commands.tsnExt = { - name: 'tsn ext', - params: [ { name: 'text', type: 'string' } ], - exec: createExec('tsnExt') -}; - -commands.tsnExte = { - name: 'tsn exte', - params: [ { name: 'text', type: 'string' } ], - exec: createExec('') -}; - -commands.tsnExten = { - name: 'tsn exten', - params: [ { name: 'text', type: 'string' } ], - exec: createExec('tsnExte') -}; - -commands.tsnExtend = { - name: 'tsn extend', - params: [ { name: 'text', type: 'string' } ], - exec: createExec('tsnExtend') -}; - -commands.tselarr = { - name: 'tselarr', - params: [ - { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } }, - { name: 'arr', type: { name: 'array', subtype: 'string' } }, - ], - exec: createExec('tselarr') -}; - -commands.tsm = { - name: 'tsm', - hidden: true, - description: 'a 3-param test selection|string|number', - params: [ - { name: 'abc', type: { name: 'selection', data: [ 'a', 'b', 'c' ] } }, - { name: 'txt', type: 'string' }, - { name: 'num', type: { name: 'number', max: 42, min: 0 } }, - ], - exec: createExec('tsm') -}; - -commands.tsg = { - name: 'tsg', - hidden: true, - description: 'a param group test', - params: [ - { name: 'solo', type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] } }, - { - group: 'First', - params: [ - { name: 'txt1', type: 'string', defaultValue: null }, - { name: 'boolean1', type: 'boolean' } - ] - }, - { - group: 'Second', - params: [ - { name: 'txt2', type: 'string', defaultValue: 'd' }, - { name: 'num2', type: { name: 'number', defaultValue: 42 } } - ] - } - ], - exec: createExec('tsg') -}; - - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testCli', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gclitest/commands', 'test/assert'], function(require, exports, module) { - - -var Requisition = require('gcli/cli').Requisition; -var Status = require('gcli/types').Status; -var commands = require('gclitest/commands'); - -var test = require('test/assert'); - -exports.setup = function() { - commands.setup(); -}; - -exports.shutdown = function() { - commands.shutdown(); -}; - - -var assign1; -var assign2; -var assignC; -var requ; -var debug = false; -var status; -var statuses; - -function update(input) { - if (!requ) { - requ = new Requisition(); +exports.testActivate = function(options) { + if (!options.display) { + test.log('No display. Skipping activate tests'); + return; } - requ.update(input); - - if (debug) { - console.log('####### TEST: typed="' + input.typed + - '" cur=' + input.cursor.start + - ' cli=', requ); - } - - status = requ.getStatus(); - assignC = requ.getAssignmentAt(input.cursor.start); - statuses = requ.getInputStatusMarkup(input.cursor.start).map(function(s) { - return Array(s.string.length + 1).join(s.status.toString()[0]); - }).join(''); - - if (requ.commandAssignment.getValue()) { - assign1 = requ.getAssignment(0); - assign2 = requ.getAssignment(1); - } - else { - assign1 = undefined; - assign2 = undefined; + + if (options.isNode) { + test.log('Running under Node. Reduced checks due to JSDom.textContent'); } -} - -function verifyPredictionsContains(name, predictions) { - return predictions.every(function(prediction) { - return name === prediction.name; - }, this); -} - - -exports.testBlank = function() { - update({ typed: '', cursor: { start: 0, end: 0 } }); - test.is( '', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.is(null, requ.commandAssignment.getValue()); - - update({ typed: ' ', cursor: { start: 1, end: 1 } }); - test.is( 'V', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.is(null, requ.commandAssignment.getValue()); - - update({ typed: ' ', cursor: { start: 0, end: 0 } }); - test.is( 'V', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.is(null, requ.commandAssignment.getValue()); -}; - -exports.testIncompleteMultiMatch = function() { - update({ typed: 't', cursor: { start: 1, end: 1 } }); - test.is( 'I', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.ok(assignC.getPredictions().length > 0); - verifyPredictionsContains('tsv', assignC.getPredictions()); - verifyPredictionsContains('tsr', assignC.getPredictions()); - test.is(null, requ.commandAssignment.getValue()); -}; - -exports.testIncompleteSingleMatch = function() { - update({ typed: 'tselar', cursor: { start: 6, end: 6 } }); - test.is( 'IIIIII', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.is(1, assignC.getPredictions().length); - test.is('tselarr', assignC.getPredictions()[0].name); - test.is(null, requ.commandAssignment.getValue()); -}; - -exports.testTsv = function() { - update({ typed: 'tsv', cursor: { start: 3, end: 3 } }); - test.is( 'VVV', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.is('tsv', requ.commandAssignment.getValue().name); - - update({ typed: 'tsv ', cursor: { start: 4, end: 4 } }); - test.is( 'VVVV', statuses); - test.is(Status.ERROR, status); - test.is(0, assignC.paramIndex); - test.is('tsv', requ.commandAssignment.getValue().name); - - update({ typed: 'tsv ', cursor: { start: 2, end: 2 } }); - test.is( 'VVVV', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.is('tsv', requ.commandAssignment.getValue().name); - - update({ typed: 'tsv o', cursor: { start: 5, end: 5 } }); - test.is( 'VVVVI', statuses); - test.is(Status.ERROR, status); - test.is(0, assignC.paramIndex); - test.is(2, assignC.getPredictions().length); - test.is(commands.option1, assignC.getPredictions()[0].value); - test.is(commands.option2, assignC.getPredictions()[1].value); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('o', assign1.getArg().text); - test.is(null, assign1.getValue()); - - update({ typed: 'tsv option', cursor: { start: 10, end: 10 } }); - test.is( 'VVVVIIIIII', statuses); - test.is(Status.ERROR, status); - test.is(0, assignC.paramIndex); - test.is(2, assignC.getPredictions().length); - test.is(commands.option1, assignC.getPredictions()[0].value); - test.is(commands.option2, assignC.getPredictions()[1].value); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('option', assign1.getArg().text); - test.is(null, assign1.getValue()); - - update({ typed: 'tsv option', cursor: { start: 1, end: 1 } }); - test.is( 'VVVVEEEEEE', statuses); - test.is(Status.ERROR, status); - test.is(-1, assignC.paramIndex); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('option', assign1.getArg().text); - test.is(null, assign1.getValue()); - - update({ typed: 'tsv option ', cursor: { start: 11, end: 11 } }); - test.is( 'VVVVEEEEEEV', statuses); - test.is(Status.ERROR, status); - test.is(1, assignC.paramIndex); - test.is(0, assignC.getPredictions().length); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('option', assign1.getArg().text); - test.is(null, assign1.getValue()); - - update({ typed: 'tsv option1', cursor: { start: 11, end: 11 } }); - test.is( 'VVVVVVVVVVV', statuses); - test.is(Status.ERROR, status); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('option1', assign1.getArg().text); - test.is(commands.option1, assign1.getValue()); - test.is(0, assignC.paramIndex); - - update({ typed: 'tsv option1 ', cursor: { start: 12, end: 12 } }); - test.is( 'VVVVVVVVVVVV', statuses); - test.is(Status.ERROR, status); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('option1', assign1.getArg().text); - test.is(commands.option1, assign1.getValue()); - test.is(1, assignC.paramIndex); - - update({ typed: 'tsv option1 6', cursor: { start: 13, end: 13 } }); - test.is( 'VVVVVVVVVVVVV', statuses); - test.is(Status.VALID, status); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('option1', assign1.getArg().text); - test.is(commands.option1, assign1.getValue()); - test.is('6', assign2.getArg().text); - test.is(6, assign2.getValue()); - test.is('number', typeof assign2.getValue()); - test.is(1, assignC.paramIndex); - - update({ typed: 'tsv option2 6', cursor: { start: 13, end: 13 } }); - test.is( 'VVVVVVVVVVVVE', statuses); - test.is(Status.ERROR, status); - test.is('tsv', requ.commandAssignment.getValue().name); - test.is('option2', assign1.getArg().text); - test.is(commands.option2, assign1.getValue()); - test.is('6', assign2.getArg().text); - test.is(null, assign2.getValue()); - test.is(1, assignC.paramIndex); -}; - -exports.testInvalid = function() { - update({ typed: 'fred', cursor: { start: 4, end: 4 } }); - test.is( 'EEEE', statuses); - test.is('fred', requ.commandAssignment.getArg().text); - test.is('', requ._unassigned.getArg().text); - test.is(-1, assignC.paramIndex); - - update({ typed: 'fred ', cursor: { start: 5, end: 5 } }); - test.is( 'EEEEV', statuses); - test.is('fred', requ.commandAssignment.getArg().text); - test.is('', requ._unassigned.getArg().text); - test.is(-1, assignC.paramIndex); - - update({ typed: 'fred one', cursor: { start: 8, end: 8 } }); - test.is( 'EEEEVEEE', statuses); - test.is('fred', requ.commandAssignment.getArg().text); - test.is('one', requ._unassigned.getArg().text); -}; - -exports.testSingleString = function() { - update({ typed: 'tsr', cursor: { start: 3, end: 3 } }); - test.is( 'VVV', statuses); - test.is(Status.ERROR, status); - test.is('tsr', requ.commandAssignment.getValue().name); - //test.is(undefined, assign1.getArg()); - //test.is(undefined, assign1.getValue()); - test.is(undefined, assign2); - - update({ typed: 'tsr ', cursor: { start: 4, end: 4 } }); - test.is( 'VVVV', statuses); - test.is(Status.ERROR, status); - test.is('tsr', requ.commandAssignment.getValue().name); - //test.is(undefined, assign1.getArg()); - //test.is(undefined, assign1.getValue()); - test.is(undefined, assign2); - - update({ typed: 'tsr h', cursor: { start: 5, end: 5 } }); - test.is( 'VVVVV', statuses); - test.is(Status.VALID, status); - test.is('tsr', requ.commandAssignment.getValue().name); - test.is('h', assign1.getArg().text); - test.is('h', assign1.getValue()); - - update({ typed: 'tsr "h h"', cursor: { start: 9, end: 9 } }); - test.is( 'VVVVVVVVV', statuses); - test.is(Status.VALID, status); - test.is('tsr', requ.commandAssignment.getValue().name); - test.is('h h', assign1.getArg().text); - test.is('h h', assign1.getValue()); - - update({ typed: 'tsr h h h', cursor: { start: 9, end: 9 } }); - test.is( 'VVVVVVVVV', statuses); - test.is('tsr', requ.commandAssignment.getValue().name); - test.is('h h h', assign1.getArg().text); - test.is('h h h', assign1.getValue()); -}; - -// BUG 664203: Add test to see that a command without mandatory param -> ERROR - -exports.testSingleNumber = function() { - update({ typed: 'tsu', cursor: { start: 3, end: 3 } }); - test.is( 'VVV', statuses); - test.is(Status.ERROR, status); - test.is('tsu', requ.commandAssignment.getValue().name); - //test.is(undefined, assign1.getArg()); - test.is(null, assign1.getValue()); - - update({ typed: 'tsu ', cursor: { start: 4, end: 4 } }); - test.is( 'VVVV', statuses); - test.is(Status.ERROR, status); - test.is('tsu', requ.commandAssignment.getValue().name); - //test.is(undefined, assign1.getArg()); - test.is(null, assign1.getValue()); - - update({ typed: 'tsu 1', cursor: { start: 5, end: 5 } }); - test.is( 'VVVVV', statuses); - test.is(Status.VALID, status); - test.is('tsu', requ.commandAssignment.getValue().name); - test.is('1', assign1.getArg().text); - test.is(1, assign1.getValue()); - test.is('number', typeof assign1.getValue()); - - update({ typed: 'tsu x', cursor: { start: 5, end: 5 } }); - test.is( 'VVVVE', statuses); - test.is(Status.ERROR, status); - test.is('tsu', requ.commandAssignment.getValue().name); - test.is('x', assign1.getArg().text); - test.is(null, assign1.getValue()); -}; - -exports.testNestedCommand = function() { - update({ typed: 'tsn', cursor: { start: 3, end: 3 } }); - test.is( 'III', statuses); - test.is(Status.ERROR, status); - test.is('tsn', requ.commandAssignment.getValue().name); - test.is(undefined, assign1); - - update({ typed: 'tsn ', cursor: { start: 4, end: 4 } }); - test.is( 'IIIV', statuses); - test.is(Status.ERROR, status); - test.is('tsn', requ.commandAssignment.getValue().name); - test.is(undefined, assign1); - - update({ typed: 'tsn x', cursor: { start: 5, end: 5 } }); - test.is( 'EEEVE', statuses); - test.is(Status.ERROR, status); - test.is('tsn x', requ.commandAssignment.getArg().text); - test.is(undefined, assign1); - - update({ typed: 'tsn dif', cursor: { start: 7, end: 7 } }); - test.is( 'VVVVVVV', statuses); - test.is(Status.ERROR, status); - test.is('tsn dif', requ.commandAssignment.getValue().name); - //test.is(undefined, assign1.getArg()); - //test.is(undefined, assign1.getValue()); - - update({ typed: 'tsn dif ', cursor: { start: 8, end: 8 } }); - test.is( 'VVVVVVVV', statuses); - test.is(Status.ERROR, status); - test.is('tsn dif', requ.commandAssignment.getValue().name); - //test.is(undefined, assign1.getArg()); - //test.is(undefined, assign1.getValue()); - - update({ typed: 'tsn dif x', cursor: { start: 9, end: 9 } }); - test.is( 'VVVVVVVVV', statuses); - test.is(Status.VALID, status); - test.is('tsn dif', requ.commandAssignment.getValue().name); - test.is('x', assign1.getArg().text); - test.is('x', assign1.getValue()); - - update({ typed: 'tsn ext', cursor: { start: 7, end: 7 } }); - test.is( 'VVVVVVV', statuses); - test.is(Status.ERROR, status); - test.is('tsn ext', requ.commandAssignment.getValue().name); - //test.is(undefined, assign1.getArg()); - //test.is(undefined, assign1.getValue()); + + type(' ', { }, options); + + type('tsb ', { + important: true, + options: [ 'false', 'true' ] + }, options); + + type('tsb t', { + important: true, + options: [ 'true' ] + }, options); + + type('tsb tt', { + important: true, + options: [ ], + error: 'Can\'t use \'tt\'.' + }, options); + + + type('asdf', { + important: false, + options: [ ], + error: 'Can\'t use \'asdf\'.' + }, options); + + type('', { }, options); }; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -define('gclitest/testExec', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/canon', 'gclitest/commands', 'gcli/types/node', 'test/assert'], function(require, exports, module) { - - -var Requisition = require('gcli/cli').Requisition; -var Status = require('gcli/types').Status; -var canon = require('gcli/canon'); -var commands = require('gclitest/commands'); -var nodetype = require('gcli/types/node'); - -var test = require('test/assert'); - -var actualExec; -var actualOutput; - -exports.setup = function() { - commands.setup(); - commands.commandExec.add(onCommandExec); - canon.commandOutputManager.addListener(onCommandOutput); -}; - -exports.shutdown = function() { - commands.shutdown(); - commands.commandExec.remove(onCommandExec); - canon.commandOutputManager.removeListener(onCommandOutput); -}; - -function onCommandExec(ev) { - actualExec = ev; -} - -function onCommandOutput(ev) { - actualOutput = ev.output; -} - -function exec(command, expectedArgs) { - var environment = {}; - - var requisition = new Requisition(environment); - var reply = requisition.exec({ typed: command }); - - test.is(command.indexOf(actualExec.command.name), 0, 'Command name: ' + command); - - if (reply !== true) { - test.ok(false, 'reply = false for command: ' + command); - } - - if (expectedArgs == null) { - test.ok(false, 'expectedArgs == null for ' + command); - return; - } - if (actualExec.args == null) { - test.ok(false, 'actualExec.args == null for ' + command); - return; - } - - test.is(Object.keys(expectedArgs).length, Object.keys(actualExec.args).length, - 'Arg count: ' + command); - Object.keys(expectedArgs).forEach(function(arg) { - var expectedArg = expectedArgs[arg]; - var actualArg = actualExec.args[arg]; - - if (Array.isArray(expectedArg)) { - if (!Array.isArray(actualArg)) { - test.ok(false, 'actual is not an array. ' + command + '/' + arg); - return; - } - - test.is(expectedArg.length, actualArg.length, - 'Array length: ' + command + '/' + arg); - for (var i = 0; i < expectedArg.length; i++) { - test.is(expectedArg[i], actualArg[i], - 'Member: "' + command + '/' + arg + '/' + i); - } - } - else { - test.is(expectedArg, actualArg, 'Command: "' + command + '" arg: ' + arg); - } - }); - - test.is(environment, actualExec.context.environment, 'Environment'); - - test.is(false, actualOutput.error, 'output error is false'); - test.is(command, actualOutput.typed, 'command is typed'); - test.ok(typeof actualOutput.canonical === 'string', 'canonical exists'); - - test.is(actualExec.args, actualOutput.args, 'actualExec.args is actualOutput.args'); -} - - -exports.testExec = function() { - exec('tss', {}); - - // Bug 707008 - GCLI defered types don't work properly - // exec('tsv option1 10', { optionType: commands.option1, optionValue: '10' }); - // exec('tsv option2 10', { optionType: commands.option1, optionValue: 10 }); - - exec('tsr fred', { text: 'fred' }); - exec('tsr fred bloggs', { text: 'fred bloggs' }); - exec('tsr "fred bloggs"', { text: 'fred bloggs' }); - - exec('tsb', { toggle: false }); - exec('tsb --toggle', { toggle: true }); - - exec('tsu 10', { num: 10 }); - exec('tsu --num 10', { num: 10 }); - - // Bug 704829 - Enable GCLI Javascript parameters - // The answer to this should be 2 - exec('tsj { 1 + 1 }', { javascript: '1 + 1' }); - - var origDoc = nodetype.getDocument(); - nodetype.setDocument(mockDoc); - exec('tse :root', { node: mockBody }); - nodetype.setDocument(origDoc); - - exec('tsn dif fred', { text: 'fred' }); - exec('tsn exten fred', { text: 'fred' }); - exec('tsn extend fred', { text: 'fred' }); - - exec('tselarr 1', { num: '1', arr: [ ] }); - exec('tselarr 1 a', { num: '1', arr: [ 'a' ] }); - exec('tselarr 1 a b', { num: '1', arr: [ 'a', 'b' ] }); - - exec('tsm a 10 10', { abc: 'a', txt: '10', num: 10 }); - - // Bug 707009 - GCLI doesn't always fill in default parameters properly - // exec('tsg a', { solo: 'a', txt1: null, boolean1: false, txt2: 'd', num2: 42 }); -}; - -var mockBody = { - style: {} -}; - -var mockDoc = { - querySelectorAll: function(css) { - if (css === ':root') { - return { - length: 1, - item: function(i) { - return mockBody; - } - }; - } - throw new Error('mockDoc.querySelectorAll(\'' + css + '\') error'); - } -}; - - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testKeyboard', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/canon', 'gclitest/commands', 'gcli/types/node', 'gcli/types/javascript', 'test/assert'], function(require, exports, module) { - - -var Requisition = require('gcli/cli').Requisition; -var Status = require('gcli/types').Status; -var canon = require('gcli/canon'); -var commands = require('gclitest/commands'); -var nodetype = require('gcli/types/node'); -var javascript = require('gcli/types/javascript'); +define('gclitest/testTypes', ['require', 'exports', 'module' , 'test/assert', 'gcli/types'], function(require, exports, module) { var test = require('test/assert'); - -var tempWindow; - -exports.setup = function(options) { - tempWindow = javascript.getGlobalObject(); - javascript.setGlobalObject(options.window); - - commands.setup(); -}; - -exports.shutdown = function(options) { - commands.shutdown(); - - javascript.setGlobalObject(tempWindow); - tempWindow = undefined; -}; - -var COMPLETES_TO = 'complete'; -var KEY_UPS_TO = 'keyup'; -var KEY_DOWNS_TO = 'keydown'; - -function check(initial, action, after) { - var requisition = new Requisition(); - requisition.update({ - typed: initial, - cursor: { start: initial.length, end: initial.length } - }); - var assignment = requisition.getAssignmentAt(initial.length); - switch (action) { - case COMPLETES_TO: - assignment.complete(); - break; - - case KEY_UPS_TO: - assignment.increment(); - break; - - case KEY_DOWNS_TO: - assignment.decrement(); - break; - } - - test.is(after, requisition.toString(), initial + ' + ' + action + ' -> ' + after); -} - -exports.testComplete = function(options) { - check('tsela', COMPLETES_TO, 'tselarr '); - check('tsn di', COMPLETES_TO, 'tsn dif '); - check('tsg a', COMPLETES_TO, 'tsg aaa '); - - check('{ wind', COMPLETES_TO, '{ window'); - check('{ window.docum', COMPLETES_TO, '{ window.document'); - - // Bug 717228: This fails under node - if (!options.isNode) { - check('{ window.document.titl', COMPLETES_TO, '{ window.document.title '); - } -}; - -exports.testIncrDecr = function() { - check('tsu -70', KEY_UPS_TO, 'tsu -5'); - check('tsu -7', KEY_UPS_TO, 'tsu -5'); - check('tsu -6', KEY_UPS_TO, 'tsu -5'); - check('tsu -5', KEY_UPS_TO, 'tsu -3'); - check('tsu -4', KEY_UPS_TO, 'tsu -3'); - check('tsu -3', KEY_UPS_TO, 'tsu 0'); - check('tsu -2', KEY_UPS_TO, 'tsu 0'); - check('tsu -1', KEY_UPS_TO, 'tsu 0'); - check('tsu 0', KEY_UPS_TO, 'tsu 3'); - check('tsu 1', KEY_UPS_TO, 'tsu 3'); - check('tsu 2', KEY_UPS_TO, 'tsu 3'); - check('tsu 3', KEY_UPS_TO, 'tsu 6'); - check('tsu 4', KEY_UPS_TO, 'tsu 6'); - check('tsu 5', KEY_UPS_TO, 'tsu 6'); - check('tsu 6', KEY_UPS_TO, 'tsu 9'); - check('tsu 7', KEY_UPS_TO, 'tsu 9'); - check('tsu 8', KEY_UPS_TO, 'tsu 9'); - check('tsu 9', KEY_UPS_TO, 'tsu 10'); - check('tsu 10', KEY_UPS_TO, 'tsu 10'); - check('tsu 100', KEY_UPS_TO, 'tsu -5'); - - check('tsu -70', KEY_DOWNS_TO, 'tsu 10'); - check('tsu -7', KEY_DOWNS_TO, 'tsu 10'); - check('tsu -6', KEY_DOWNS_TO, 'tsu 10'); - check('tsu -5', KEY_DOWNS_TO, 'tsu -5'); - check('tsu -4', KEY_DOWNS_TO, 'tsu -5'); - check('tsu -3', KEY_DOWNS_TO, 'tsu -5'); - check('tsu -2', KEY_DOWNS_TO, 'tsu -3'); - check('tsu -1', KEY_DOWNS_TO, 'tsu -3'); - check('tsu 0', KEY_DOWNS_TO, 'tsu -3'); - check('tsu 1', KEY_DOWNS_TO, 'tsu 0'); - check('tsu 2', KEY_DOWNS_TO, 'tsu 0'); - check('tsu 3', KEY_DOWNS_TO, 'tsu 0'); - check('tsu 4', KEY_DOWNS_TO, 'tsu 3'); - check('tsu 5', KEY_DOWNS_TO, 'tsu 3'); - check('tsu 6', KEY_DOWNS_TO, 'tsu 3'); - check('tsu 7', KEY_DOWNS_TO, 'tsu 6'); - check('tsu 8', KEY_DOWNS_TO, 'tsu 6'); - check('tsu 9', KEY_DOWNS_TO, 'tsu 6'); - check('tsu 10', KEY_DOWNS_TO, 'tsu 9'); - check('tsu 100', KEY_DOWNS_TO, 'tsu 10'); - - // Bug 707007 - GCLI increment and decrement operations cycle through - // selection options in the wrong order - check('tselarr 1', KEY_DOWNS_TO, 'tselarr 2'); - check('tselarr 2', KEY_DOWNS_TO, 'tselarr 3'); - check('tselarr 3', KEY_DOWNS_TO, 'tselarr 1'); - - check('tselarr 3', KEY_UPS_TO, 'tselarr 2'); -}; - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testScratchpad', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) { - - -var test = require('test/assert'); - -var origScratchpad; - -exports.setup = function(options) { - if (options.inputter) { - origScratchpad = options.inputter.scratchpad; - options.inputter.scratchpad = stubScratchpad; - } -}; - -exports.shutdown = function(options) { - if (options.inputter) { - options.inputter.scratchpad = origScratchpad; - } -}; - -var stubScratchpad = { - shouldActivate: function(ev) { - return true; - }, - activatedCount: 0, - linkText: 'scratchpad.linkText' -}; -stubScratchpad.activate = function(value) { - stubScratchpad.activatedCount++; - return true; -}; - - -exports.testActivate = function(options) { - if (!options.inputter) { - console.log('No inputter. Skipping scratchpad tests'); - return; - } - - var ev = {}; - stubScratchpad.activatedCount = 0; - options.inputter.onKeyUp(ev); - test.is(1, stubScratchpad.activatedCount, 'scratchpad is activated'); -}; - - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testHistory', ['require', 'exports', 'module' , 'test/assert', 'gcli/history'], function(require, exports, module) { - -var test = require('test/assert'); -var History = require('gcli/history').History; +var types = require('gcli/types'); exports.setup = function() { }; exports.shutdown = function() { }; -exports.testSimpleHistory = function () { - var history = new History({}); - history.add('foo'); - history.add('bar'); - test.is('bar', history.backward()); - test.is('foo', history.backward()); - - // Adding to the history again moves us back to the start of the history. - history.add('quux'); - test.is('quux', history.backward()); - test.is('bar', history.backward()); - test.is('foo', history.backward()); -}; - -exports.testBackwardsPastIndex = function () { - var history = new History({}); - history.add('foo'); - history.add('bar'); - test.is('bar', history.backward()); - test.is('foo', history.backward()); - - // Moving backwards past recorded history just keeps giving you the last - // item. - test.is('foo', history.backward()); -}; - -exports.testForwardsPastIndex = function () { - var history = new History({}); - history.add('foo'); - history.add('bar'); - test.is('bar', history.backward()); - test.is('foo', history.backward()); - - // Going forward through the history again. - test.is('bar', history.forward()); - - // 'Present' time. - test.is('', history.forward()); - - // Going to the 'future' just keeps giving us the empty string. - test.is('', history.forward()); -}; - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testRequire', ['require', 'exports', 'module' , 'test/assert', 'gclitest/requirable'], function(require, exports, module) { - -var test = require('test/assert'); - - -exports.testWorking = function() { - // There are lots of requirement tests that we could be doing here - // The fact that we can get anything at all working is a testament to - // require doing what it should - we don't need to test the - var requireable = require('gclitest/requirable'); - test.is('thing1', requireable.thing1); - test.is(2, requireable.thing2); - test.is(undefined, requireable.thing3); -}; - -exports.testDomains = function() { - var requireable = require('gclitest/requirable'); - test.is(undefined, requireable.status); - requireable.setStatus(null); - test.is(null, requireable.getStatus()); - test.is(undefined, requireable.status); - requireable.setStatus('42'); - test.is('42', requireable.getStatus()); - test.is(undefined, requireable.status); - - if (define.Domain) { - var domain = new define.Domain(); - var requireable2 = domain.require('gclitest/requirable'); - test.is(undefined, requireable2.status); - test.is('initial', requireable2.getStatus()); - requireable2.setStatus(999); - test.is(999, requireable2.getStatus()); - test.is(undefined, requireable2.status); - - test.is('42', requireable.getStatus()); - test.is(undefined, requireable.status); - } -}; - -exports.testLeakage = function() { - var requireable = require('gclitest/requirable'); - test.is(undefined, requireable.setup); - test.is(undefined, requireable.shutdown); - test.is(undefined, requireable.testWorking); -}; - -exports.testMultiImport = function() { - var r1 = require('gclitest/requirable'); - var r2 = require('gclitest/requirable'); - test.is(r1, r2); -}; - -exports.testUncompilable = function() { - // This test is commented out because it breaks the RequireJS module - // loader and because it causes console output and because testing failure - // cases such as this is something of a luxury - // It's not totally clear how a module loader should perform with unusable - // modules, however at least it should go into a flat spin ... - // GCLI mini_require reports an error as it should - /* - if (define.Domain) { - try { - var unrequireable = require('gclitest/unrequirable'); - t.fail(); - } - catch (ex) { - console.error(ex); - } - } - */ -}; - -exports.testRecursive = function() { - // See Bug 658583 - /* - var recurse = require('gclitest/recurse'); - */ -}; - - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/requirable', ['require', 'exports', 'module' ], function(require, exports, module) { - - exports.thing1 = 'thing1'; - exports.thing2 = 2; - - var status = 'initial'; - exports.setStatus = function(aStatus) { status = aStatus; }; - exports.getStatus = function() { return status; }; - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testResource', ['require', 'exports', 'module' , 'gcli/types/resource', 'gcli/types', 'test/assert'], function(require, exports, module) { - - -var resource = require('gcli/types/resource'); -var types = require('gcli/types'); -var Status = require('gcli/types').Status; - -var test = require('test/assert'); - -var tempDocument; - -exports.setup = function(options) { - tempDocument = resource.getDocument(); - resource.setDocument(options.window.document); -}; - -exports.shutdown = function(options) { - resource.setDocument(tempDocument); - tempDocument = undefined; -}; - -exports.testPredictions = function(options) { - if (options.useFakeWindow) { - console.log('Skipping resource tests: options.useFakeWindow = true'); +exports.testDefault = function(options) { + if (options.isNode) { + test.log('Running under Node. ' + + 'Skipping tests due to issues with resource type.'); return; } - var resource1 = types.getType('resource'); - var predictions1 = resource1.parseString('').getPredictions(); - test.ok(predictions1.length > 1, 'have resources'); - predictions1.forEach(function(prediction) { - checkPrediction(resource1, prediction); - }); - - var resource2 = types.getType({ name: 'resource', include: 'text/javascript' }); - var predictions2 = resource2.parseString('').getPredictions(); - test.ok(predictions2.length > 1, 'have resources'); - predictions2.forEach(function(prediction) { - checkPrediction(resource2, prediction); - }); - - var resource3 = types.getType({ name: 'resource', include: 'text/css' }); - var predictions3 = resource3.parseString('').getPredictions(); - // jsdom fails to support digging into stylesheets - if (!options.isNode) { - test.ok(predictions3.length > 1, 'have resources'); - } - predictions3.forEach(function(prediction) { - checkPrediction(resource3, prediction); - }); - - var resource4 = types.getType({ name: 'resource' }); - var predictions4 = resource4.parseString('').getPredictions(); - - test.is(predictions1.length, predictions4.length, 'type spec'); - test.is(predictions2.length + predictions3.length, predictions4.length, 'split'); -}; - -function checkPrediction(res, prediction) { - var name = prediction.name; - var value = prediction.value; - - var conversion = res.parseString(name); - test.is(conversion.getStatus(), Status.VALID, 'status VALID for ' + name); - test.is(conversion.value, value, 'value for ' + name); - - var strung = res.stringify(value); - test.is(strung, name, 'stringify for ' + name); - - test.is(typeof value.loadContents, 'function', 'resource for ' + name); - test.is(typeof value.element, 'object', 'resource for ' + name); -} - -}); -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -define('gclitest/testJs', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/types/javascript', 'test/assert'], function(require, exports, module) { - - -var Requisition = require('gcli/cli').Requisition; -var Status = require('gcli/types').Status; -var javascript = require('gcli/types/javascript'); - -var test = require('test/assert'); - -var debug = false; -var requ; - -var assign; -var status; -var statuses; -var tempWindow; - - -exports.setup = function(options) { - tempWindow = javascript.getGlobalObject(); - javascript.setGlobalObject(options.window); - - Object.defineProperty(options.window, 'donteval', { - get: function() { - test.ok(false, 'donteval should not be used'); - return { cant: '', touch: '', 'this': '' }; - }, - enumerable: true, - configurable : true + types.getTypeNames().forEach(function(name) { + if (name === 'selection') { + name = { name: 'selection', data: [ 'a', 'b' ] }; + } + if (name === 'deferred') { + name = { + name: 'deferred', + defer: function() { return types.getType('string'); } + }; + } + if (name === 'array') { + name = { name: 'array', subtype: 'string' }; + } + var type = types.getType(name); + if (type.name !== 'boolean' && type.name !== 'array') { + test.ok(type.getBlank().value === undefined, + 'default defined for ' + type.name); + } }); }; -exports.shutdown = function(options) { - delete options.window.donteval; - - javascript.setGlobalObject(tempWindow); - tempWindow = undefined; -}; - -function input(typed) { - if (!requ) { - requ = new Requisition(); - } - var cursor = { start: typed.length, end: typed.length }; - var input = { typed: typed, cursor: cursor }; - requ.update(input); - - if (debug) { - console.log('####### TEST: typed="' + typed + - '" cur=' + cursor.start + - ' cli=', requ); - } - - status = requ.getStatus(); - statuses = requ.getInputStatusMarkup(input.cursor.start).map(function(s) { - return Array(s.string.length + 1).join(s.status.toString()[0]); - }).join(''); - - if (requ.commandAssignment.getValue()) { - assign = requ.getAssignment(0); - } - else { - assign = undefined; - } -} - -function predictionsHas(name) { - return assign.getPredictions().some(function(prediction) { - return name === prediction.name; - }, this); -} - -function check(expStatuses, expStatus, expAssign, expPredict) { - test.is('{', requ.commandAssignment.getValue().name, 'is exec'); - - test.is(expStatuses, statuses, 'unexpected status markup'); - test.is(expStatus.toString(), status.toString(), 'unexpected status'); - test.is(expAssign, assign.getValue(), 'unexpected assignment'); - - if (expPredict != null) { - var contains; - if (Array.isArray(expPredict)) { - expPredict.forEach(function(p) { - contains = predictionsHas(p); - test.ok(contains, 'missing prediction ' + p); - }); - } - else if (typeof expPredict === 'number') { - contains = true; - test.is(assign.getPredictions().length, expPredict, 'prediction count'); - if (assign.getPredictions().length !== expPredict) { - assign.getPredictions().forEach(function(prediction) { - console.log('actual prediction: ', prediction); - }); - } - } - else { - contains = predictionsHas(expPredict); - test.ok(contains, 'missing prediction ' + expPredict); - } - - if (!contains) { - console.log('Predictions: ' + assign.getPredictions().map(function(p) { - return p.name; - }).join(', ')); - } - } -} - -exports.testBasic = function(options) { - input('{'); - check('V', Status.ERROR, ''); - - input('{ '); - check('VV', Status.ERROR, ''); - - input('{ w'); - check('VVI', Status.ERROR, 'w', 'window'); - - input('{ windo'); - check('VVIIIII', Status.ERROR, 'windo', 'window'); - - input('{ window'); - check('VVVVVVVV', Status.VALID, 'window'); - - input('{ window.d'); - check('VVIIIIIIII', Status.ERROR, 'window.d', 'window.document'); - - input('{ window.document.title'); - check('VVVVVVVVVVVVVVVVVVVVVVV', Status.VALID, 'window.document.title', 0); - - input('{ d'); - check('VVI', Status.ERROR, 'd', 'document'); - - input('{ document.title'); - check('VVVVVVVVVVVVVVVV', Status.VALID, 'document.title', 0); - - test.ok('donteval' in options.window, 'donteval exists'); - - input('{ don'); - check('VVIII', Status.ERROR, 'don', 'donteval'); - - input('{ donteval'); - check('VVVVVVVVVV', Status.VALID, 'donteval', 0); - - /* - // This is a controversial test - technically we can tell that it's an error - // because 'donteval.' is a syntax error, however donteval is unsafe so we - // are playing safe by bailing out early. It's enough of a corner case that - // I don't think it warrants fixing - input('{ donteval.'); - check('VVIIIIIIIII', Status.ERROR, 'donteval.', 0); - */ - - input('{ donteval.cant'); - check('VVVVVVVVVVVVVVV', Status.VALID, 'donteval.cant', 0); - - input('{ donteval.xxx'); - check('VVVVVVVVVVVVVV', Status.VALID, 'donteval.xxx', 0); -}; - - }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gclitest/testUtil', ['require', 'exports', 'module' , 'gcli/util', 'test/assert'], function(require, exports, module) { var util = require('gcli/util'); var test = require('test/assert'); exports.testFindCssSelector = function(options) { - if (options.useFakeWindow) { - console.log('Skipping dom.findCssSelector tests due to useFakeWindow'); + if (options.window.isFake) { + test.log('Skipping dom.findCssSelector tests due to window.isFake'); return; } var nodes = options.window.document.querySelectorAll('*'); for (var i = 0; i < nodes.length; i++) { - var selector = util.dom.findCssSelector(nodes[i]); + var selector = util.findCssSelector(nodes[i]); var matches = options.window.document.querySelectorAll(selector); test.is(matches.length, 1, 'multiple matches for ' + selector); test.is(matches[0], nodes[i], 'non-matching selector: ' + selector); } }; }); -function undefine() { - delete define.modules['gclitest/index']; - delete define.modules['gclitest/suite']; - delete define.modules['test/examiner']; - delete define.modules['gclitest/testTokenize']; - delete define.modules['test/assert']; - delete define.modules['gclitest/testSplit']; - delete define.modules['gclitest/commands']; - delete define.modules['gclitest/testCli']; - delete define.modules['gclitest/testExec']; - delete define.modules['gclitest/testKeyboard']; - delete define.modules['gclitest/testScratchpad']; - delete define.modules['gclitest/testHistory']; - delete define.modules['gclitest/testRequire']; - delete define.modules['gclitest/requirable']; - delete define.modules['gclitest/testResource']; - delete define.modules['gclitest/testJs']; - delete define.modules['gclitest/testUtil']; - - delete define.globalDomain.modules['gclitest/index']; - delete define.globalDomain.modules['gclitest/suite']; - delete define.globalDomain.modules['test/examiner']; - delete define.globalDomain.modules['gclitest/testTokenize']; - delete define.globalDomain.modules['test/assert']; - delete define.globalDomain.modules['gclitest/testSplit']; - delete define.globalDomain.modules['gclitest/commands']; - delete define.globalDomain.modules['gclitest/testCli']; - delete define.globalDomain.modules['gclitest/testExec']; - delete define.globalDomain.modules['gclitest/testKeyboard']; - delete define.globalDomain.modules['gclitest/testScratchpad']; - delete define.globalDomain.modules['gclitest/testHistory']; - delete define.globalDomain.modules['gclitest/testRequire']; - delete define.globalDomain.modules['gclitest/requirable']; - delete define.globalDomain.modules['gclitest/testResource']; - delete define.globalDomain.modules['gclitest/testJs']; - delete define.globalDomain.modules['gclitest/testUtil']; +let testModuleNames = [ + 'gclitest/index', + 'gclitest/suite', + 'test/examiner', + 'gclitest/testCli', + 'gclitest/commands', + 'test/assert', + 'gclitest/testCompletion', + 'gclitest/testExec', + 'gclitest/testHistory', + 'gclitest/testJs', + 'gclitest/testKeyboard', + 'gclitest/testRequire', + 'gclitest/requirable', + 'gclitest/testResource', + 'gclitest/testScratchpad', + 'gclitest/testSpell', + 'gclitest/testSplit', + 'gclitest/testTokenize', + 'gclitest/testTooltip', + 'gclitest/testTypes', + 'gclitest/testUtil', +]; + +// Cached so it still exists during cleanup until we need it to +let localDefine; + +const TEST_URI = "data:text/html;charset=utf-8,gcli-web"; + +function test() { + localDefine = define; + + DeveloperToolbarTest.test(TEST_URI, function(browser, tab) { + var gclitest = define.globalDomain.require("gclitest/index"); + gclitest.run({ + display: DeveloperToolbar.display, + // window: browser.getBrowser().contentWindow + }); + + finish(); + }); } registerCleanupFunction(function() { - Services.prefs.clearUserPref("devtools.gcli.enable"); - undefine(); - obj = undefined; - define = undefined; - console = undefined; - Node = undefined; + testModuleNames.forEach(function(moduleName) { + delete localDefine.modules[moduleName]; + delete localDefine.globalDomain.modules[moduleName]; + }); + + localDefine = undefined; }); - -function test() { - Services.prefs.setBoolPref("devtools.gcli.enable", true); - addTab("http://example.com/browser/browser/devtools/webconsole/test/test-console.html"); - browser.addEventListener("DOMContentLoaded", onLoad, false); -} - -function onLoad() { - browser.removeEventListener("DOMContentLoaded", onLoad, false); - var failed = false; - - try { - openConsole(); - - var gcliterm = HUDService.getHudByWindow(content).gcliterm; - - var gclitest = define.globalDomain.require("gclitest/index"); - gclitest.run({ - window: gcliterm.document.defaultView, - inputter: gcliterm.opts.console.inputter, - requisition: gcliterm.opts.requistion - }); - } - catch (ex) { - failed = ex; - console.error("Test Failure", ex); - ok(false, "" + ex); - } - finally { - closeConsole(); - finish(); - } - - if (failed) { - throw failed; - } -}
--- a/browser/devtools/shared/test/browser_templater_basic.js +++ b/browser/devtools/shared/test/browser_templater_basic.js @@ -1,18 +1,22 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests that the DOM Template engine works properly -let tempScope = {}; -Cu.import("resource:///modules/devtools/Templater.jsm", tempScope); -Cu.import("resource:///modules/devtools/Promise.jsm", tempScope); -let template = tempScope.template; -let Promise = tempScope.Promise; +/* + * These tests run both in Mozilla/Mochitest and plain browsers (as does + * domtemplate) + * We should endevour to keep the source in sync. + */ + +var imports = {}; +Cu.import("resource:///modules/devtools/Templater.jsm", imports); +Cu.import("resource:///modules/devtools/Promise.jsm", imports); function test() { addTab("http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html", function() { info("Starting DOM Templater Tests"); runTest(0); }); } @@ -20,17 +24,17 @@ function runTest(index) { var options = tests[index] = tests[index](); var holder = content.document.createElement('div'); holder.id = options.name; var body = content.document.body; body.appendChild(holder); holder.innerHTML = options.template; info('Running ' + options.name); - template(holder, options.data, options.options); + imports.template(holder, options.data, options.options); if (typeof options.result == 'string') { is(holder.innerHTML, options.result, options.name); } else { ok(holder.innerHTML.match(options.result), options.name); } @@ -233,18 +237,50 @@ var tests = [ options: { allowEval: true }, result: '<p>2</p>' };}, function() { return { name: 'propertyFail', template: '<p>${Math.max(1, 2)}</p>', result: '<p>${Math.max(1, 2)}</p>' + };}, + + // Bug 723431: DOMTemplate should allow customisation of display of + // null/undefined values + function() { return { + name: 'propertyUndefAttrFull', + template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>', + data: { nullvar: null, undefinedvar1: undefined }, + result: '<p>null|undefined|undefined</p>' + };}, + + function() { return { + name: 'propertyUndefAttrBlank', + template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>', + data: { nullvar: null, undefinedvar1: undefined }, + options: { blankNullUndefined: true }, + result: '<p>||</p>' + };}, + + function() { return { + name: 'propertyUndefAttrFull', + template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>', + data: { nullvar: null, undefinedvar1: undefined }, + result: '<div><p value="null"></p><p value="undefined"></p><p value="undefined"></p></div>' + };}, + + function() { return { + name: 'propertyUndefAttrBlank', + template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>', + data: { nullvar: null, undefinedvar1: undefined }, + options: { blankNullUndefined: true }, + result: '<div><p value=""></p><p value=""></p><p value=""></p></div>' };} ]; function delayReply(data) { - var p = new Promise(); + var p = new imports.Promise(); executeSoon(function() { p.resolve(data); }); return p; }
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_toolbar_basic.html @@ -0,0 +1,35 @@ +<!doctype html> +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<html> +<head> + <meta charset="UTF-8"> + <title>Developer Toolbar Tests</title> + <style type="text/css"> + #single { color: red; } + </style> + <script type="text/javascript">var a=1;</script> +</head> +<body> + +<p id=single> +1 +</p> + +<p class=twin> +2a +</p> + +<p class=twin> +2b +</p> + +<style> +.twin { color: blue; } +</style> +<script>var b=2;</script> + +</body> +</html> +
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_toolbar_basic.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the developer toolbar works properly + +const URL = "http://example.com/browser/browser/devtools/shared/test/browser_toolbar_basic.html"; + +function test() { + addTab(URL, function(browser, tab) { + info("Starting browser_toolbar_basic.js"); + runTest(); + }); +} + +function runTest() { + Services.obs.addObserver(checkOpen, DeveloperToolbar.NOTIFICATIONS.SHOW, false); + // TODO: reopen the window so the pref has a chance to take effect + // EventUtils.synthesizeKey("v", { ctrlKey: true, shiftKey: true }); + DeveloperToolbarTest.show(); +} + +function checkOpen() { + Services.obs.removeObserver(checkOpen, DeveloperToolbar.NOTIFICATIONS.SHOW, false); + ok(DeveloperToolbar.visible, "DeveloperToolbar is visible"); + + Services.obs.addObserver(checkClosed, DeveloperToolbar.NOTIFICATIONS.HIDE, false); + // EventUtils.synthesizeKey("v", { ctrlKey: true, shiftKey: true }); + DeveloperToolbarTest.hide(); +} + +function checkClosed() { + Services.obs.removeObserver(checkClosed, DeveloperToolbar.NOTIFICATIONS.HIDE, false); + ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible"); + + finish(); +}
--- a/browser/devtools/shared/test/head.js +++ b/browser/devtools/shared/test/head.js @@ -31,37 +31,282 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -let tab; -let browser; +let gcli; +let console; +let require; +let define; + +(function() { + let tempScope = {}; + Components.utils.import("resource:///modules/gcli.jsm", tempScope); + gcli = tempScope.gcli; + console = gcli._internal.console; + define = gcli._internal.define; + require = gcli._internal.require; +})(); + +/** + * Open a new tab at a URL and call a callback on load + */ function addTab(aURL, aCallback) { waitForExplicitFinish(); - function onTabLoad() { - browser.removeEventListener("load", onTabLoad, true); - aCallback(); - } - gBrowser.selectedTab = gBrowser.addTab(); content.location = aURL; - tab = gBrowser.selectedTab; - browser = gBrowser.getBrowserForTab(tab); + let tab = gBrowser.selectedTab; + let browser = gBrowser.getBrowserForTab(tab); + + function onTabLoad() { + browser.removeEventListener("load", onTabLoad, true); + aCallback(browser, tab); + } browser.addEventListener("load", onTabLoad, true); } registerCleanupFunction(function tearDown() { while (gBrowser.tabs.length > 1) { gBrowser.removeCurrentTab(); } - tab = undefined; - browser = undefined; + console = undefined; + define = undefined; + require = undefined; + gcli = undefined; }); + +/** + * Various functions for testing DeveloperToolbar + */ +let DeveloperToolbarTest = { + /** + * Paranoid DeveloperToolbar.show(); + */ + show: function DTT_show() { + if (DeveloperToolbar.visible) { + ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar"); + } + else { + DeveloperToolbar.show(); + } + }, + + /** + * Paranoid DeveloperToolbar.hide(); + */ + hide: function DTT_hide() { + if (!DeveloperToolbar.visible) { + ok(false, "!DeveloperToolbar.visible at start of closeDeveloperToolbar"); + } + else { + DeveloperToolbar.display.inputter.setInput(""); + DeveloperToolbar.hide(); + } + }, + + /** + * Check that we can parse command input. + * Doesn't execute the command, just checks that we grok the input properly: + * + * DeveloperToolbarTest.checkInputStatus({ + * // Test inputs + * typed: "ech", // Required + * cursor: 3, // Optional cursor position + * + * // Thing to check + * status: "INCOMPLETE", // One of "VALID", "ERROR", "INCOMPLETE" + * emptyParameters: [ "<message>" ], // Still to type + * directTabText: "o", // Simple completion text + * arrowTabText: "", // When the completion is not an extension + * }); + */ + checkInputStatus: function DTT_checkInputStatus(test) { + if (test.typed) { + DeveloperToolbar.display.inputter.setInput(test.typed); + } + else { + ok(false, "Missing typed for " + JSON.stringify(test)); + return; + } + + if (test.cursor) { + DeveloperToolbar.display.inputter.setCursor(test.cursor) + } + + if (test.status) { + is(DeveloperToolbar.display.requisition.getStatus().toString(), + test.status, + "status for " + test.typed); + } + + if (test.emptyParameters == null) { + test.emptyParameters = []; + } + + let completer = DeveloperToolbar.display.completer; + let realParams = completer.emptyParameters; + is(realParams.length, test.emptyParameters.length, + 'emptyParameters.length for \'' + test.typed + '\''); + + if (realParams.length === test.emptyParameters.length) { + for (let i = 0; i < realParams.length; i++) { + is(realParams[i].replace(/\u00a0/g, ' '), test.emptyParameters[i], + 'emptyParameters[' + i + '] for \'' + test.typed + '\''); + } + } + + if (test.directTabText) { + is(completer.directTabText, test.directTabText, + 'directTabText for \'' + test.typed + '\''); + } + else { + is(completer.directTabText, '', 'directTabText for \'' + test.typed + '\''); + } + + if (test.arrowTabText) { + is(completer.arrowTabText, ' \u00a0\u21E5 ' + test.arrowTabText, + 'arrowTabText for \'' + test.typed + '\''); + } + else { + is(completer.arrowTabText, '', 'arrowTabText for \'' + test.typed + '\''); + } + }, + + /** + * Execute a command: + * + * DeveloperToolbarTest.exec({ + * // Test inputs + * typed: "echo hi", // Optional, uses existing if undefined + * + * // Thing to check + * args: { message: "hi" }, // Check that the args were understood properly + * outputMatch: /^hi$/, // Regex to test against textContent of output + * blankOutput: true, // Special checks when there is no output + * }); + */ + exec: function DTT_exec(test) { + test = test || {}; + + if (test.typed) { + DeveloperToolbar.display.inputter.setInput(test.typed); + } + + let typed = DeveloperToolbar.display.inputter.getInputState().typed; + let output = DeveloperToolbar.display.requisition.exec(); + + is(typed, output.typed, 'output.command for: ' + typed); + + if (test.completed !== false) { + ok(output.completed, 'output.completed false for: ' + typed); + } + else { + // It is actually an error if we say something is async and it turns + // out not to be? For now we're saying 'no' + // ok(!output.completed, 'output.completed true for: ' + typed); + } + + if (test.args != null) { + is(Object.keys(test.args).length, Object.keys(output.args).length, + 'arg count for ' + typed); + + Object.keys(output.args).forEach(function(arg) { + let expectedArg = test.args[arg]; + let actualArg = output.args[arg]; + + if (Array.isArray(expectedArg)) { + if (!Array.isArray(actualArg)) { + ok(false, 'actual is not an array. ' + typed + '/' + arg); + return; + } + + is(expectedArg.length, actualArg.length, + 'array length: ' + typed + '/' + arg); + for (let i = 0; i < expectedArg.length; i++) { + is(expectedArg[i], actualArg[i], + 'member: "' + typed + '/' + arg + '/' + i); + } + } + else { + is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg); + } + }); + } + + let displayed = DeveloperToolbar.outputPanel._div.textContent; + + if (test.outputMatch) { + if (!test.outputMatch.test(displayed)) { + ok(false, "html output for " + typed + " (textContent sent to info)"); + info("Actual textContent"); + info(displayed); + } + } + + if (test.blankOutput != null) { + if (!/^$/.test(displayed)) { + ok(false, "html output for " + typed + " (textContent sent to info)"); + info("Actual textContent"); + info(displayed); + } + } + }, + + /** + * Quick wrapper around the things you need to do to run DeveloperToolbar + * command tests: + * - Set the pref 'devtools.toolbar.enabled' to true + * - Add a tab pointing at |uri| + * - Open the DeveloperToolbar + * - Register a cleanup function to undo the above + * - Run the tests + * + * @param uri The uri of a page to load. Can be 'about:blank' or 'data:...' + * @param testFunc A function containing the tests to run. This should + * arrange for 'finish()' to be called on completion. + */ + test: function DTT_test(uri, testFunc) { + let menuItem = document.getElementById("menu_devToolbar"); + let command = document.getElementById("Tools:DevToolbar"); + let appMenuItem = document.getElementById("appmenu_devToolbar"); + + registerCleanupFunction(function() { + DeveloperToolbarTest.hide(); + + // a.k.a Services.prefs.clearUserPref("devtools.toolbar.enabled"); + if (menuItem) { + menuItem.hidden = true; + } + if (command) { + command.setAttribute("disabled", "true"); + } + if (appMenuItem) { + appMenuItem.hidden = true; + } + }); + + // a.k.a: Services.prefs.setBoolPref("devtools.toolbar.enabled", true); + if (menuItem) { + menuItem.hidden = false; + } + if (command) { + command.removeAttribute("disabled"); + } + if (appMenuItem) { + appMenuItem.hidden = false; + } + + addTab(uri, function(browser, tab) { + DeveloperToolbarTest.show(); + testFunc(browser, tab); + }); + }, +};
--- a/browser/devtools/webconsole/GcliCommands.jsm +++ b/browser/devtools/webconsole/GcliCommands.jsm @@ -96,22 +96,34 @@ gcli.addCommand({ /** * 'console close' command */ gcli.addCommand({ name: "console close", description: gcli.lookup("consolecloseDesc"), exec: function Command_consoleClose(args, context) { - let tab = HUDService.getHudReferenceById(context.environment.hudId).tab; + let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab HUDService.deactivateHUDForContext(tab); } }); /** + * 'console open' command + */ +gcli.addCommand({ + name: "console open", + description: gcli.lookup("consoleopenDesc"), + exec: function Command_consoleOpen(args, context) { + let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab + HUDService.activateHUDForContext(tab); + } +}); + +/** * 'inspect' command */ gcli.addCommand({ name: "inspect", description: gcli.lookup("inspectDesc"), manual: gcli.lookup("inspectManual"), params: [ {
--- a/browser/devtools/webconsole/HUDService.jsm +++ b/browser/devtools/webconsole/HUDService.jsm @@ -146,34 +146,16 @@ function LogFactory(aMessagePrefix) { function log(aMessage) { var _msg = aMessagePrefix + " " + aMessage + "\n"; dump(_msg); } return log; } -/** - * Load the various Command JSMs. - * Should be called when the console first opens. - * - * @return an object containing the EXPORTED_SYMBOLS from all the command - * modules. In general there is no reason when JSMs need to export symbols - * except when they need the host environment to inform them of things like the - * current window/document/etc. - */ -function loadCommands() { - let commandExports = {}; - - Cu.import("resource:///modules/GcliCommands.jsm", commandExports); - Cu.import("resource:///modules/GcliTiltCommands.jsm", commandExports); - - return commandExports; -} - let log = LogFactory("*** HUDService:"); const HUD_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties"; XPCOMUtils.defineLazyGetter(this, "stringBundle", function () { return Services.strings.createBundle(HUD_STRINGS_URI); }); @@ -1565,16 +1547,17 @@ HUD_SERVICE.prototype = let root = aContext.ownerDocument.getElementsByTagName('window')[0]; root.parentNode.insertBefore(procInstr, root); aContext.ownerDocument.gcliCssProcInstr = procInstr; } if (procInstr.contexts.indexOf(hudId) == -1) { procInstr.contexts.push(hudId); } + HeadsUpDisplayUICommands.refreshCommand(); }, /** * Deactivate a HeadsUpDisplay for the given tab context. * * @param nsIDOMWindow aContext * @param aAnimated animate closing the web console? * @returns void @@ -1595,16 +1578,17 @@ HUD_SERVICE.prototype = let hud = this.hudReferences[hudId]; browser.webProgress.removeProgressListener(hud.progressListener); delete hud.progressListener; this.unregisterDisplay(hudId); window.focus(); + HeadsUpDisplayUICommands.refreshCommand(); } // Remove this context from the list of contexts that need the GCLI CSS // processing instruction and then remove the processing instruction if it // isn't needed any more. let procInstr = aContext.ownerDocument.gcliCssProcInstr; if (procInstr) { procInstr.contexts = procInstr.contexts.filter(function(id) { @@ -3609,17 +3593,17 @@ HeadsUpDisplay.prototype = { * @param nsIDOMWindow aWindow * @returns void */ createConsoleInput: function HUD_createConsoleInput(aWindow, aParentNode, aExistingConsole) { let usegcli = false; try { - usegcli = Services.prefs.getBoolPref("devtools.gcli.enable"); + // usegcli = Services.prefs.getBoolPref("devtools.gcli.enable"); } catch (ex) {} if (appName() == "FIREFOX") { if (!usegcli) { let context = Cu.getWeakReference(aWindow); let mixin = new JSTermFirefoxMixin(context, aParentNode, aExistingConsole); @@ -6330,16 +6314,26 @@ ConsoleUtils = { } }; ////////////////////////////////////////////////////////////////////////// // HeadsUpDisplayUICommands ////////////////////////////////////////////////////////////////////////// HeadsUpDisplayUICommands = { + refreshCommand: function UIC_refreshCommand() { + var window = HUDService.currentContext(); + let command = window.document.getElementById("Tools:WebConsole"); + if (this.getOpenHUD() != null) { + command.setAttribute("checked", true); + } else { + command.removeAttribute("checked"); + } + }, + toggleHUD: function UIC_toggleHUD() { var window = HUDService.currentContext(); var gBrowser = window.gBrowser; var linkedBrowser = gBrowser.selectedTab.linkedBrowser; var tabId = gBrowser.getNotificationBox(linkedBrowser).getAttribute("id"); var hudId = "hud_" + tabId; var ownerDocument = gBrowser.selectedTab.ownerDocument; var hud = ownerDocument.getElementById(hudId); @@ -6830,136 +6824,121 @@ function GcliTerm(aContentWindow, aHudId this.document = aDocument; this.console = aConsole; this.hintNode = aHintNode; this._window = this.context.get().QueryInterface(Ci.nsIDOMWindow); this.createUI(); this.createSandbox(); - this.show = this.show.bind(this); - this.hide = this.hide.bind(this); - - // Allow GCLI:Inputter to decide how and when to open a scratchpad window - let scratchpad = { - shouldActivate: function Scratchpad_shouldActivate(aEvent) { - return aEvent.shiftKey && - aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN; - }, - activate: function Scratchpad_activate(aValue) { - aValue = aValue.replace(/^\s*{\s*/, ''); - ScratchpadManager.openScratchpad({ text: aValue }); - return true; - }, - linkText: stringBundle.GetStringFromName('scratchpad.linkText') - }; - - this.opts = { + this.gcliConsole = gcli._internal.createDisplay({ + contentDocument: aContentWindow.document, + chromeDocument: this.document, + outputDocument: this.document, + chromeWindow: this.document.defaultView, + + hintElement: this.hintNode, + inputElement: this.inputNode, + completeElement: this.completeNode, + backgroundElement: this.inputStack, + consoleWrap: aConsoleWrap, + + eval: this.evalInSandbox.bind(this), + environment: { - hudId: this.hudId, chromeDocument: this.document, contentDocument: aContentWindow.document }, - chromeDocument: this.document, - contentDocument: aContentWindow.document, - jsEnvironment: { - globalObject: unwrap(aContentWindow), - evalFunction: this.evalInSandbox.bind(this) + + tooltipClass: 'gcliterm-tooltip', + + // Allow GCLI:Inputter to decide how and when to open a scratchpad window + scratchpad: { + shouldActivate: function Scratchpad_shouldActivate(aEvent) { + return aEvent.shiftKey && + aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN; + }, + activate: function Scratchpad_activate(aValue) { + aValue = aValue.replace(/^\s*{\s*/, ''); + ScratchpadManager.openScratchpad({ text: aValue }); + return true; + }, + linkText: stringBundle.GetStringFromName('scratchpad.linkText') }, - inputElement: this.inputNode, - completeElement: this.completeNode, - inputBackgroundElement: this.inputStack, - hintElement: this.hintNode, - consoleWrap: aConsoleWrap, - scratchpad: scratchpad, - gcliTerm: this - }; - - gcli._internal.commandOutputManager.addListener(this.onCommandOutput, this); - gcli._internal.createView(this.opts); - - if (!commandExports) { - commandExports = loadCommands(); - } + }); + + this.gcliConsole.onVisibilityChange.add(this.onVisibilityChange, this); + this.gcliConsole.onOutput.add(this.onOutput, this); } GcliTerm.prototype = { /** - * Remove the hint column from the display. - */ - hide: function GcliTerm_hide() - { - let permaHint = false; - try { - permaHint = Services.prefs.getBoolPref("devtools.gcli.permaHint"); - } - catch (ex) {} - - if (!permaHint) { - this.hintNode.parentNode.hidden = true; - } - }, - - /** - * Undo the effects of calling hide(). - */ - show: function GcliTerm_show() - { - this.hintNode.parentNode.hidden = false; + * Show or remove the hint column from the display. + */ + onVisibilityChange: function GcliTerm_onVisibilityChange(ev) + { + if (ev.visible) { + this.hintNode.parentNode.hidden = false; + } + else { + let permaHint = false; + try { + permaHint = Services.prefs.getBoolPref("devtools.gcli.permaHint"); + } + catch (ex) {} + + if (!permaHint) { + this.hintNode.parentNode.hidden = true; + } + } }, /** * Destroy the GcliTerm object. Call this method to avoid memory leaks. */ destroy: function Gcli_destroy() { - gcli._internal.removeView(this.opts); - gcli._internal.commandOutputManager.removeListener(this.onCommandOutput, this); - - delete this.opts.chromeDocument; - delete this.opts.inputElement; - delete this.opts.completeElement; - delete this.opts.inputBackgroundElement; - delete this.opts.hintElement; - delete this.opts.contentDocument; - delete this.opts.jsEnvironment; - delete this.opts.gcliTerm; + this.gcliConsole.onVisibilityChange.remove(this.onVisibilityChange, this); + this.gcliConsole.onOutput.remove(this.onOutput, this); + this.gcliConsole.destroy(); delete this.context; delete this.document; delete this.console; delete this.hintNode; delete this._window; delete this.sandbox; - delete this.element - delete this.inputStack - delete this.completeNode - delete this.inputNode + delete this.element; + delete this.inputStack; + delete this.completeNode; + delete this.inputNode; }, /** * Re-attaches a console when the contentWindow is recreated. * * @param nsIDOMWindow aContentWindow * The content window that we're providing as the context to commands * @param object aConsole * Console object to use within the GcliTerm. */ reattachConsole: function Gcli_reattachConsole(aContentWindow, aConsole) { this.context = Cu.getWeakReference(aContentWindow); this.console = aConsole; this.createSandbox(); - this.opts.environment.contentDocument = aContentWindow.document; - this.opts.contentDocument = aContentWindow.document; - this.opts.jsEnvironment.globalObject = unwrap(aContentWindow); - - gcli._internal.reattachConsole(this.opts); + this.gcliConsole.reattach({ + contentDocument: aContentWindow.document, + environment: { + chromeDocument: this.document, + contentDocument: aContentWindow.document + }, + }); }, /** * Generates and attaches the GCLI Terminal part of the Web Console, which * essentially consists of the interactive JavaScript input facility. */ createUI: function Gcli_createUI() { @@ -6980,17 +6959,17 @@ GcliTerm.prototype = { this.inputNode.setAttribute("class", "gcliterm-input-node"); this.inputNode.setAttribute("rows", "1"); this.inputStack.appendChild(this.inputNode); }, /** * Called by GCLI/canon when command line output changes. */ - onCommandOutput: function Gcli_onCommandOutput(aEvent) + onOutput: function Gcli_onOutput(aEvent) { // When we can update the history of the console, then we should stop // filtering incomplete reports. if (!aEvent.output.completed) { return; } this.writeOutput(aEvent.output.typed, CATEGORY_INPUT);
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/gcli.css @@ -0,0 +1,74 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the GCLI. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Joe Walker <jwalker@mozilla.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +.gclichrome-output { + max-width: 350px; +} + +.gclichrome-tooltip { + max-width: 350px; +} + +.gcli-help-name { + text-align: end; +} + +.gcli-help-synopsis { + cursor: pointer; + display: inline-block; +} + +.gcli-help-synopsis:before { + content: '\bb'; +} + +.gcli-menu-option { + overflow: hidden; + white-space: nowrap; + cursor: pointer; +} + +.gcli-menu-template { + border-collapse: collapse; + width: 100%; +} + +.gcli-menu-error { + overflow: hidden; + white-space: nowrap; +} +
--- a/browser/devtools/webconsole/gcli.jsm +++ b/browser/devtools/webconsole/gcli.jsm @@ -31,17 +31,17 @@ * - mini_require: A very basic commonjs AMD (Asynchronous Modules Definition) * 'require' implementation (which is just good enough to load GCLI). For * more, see http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition. * This alleviates the need for requirejs (http://requirejs.org/) which is * used when running in the browser. This code is provided by dryice. * - A build of GCLI itself, packaged using dryice * - suffix-gcli.jsm - code to require the gcli object for EXPORTED_SYMBOLS. * - * See Makefile.dryice.js for more details of this build. + * See gcli.js for more details of this build. * For more details on dryice, see the https://github.com/mozilla/dryice * ******************************************************************************* * * * * * @@ -52,20 +52,21 @@ */ /////////////////////////////////////////////////////////////////////////////// var EXPORTED_SYMBOLS = [ "gcli" ]; /** - * Expose a Node object. This allows us to use the Node constants without - * resorting to hardcoded numbers + * Expose Node/HTMLElement objects. This allows us to use the Node constants + * without resorting to hardcoded numbers */ var Node = Components.interfaces.nsIDOMNode; +var HTMLElement = Components.interfaces.nsIDOMHTMLElement; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); /** * Define setTimeout and clearTimeout to match the browser functions */ var setTimeout; @@ -223,17 +224,17 @@ var console = {}; } if (aThing === null) { return "null"; } if (typeof aThing == "object") { var type = getCtorName(aThing); - if (type == "XULElement") { + if (aThing instanceof Node && aThing.tagName) { return debugElement(aThing); } type = (type == "Object" ? "" : type + " "); var json; try { json = JSON.stringify(aThing); } catch (ex) { @@ -287,18 +288,18 @@ var console = {}; if (typeof aThing == "object") { var reply = ""; var type = getCtorName(aThing); if (type == "Error") { reply += " " + aThing.message + "\n"; reply += logProperty("stack", aThing.stack); } - else if (type == "XULElement") { - reply += " " + debugElement(aThing) + " (XUL)\n"; + else if (aThing instanceof Node && aThing.tagName) { + reply += " " + debugElement(aThing) + "\n"; } else { var keys = Object.getOwnPropertyNames(aThing); if (keys.length > 0) { reply += type + "\n"; keys.forEach(function(aProp) { reply += logProperty(aProp, aThing[aProp]); }, this); @@ -686,39 +687,47 @@ var mozl10n = {}; } catch (ex) { throw new Error("Failure in lookupFormat('" + name + "')"); } }; })(mozl10n); -define('gcli/index', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/types/basic', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/cli', 'gcli/commands/help', 'gcli/ui/console'], function(require, exports, module) { +define('gcli/index', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/types/basic', 'gcli/types/command', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/types/selection', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/help', 'gcli/ui/ffdisplay'], function(require, exports, module) { // The API for use by command authors exports.addCommand = require('gcli/canon').addCommand; exports.removeCommand = require('gcli/canon').removeCommand; exports.lookup = mozl10n.lookup; exports.lookupFormat = mozl10n.lookupFormat; // Internal startup process. Not exported require('gcli/types/basic').startup(); + require('gcli/types/command').startup(); require('gcli/types/javascript').startup(); require('gcli/types/node').startup(); require('gcli/types/resource').startup(); - require('gcli/cli').startup(); + require('gcli/types/setting').startup(); + require('gcli/types/selection').startup(); + + require('gcli/settings').startup(); + require('gcli/ui/intro').startup(); + require('gcli/ui/focus').startup(); + require('gcli/ui/fields/basic').startup(); + require('gcli/ui/fields/javascript').startup(); + require('gcli/ui/fields/selection').startup(); + require('gcli/commands/help').startup(); - var Requisition = require('gcli/cli').Requisition; - var Console = require('gcli/ui/console').Console; - - var cli = require('gcli/cli'); - var jstype = require('gcli/types/javascript'); - var nodetype = require('gcli/types/node'); - var resource = require('gcli/types/resource'); + // Some commands require customizing for Firefox before we include them + // require('gcli/cli').startup(); + // require('gcli/commands/pref').startup(); + + var FFDisplay = require('gcli/ui/ffdisplay').FFDisplay; /** * API for use by HUDService only. * This code is internal and subject to change without notice. */ exports._internal = { require: require, define: define, @@ -729,65 +738,22 @@ define('gcli/index', ['require', 'export * members: * - contentDocument: From the window of the attached tab * - chromeDocument: GCLITerm.document * - environment.hudId: GCLITerm.hudId * - jsEnvironment.globalObject: 'window' * - jsEnvironment.evalFunction: 'eval' in a sandbox * - inputElement: GCLITerm.inputNode * - completeElement: GCLITerm.completeNode - * - gcliTerm: GCLITerm * - hintElement: GCLITerm.hintNode * - inputBackgroundElement: GCLITerm.inputStack */ - createView: function(opts) { - jstype.setGlobalObject(opts.jsEnvironment.globalObject); - nodetype.setDocument(opts.contentDocument); - cli.setEvalFunction(opts.jsEnvironment.evalFunction); - resource.setDocument(opts.contentDocument); - - if (opts.requisition == null) { - opts.requisition = new Requisition(opts.environment, opts.chromeDocument); - } - - opts.console = new Console(opts); - }, - - /** - * Called when the page to which we're attached changes - */ - reattachConsole: function(opts) { - jstype.setGlobalObject(opts.jsEnvironment.globalObject); - nodetype.setDocument(opts.contentDocument); - cli.setEvalFunction(opts.jsEnvironment.evalFunction); - - opts.requisition.environment = opts.environment; - opts.requisition.document = opts.chromeDocument; - - opts.console.reattachConsole(opts); - }, - - /** - * Undo the effects of createView() to prevent memory leaks - */ - removeView: function(opts) { - opts.console.destroy(); - delete opts.console; - - opts.requisition.destroy(); - delete opts.requisition; - - cli.unsetEvalFunction(); - nodetype.unsetDocument(); - jstype.unsetGlobalObject(); - resource.unsetDocument(); - resource.clearResourceCache(); - }, - - commandOutputManager: require('gcli/canon').commandOutputManager + createDisplay: function(opts) { + return new FFDisplay(opts); + } }; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */