From deba10fdc2553acaa944406c69ea9f21b3ee7e7e Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Mon, 26 Sep 2011 13:43:37 -0300 Subject: [PATCH 01/10] Bug 681653 - Augment RegisterTools API in Highlighter to deregister tools; r=msucan, gavin --- browser/devtools/highlighter/inspector.js | 233 +++++++++++++----- .../test/browser_inspector_registertools.js | 183 +++++++------- 2 files changed, 271 insertions(+), 145 deletions(-) diff --git a/browser/devtools/highlighter/inspector.js b/browser/devtools/highlighter/inspector.js index e90bf6058c5..18993e39fc8 100644 --- a/browser/devtools/highlighter/inspector.js +++ b/browser/devtools/highlighter/inspector.js @@ -135,7 +135,6 @@ Highlighter.prototype = { this.handleResize(); }, - /** * Build the veil: * @@ -555,6 +554,7 @@ Highlighter.prototype = { var InspectorUI = { browser: null, tools: {}, + toolEvents: {}, showTextNodesWithWhitespace: false, inspecting: false, treeLoaded: false, @@ -814,6 +814,8 @@ var InspectorUI = { this.winID = this.getWindowID(this.win); this.toolbar = document.getElementById("inspector-toolbar"); + this.initTools(); + if (!this.domplate) { Cu.import("resource:///modules/domplate.jsm", this); this.domplateUtils.setDOM(window); @@ -827,6 +829,13 @@ var InspectorUI = { gBrowser.addProgressListener(InspectorProgressListener); }, + /** + * Register and initialize any included tools. + */ + initTools: function IUI_initTools() + { + }, + /** * Initialize highlighter. */ @@ -903,6 +912,12 @@ var InspectorUI = { } this.stopInspecting(); + + this.saveToolState(this.winID); + this.toolsDo(function IUI_toolsHide(aTool) { + this.unregisterTool(aTool); + }.bind(this)); + if (this.highlighter) { this.highlighter.destroy(); this.highlighter = null; @@ -930,13 +945,6 @@ var InspectorUI = { delete this.domplateUtils; } - this.saveToolState(this.winID); - this.toolsDo(function IUI_toolsHide(aTool) { - if (aTool.panel) { - aTool.panel.hidePopup(); - } - }); - this.inspectCmd.setAttribute("checked", false); this.browser = this.win = null; // null out references to browser and window this.winID = null; @@ -1020,11 +1028,8 @@ var InspectorUI = { } this.ioBox.select(this.selection, true, true, aScroll); } - this.toolsDo(function IUI_toolsOnSelect(aTool) { - if (aTool.panel.state == "open") { - aTool.onSelect.apply(aTool.context, [aNode]); - } - }); + + this.toolsSelect(); }, ///////////////////////////////////////////////////////////////////////// @@ -1039,6 +1044,8 @@ var InspectorUI = { this.startInspecting(); } + this.restoreToolState(this.winID); + this.win.focus(); Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.OPENED, null); }, @@ -1073,7 +1080,6 @@ var InspectorUI = { }, INSPECTOR_NOTIFICATIONS.CLOSED, false); } else { this.openInspectorUI(); - this.restoreToolState(winID); } } @@ -1592,6 +1598,19 @@ var InspectorUI = { this._log("END TRACE"); }, + /** + * Get the toolbar button name for a given id string. Used by the + * registerTools API to retrieve a consistent name for toolbar buttons + * based on the ID of the tool. + * @param anId String + * id of the tool to be buttonized + * @returns String + */ + getToolbarButtonId: function IUI_createButtonId(anId) + { + return "inspector-" + anId + "-toolbutton"; + }, + /** * Register an external tool with the inspector. * @@ -1602,93 +1621,174 @@ var InspectorUI = { * icon: "chrome://somepath.png", * tooltiptext: "Button tooltip", * accesskey: "S", + * isOpen: object.property, (getter) returning true if tool is open. * onSelect: object.method, - * onShow: object.method, - * onHide: object.method, + * show: object.method, called to show the tool when button is pressed. + * hide: object.method, called to hide the tool when button is pressed. + * unregister: object.method, called when tool should be destroyed. * panel: myTool.panel * } * - * @param aRegObj + * @param aRegObj Object + * The Registration Object used to register this tool described + * above. The tool should cache this object for later deregistration. */ - registerTool: function IUI_RegisterTool(aRegObj) { - if (this.tools[aRegObj.id]) { + registerTool: function IUI_registerTool(aRegObj) + { + if (this.toolRegistered(aRegObj.id)) { return; - } else { - let id = aRegObj.id; - let buttonId = "inspector-" + id + "-toolbutton"; - aRegObj.buttonId = buttonId; - - aRegObj.panel.addEventListener("popuphiding", - function IUI_toolPanelHiding() { - btn.setAttribute("checked", "false"); - }, false); - aRegObj.panel.addEventListener("popupshowing", - function IUI_toolPanelShowing() { - btn.setAttribute("checked", "true"); - }, false); - - this.tools[id] = aRegObj; } - let toolbox = document.getElementById("inspector-tools"); + this.tools[aRegObj.id] = aRegObj; + + let buttonContainer = document.getElementById("inspector-tools"); let btn = document.createElement("toolbarbutton"); - btn.setAttribute("id", aRegObj.buttonId); + let buttonId = this.getToolbarButtonId(aRegObj.id); + btn.setAttribute("id", buttonId); btn.setAttribute("label", aRegObj.label); btn.setAttribute("tooltiptext", aRegObj.tooltiptext); btn.setAttribute("accesskey", aRegObj.accesskey); - btn.setAttribute("class", "toolbarbutton-text"); btn.setAttribute("image", aRegObj.icon || ""); - toolbox.appendChild(btn); + buttonContainer.appendChild(btn); - btn.addEventListener("click", - function IUI_ToolButtonClick(aEvent) { - if (btn.getAttribute("checked") == "true") { - aRegObj.onHide.apply(aRegObj.context); + /** + * Save the registered tool's toolbar button's click handler so we can remove + * it at deregistration time. + * @param aButton XUL:toolbarbutton + * @param aCallback Function the click event handler for the button + */ + function bindToolEvent(aWidget, aEvent, aCallback) + { + let toolEvent = aWidget.id + "_" + aEvent; + InspectorUI.toolEvents[toolEvent] = aCallback; + aWidget.addEventListener(aEvent, aCallback, false); + } + + bindToolEvent(btn, "click", + function IUI_toolButtonClick(aEvent) { + if (btn.checked) { + this.toolHide(aRegObj); } else { - aRegObj.onShow.apply(aRegObj.context, [InspectorUI.selection]); - aRegObj.onSelect.apply(aRegObj.context, [InspectorUI.selection]); + this.toolShow(aRegObj); } - }, false); + }.bind(this)); + + if (aRegObj.panel) { + bindToolEvent(aRegObj.panel, "popuphiding", + function IUI_toolPanelHiding() { + btn.checked = false; + }); + } }, -/** - * Save a list of open tools to the inspector store. - * - * @param aWinID The ID of the window used to save the associated tools - */ + /** + * Show the specified tool. + * @param aTool Object (see comment for IUI_registerTool) + */ + toolShow: function IUI_toolShow(aTool) + { + aTool.show.call(aTool.context, this.selection); + document.getElementById(this.getToolbarButtonId(aTool.id)).checked = true; + }, + + /** + * Hide the specified tool. + * @param aTool Object (see comment for IUI_registerTool) + */ + toolHide: function IUI_toolHide(aTool) + { + aTool.hide.call(aTool.context); + document.getElementById(this.getToolbarButtonId(aTool.id)).checked = false; + }, + + /** + * Unregister the registered tool, unbinding click events for the buttons + * and showing and hiding events for the panel. + * @param aRegObj Object + * The registration object used to register the tool. + */ + unregisterTool: function IUI_unregisterTool(aRegObj) + { + let button = document.getElementById(this.getToolbarButtonId(aRegObj.id)); + + /** + * Unregister the click handler for the registered tool's button. + * @param aButton XUL:toolbarbutton + */ + function unbindToolEvent(aWidget, aEvent) + { + let toolEvent = aWidget.id + "_" + aEvent; + if (!InspectorUI.toolEvents[toolEvent]) { + return; + } + + aWidget.removeEventListener(aEvent, InspectorUI.toolEvents[toolEvent], false); + delete InspectorUI.toolEvents[toolEvent] + } + + let buttonContainer = document.getElementById("inspector-tools"); + unbindToolEvent(button, "click"); + + if (aRegObj.panel) + unbindToolEvent(aRegObj.panel, "popuphiding"); + + buttonContainer.removeChild(button); + + if (aRegObj.unregister) + aRegObj.unregister.call(aRegObj.context); + + delete this.tools[aRegObj.id]; + }, + + /** + * Save a list of open tools to the inspector store. + * + * @param aWinID The ID of the window used to save the associated tools + */ saveToolState: function IUI_saveToolState(aWinID) { let openTools = {}; this.toolsDo(function IUI_toolsSetId(aTool) { - if (aTool.panel.state == "open") { + if (aTool.isOpen) { openTools[aTool.id] = true; } }); InspectorStore.setValue(aWinID, "openTools", openTools); }, -/** - * Restore tools previously save using saveToolState(). - * - * @param aWinID The ID of the window to which the associated tools are to be - * restored. - */ + /** + * Restore tools previously save using saveToolState(). + * + * @param aWinID The ID of the window to which the associated tools are to be + * restored. + */ restoreToolState: function IUI_restoreToolState(aWinID) { let openTools = InspectorStore.getValue(aWinID, "openTools"); - InspectorUI.selection = InspectorUI.selection; if (openTools) { this.toolsDo(function IUI_toolsOnShow(aTool) { if (aTool.id in openTools) { - aTool.onShow.apply(aTool.context, [InspectorUI.selection]); + this.toolShow(aTool); } - }); + }.bind(this)); } }, - + + /** + * For each tool in the tools collection select the current node that is + * selected in the highlighter + */ + toolsSelect: function IUI_toolsSelect() + { + this.toolsDo(function IUI_toolsOnSelect(aTool) { + if (aTool.isOpen) { + aTool.onSelect.call(aTool.context, InspectorUI.selection); + } + }); + }, + /** * Loop through all registered tools and pass each into the provided function - * * @param aFunction The function to which each tool is to be passed */ toolsDo: function IUI_toolsDo(aFunction) @@ -1697,6 +1797,15 @@ var InspectorUI = { aFunction(tool); } }, + + /** + * Check if a tool is registered? + * @param aId The id of the tool to check + */ + toolRegistered: function IUI_toolRegistered(aId) + { + return aId in this.tools; + }, }; /** diff --git a/browser/devtools/highlighter/test/browser_inspector_registertools.js b/browser/devtools/highlighter/test/browser_inspector_registertools.js index d8f24ed1973..19ef0ccd382 100644 --- a/browser/devtools/highlighter/test/browser_inspector_registertools.js +++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js @@ -39,16 +39,20 @@ let doc; let h1; +let p2; +let toolsLength = 0; +let toolEvents = 0; let tool1; let tool2; let tool3; +let initToolsMethod = InspectorUI.initTools; function createDocument() { let div = doc.createElement("div"); - let h1 = doc.createElement("h1"); + h1 = doc.createElement("h1"); let p1 = doc.createElement("p"); - let p2 = doc.createElement("p"); + p2 = doc.createElement("p"); let div2 = doc.createElement("div"); let p3 = doc.createElement("p"); doc.title = "Inspector Tree Selection Test"; @@ -79,73 +83,70 @@ function createDocument() function setupHighlighterTests() { - h1 = doc.querySelectorAll("h1")[0]; ok(h1, "we have the header node"); - Services.obs.addObserver(inspectorOpen, "inspector-opened", false); + Services.obs.addObserver(inspectorOpen, INSPECTOR_NOTIFICATIONS.OPENED, false); + registerTools(); InspectorUI.toggleInspectorUI(); } function inspectorOpen() { info("we received the inspector-opened notification"); - Services.obs.removeObserver(inspectorOpen, "inspector-opened", false); - Services.obs.addObserver(startToolTests, "inspector-highlighting", false); - let rect = h1.getBoundingClientRect(); - executeSoon(function() { - EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content); - }); + Services.obs.removeObserver(inspectorOpen, INSPECTOR_NOTIFICATIONS.OPENED); + toolsLength = InspectorUI.tools.length; + toolEvents = InspectorUI.toolEvents.length; + info("tools registered"); + Services.obs.addObserver(startToolTests, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false); + InspectorUI.inspectNode(h1); } function startToolTests(evt) { - info("we received the inspector-highlighting notification"); - Services.obs.removeObserver(startToolTests, "inspector-highlighting", false); + Services.obs.removeObserver(startToolTests, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING); InspectorUI.stopInspecting(); - info("Getting InspectorUI.tools"); let tools = InspectorUI.tools; + tool1 = InspectorUI.tools["tool_1"]; tool2 = InspectorUI.tools["tool_2"]; tool3 = InspectorUI.tools["tool_3"]; info("Checking panel states 1"); - ok(tool1.context.panelIsClosed, "Panel 1 is closed"); - ok(tool2.context.panelIsClosed, "Panel 2 is closed"); - ok(tool3.context.panelIsClosed, "Panel 3 is closed"); + ok(!tool1.isOpen, "Panel 1 is closed"); + ok(!tool2.isOpen, "Panel 2 is closed"); + ok(!tool3.isOpen, "Panel 3 is closed"); info("Calling show method for all tools"); - tool1.onShow.apply(tool1.context, [h1]); - tool2.onShow.apply(tool2.context, [h1]); - tool3.onShow.apply(tool3.context, [h1]); + InspectorUI.toolShow(tool1); + InspectorUI.toolShow(tool2); + InspectorUI.toolShow(tool3); info("Checking panel states 2"); - ok(tool1.context.panelIsOpen, "Panel 1 is open"); - ok(tool2.context.panelIsOpen, "Panel 2 is open"); - ok(tool3.context.panelIsOpen, "Panel 3 is open"); + ok(tool1.isOpen, "Panel 1 is open"); + ok(tool2.isOpen, "Panel 2 is open"); + ok(tool3.isOpen, "Panel 3 is open"); - info("Calling selectNode method for all tools"); - tool1.onSelect.apply(tool1.context, [h1]); - tool2.onSelect.apply(tool2.context, [h1]); - tool3.onSelect.apply(tool3.context, [h1]); + info("Calling selectNode method for all tools, should see 3 selects"); + InspectorUI.inspectNode(p2); info("Calling hide method for all tools"); - tool1.onHide.apply(tool1.context, [h1]); - tool2.onHide.apply(tool2.context, [h1]); - tool3.onHide.apply(tool3.context, [h1]); - + InspectorUI.toolHide(tool1); + InspectorUI.toolHide(tool2); + InspectorUI.toolHide(tool3); + info("Checking panel states 3"); - ok(tool1.context.panelIsClosed, "Panel 1 is closed"); - ok(tool2.context.panelIsClosed, "Panel 2 is closed"); - ok(tool3.context.panelIsClosed, "Panel 3 is closed"); + ok(!tool1.isOpen, "Panel 1 is closed"); + ok(!tool2.isOpen, "Panel 2 is closed"); + ok(!tool3.isOpen, "Panel 3 is closed"); info("Showing tools 1 & 3"); - tool1.onShow.apply(tool1.context, [h1]); - tool3.onShow.apply(tool3.context, [h1]); + InspectorUI.toolShow(tool1); + InspectorUI.toolShow(tool3); info("Checking panel states 4"); - ok(tool1.context.panelIsOpen, "Panel 1 is open"); - ok(tool2.context.panelIsClosed, "Panel 2 is closed"); - ok(tool3.context.panelIsOpen, "Panel 3 is open"); + ok(tool1.isOpen, "Panel 1 is open"); + ok(!tool2.isOpen, "Panel 2 is closed"); + ok(tool3.isOpen, "Panel 3 is open"); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { @@ -160,24 +161,52 @@ function testSecondTab() { info("Opened second tab"); info("Checking panel states 5"); - ok(tool1.context.panelIsClosed, "Panel 1 is closed"); - ok(tool2.context.panelIsClosed, "Panel 2 is closed"); - ok(tool3.context.panelIsClosed, "Panel 3 is closed"); + + let tools = InspectorUI.tools; + ok(!(tool1 in tools), "Panel 1 not in tools"); + ok(!(tool2 in tools), "Panel 2 not in tools"); + ok(!(tool3 in tools), "Panel 3 not in tools"); info("Closing current tab"); + Services.obs.addObserver(testOriginalTab, INSPECTOR_NOTIFICATIONS.OPENED, false); gBrowser.removeCurrentTab(); +} +function testOriginalTab() +{ + Services.obs.removeObserver(testOriginalTab, INSPECTOR_NOTIFICATIONS.OPENED); info("Checking panel states 6"); - ok(tool1.context.panelIsOpen, "Panel 1 is open"); - ok(tool2.context.panelIsClosed, "Panel 2 is closed"); - ok(tool3.context.panelIsOpen, "Panel 3 is open"); - executeSoon(finishUp); + info("Tools: " + InspectorUI.tools); + // reacquaint ourselves with our tools + tool1 = InspectorUI.tools["tool_1"]; + tool2 = InspectorUI.tools["tool_2"]; + tool3 = InspectorUI.tools["tool_3"]; + + ok(tool1.isOpen, "Panel 1 is open after reactivation"); + ok(!tool2.isOpen, "Panel 2 is closed after reactivation"); + ok(tool3.isOpen, "Panel 3 is open after reactivation"); + + Services.obs.addObserver(unregisterTools, INSPECTOR_NOTIFICATIONS.CLOSED, false); + InspectorUI.closeInspectorUI(true); +} + +function unregisterTools() +{ + Services.obs.removeObserver(unregisterTools, INSPECTOR_NOTIFICATIONS.CLOSED); + let tools = InspectorUI.tools; + + ok(!(tool1 in tools), "Tool 1 removed"); + ok(!(tool2 in tools), "Tool 2 removed"); + ok(!(tool3 in tools), "Tool 3 removed"); + is(tools.length, toolsLength, "Number of Registered Tools matches original"); + is(InspectorUI.toolEvents.length, toolEvents, "Number of tool events matches original"); + finishUp(); } function finishUp() { - InspectorUI.closeInspectorUI(true); gBrowser.removeCurrentTab(); + InspectorUI.initTools = initToolsMethod; finish(); } @@ -188,7 +217,7 @@ function test() gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; - waitForFocus(registerTools, content); + waitForFocus(createDocument, content); }, true); content.location = "data:text/html,registertool tests for inspector"; @@ -196,10 +225,12 @@ function test() function registerTools() { - createDocument(); - registerTool(new testTool("tool_1", "Tool 1", "Tool 1 tooltip", "I")); - registerTool(new testTool("tool_2", "Tool 2", "Tool 2 tooltip", "J")); - registerTool(new testTool("tool_3", "Tool 3", "Tool 3 tooltip", "K")); + InspectorUI.initTools = function() { + info("(re)registering tools"); + registerTool(new testTool("tool_1", "Tool 1", "Tool 1 tooltip", "I")); + registerTool(new testTool("tool_2", "Tool 2", "Tool 2 tooltip", "J")); + registerTool(new testTool("tool_3", "Tool 3", "Tool 3 tooltip", "K")); + } } function registerTool(aTool) @@ -210,10 +241,11 @@ function registerTool(aTool) tooltiptext: aTool.tooltip, accesskey: aTool.accesskey, context: aTool, + get isOpen() aTool.isOpen(), onSelect: aTool.selectNode, - onShow: aTool.show, - onHide: aTool.hide, - panel: aTool.panel + show: aTool.show, + hide: aTool.hide, + unregister: aTool.destroy, }); } @@ -223,19 +255,13 @@ function testTool(aToolId, aLabel, aTooltip, aAccesskey) this.id = aToolId; this.label = aLabel; this.tooltip = aTooltip; - this.accesskey = aAccesskey - this.panel = this.createPanel(); + this.accesskey = aAccesskey; + this._isOpen = false; } testTool.prototype = { - get panelIsOpen() - { - return this.panel.state == "open" || this.panel.state == "showing"; - }, - - get panelIsClosed() - { - return this.panel.state == "closed" || this.panel.state == "hiding"; + isOpen: function BIR_isOpen() { + return this._isOpen; }, selectNode: function BIR_selectNode(aNode) { @@ -244,32 +270,23 @@ testTool.prototype = { }, show: function BIR_show(aNode) { - this.panel.openPopup(gBrowser.selectedBrowser, - "end_before", 0, 20, false, false); + this._isOpen = true; is(InspectorUI.selection, aNode, "show: currently selected node was passed: " + this.id); }, hide: function BIR_hide() { info(this.id + " hide"); - this.panel.hidePopup(); + this._isOpen = false; }, - createPanel: function BIR_createPanel() { - let popupSet = document.getElementById("mainPopupSet"); - let ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - let panel = this.panel = document.createElementNS(ns, "panel"); - panel.setAttribute("orient", "vertical"); - panel.setAttribute("noautofocus", "true"); - panel.setAttribute("noautohide", "true"); - panel.setAttribute("titlebar", "normal"); - panel.setAttribute("close", "true"); - panel.setAttribute("label", "Panel for " + this.id); - panel.setAttribute("width", 200); - panel.setAttribute("height", 400); - popupSet.appendChild(panel); - - ok(panel.parentNode == popupSet, "Panel created and appended successfully"); - return panel; + destroy: function BIR_destroy() { + info("tool destroyed " + this.id); + if (this.isOpen()) + this.hide(); + delete this.id; + delete this.label; + delete this.tooltip; + delete this.accesskey; }, }; From d886182d5f2266fdbd7f614a33beeb772517db1c Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Mon, 26 Sep 2011 13:46:44 -0300 Subject: [PATCH 02/10] Bug 663831 - Style inspector should be controllable from the highlighter; r=gavin,msucan --- browser/devtools/highlighter/inspector.js | 46 ++++++++++++++++- .../devtools/styleinspector/CssHtmlTree.jsm | 3 +- .../styleinspector/StyleInspector.jsm | 49 ++++++++++++++++--- .../browser_styleinspector_webconsole.js | 16 ++---- browser/devtools/webconsole/HUDService.jsm | 2 +- .../chrome/browser/styleinspector.properties | 6 +++ 6 files changed, 100 insertions(+), 22 deletions(-) diff --git a/browser/devtools/highlighter/inspector.js b/browser/devtools/highlighter/inspector.js index 18993e39fc8..6c0fadd2529 100644 --- a/browser/devtools/highlighter/inspector.js +++ b/browser/devtools/highlighter/inspector.js @@ -208,7 +208,6 @@ Highlighter.prototype = { aParent.appendChild(closeButton); }, - /** * Destroy the nodes. */ @@ -834,6 +833,26 @@ var InspectorUI = { */ initTools: function IUI_initTools() { + // Style inspector + if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") && + !this.toolRegistered("styleinspector")) { + let stylePanel = this.StyleInspector.createPanel(true); + this.registerTool({ + id: "styleinspector", + label: InspectorUI.StyleInspector.l10n("style.highlighter.button.label"), + tooltiptext: InspectorUI.StyleInspector.l10n("style.highlighter.button.tooltip"), + accesskey: InspectorUI.StyleInspector.l10n("style.highlighter.accesskey"), + context: stylePanel, + get isOpen() stylePanel.isOpen(), + onSelect: stylePanel.selectNode, + show: stylePanel.showTool, + hide: stylePanel.hideTool, + dim: stylePanel.dimTool, + panel: stylePanel, + unregister: stylePanel.destroy, + }); + this.stylePanel = stylePanel; + } }, /** @@ -960,6 +979,7 @@ var InspectorUI = { this.treePanel.hidePopup(); delete this.treePanel; + delete this.stylePanel; }, /** @@ -976,6 +996,7 @@ var InspectorUI = { document.getElementById("inspector-inspect-toolbutton").checked = true; this.attachPageListeners(); this.inspecting = true; + this.toolsDim(true); this.highlighter.veilContainer.removeAttribute("locked"); }, @@ -994,6 +1015,7 @@ var InspectorUI = { document.getElementById("inspector-inspect-toolbutton").checked = false; this.detachPageListeners(); this.inspecting = false; + this.toolsDim(false); if (this.highlighter.node) { this.select(this.highlighter.node, true, true, !aPreventScroll); } else { @@ -1153,10 +1175,12 @@ var InspectorUI = { this.ioBox.toggleObject(node); } else { if (this.inspecting) { + this.toolsSelect(); this.stopInspecting(true); } else { this.select(node, true, false); this.highlighter.highlightNode(node); + this.toolsSelect(); } } } @@ -1625,6 +1649,7 @@ var InspectorUI = { * onSelect: object.method, * show: object.method, called to show the tool when button is pressed. * hide: object.method, called to hide the tool when button is pressed. + * dim: object.method, called to disable a tool during highlighting. * unregister: object.method, called when tool should be destroyed. * panel: myTool.panel * } @@ -1787,6 +1812,19 @@ var InspectorUI = { }); }, + /** + * Dim or undim each tool in the tools collection + * @param aState true = dim, false = undim + */ + toolsDim: function IUI_toolsDim(aState) + { + this.toolsDo(function IUI_toolsOnSelect(aTool) { + if (aTool.isOpen && "dim" in aTool) { + aTool.dim.call(aTool.context, aState); + } + }); + }, + /** * Loop through all registered tools and pass each into the provided function * @param aFunction The function to which each tool is to be passed @@ -2054,3 +2092,9 @@ XPCOMUtils.defineLazyGetter(InspectorUI, "strings", function () { createBundle("chrome://browser/locale/inspector.properties"); }); +XPCOMUtils.defineLazyGetter(InspectorUI, "StyleInspector", function () { + var obj = {}; + Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj); + return obj.StyleInspector; +}); + diff --git a/browser/devtools/styleinspector/CssHtmlTree.jsm b/browser/devtools/styleinspector/CssHtmlTree.jsm index 5e2a5dcc6ac..7cf71d4c24f 100644 --- a/browser/devtools/styleinspector/CssHtmlTree.jsm +++ b/browser/devtools/styleinspector/CssHtmlTree.jsm @@ -21,9 +21,10 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Joe Walker (jwalker@mozilla.com) (original author) + * Joe Walker (jwalker@mozilla.com) (Original Author) * Mihai Șucan * Michael Ratcliffe + * Rob Campbell * * 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 diff --git a/browser/devtools/styleinspector/StyleInspector.jsm b/browser/devtools/styleinspector/StyleInspector.jsm index 34888f89b15..230a13253d6 100644 --- a/browser/devtools/styleinspector/StyleInspector.jsm +++ b/browser/devtools/styleinspector/StyleInspector.jsm @@ -21,7 +21,8 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Mike Ratcliffe + * Mike Ratcliffe (Original Author) + * Rob Campbell * * 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 @@ -109,8 +110,6 @@ var StyleInspector = { let iframeReady = false; function SI_iframeOnload() { iframe.removeEventListener("load", SI_iframeOnload, true); - panel.cssLogic = new CssLogic(); - panel.cssHtmlTree = new CssHtmlTree(iframe, panel.cssLogic, panel); iframeReady = true; if (panelReady) { SI_popupShown.call(panel); @@ -124,6 +123,10 @@ var StyleInspector = { function SI_popupShown() { panelReady = true; if (iframeReady) { + if (!this.cssLogic) { + this.cssLogic = new CssLogic(); + this.cssHtmlTree = new CssHtmlTree(iframe, this.cssLogic, this); + } let selectedNode = this.selectedNode || null; this.cssLogic.highlight(selectedNode); this.cssHtmlTree.highlight(selectedNode); @@ -162,12 +165,9 @@ var StyleInspector = { panel.selectNode = function SI_selectNode(aNode) { this.selectedNode = aNode; - if (this.isOpen()) { + if (this.isOpen() && !this.hasAttribute("dimmed")) { this.cssLogic.highlight(aNode); this.cssHtmlTree.highlight(aNode); - } else { - let win = Services.wm.getMostRecentWindow("navigator:browser"); - this.openPopup(win.gBrowser.selectedBrowser, "end_before", 0, 0, false, false); } }; @@ -176,6 +176,10 @@ var StyleInspector = { */ panel.destroy = function SI_destroy() { + if (!this.cssLogic) + return; + if (this.isOpen()) + this.hideTool(); this.cssLogic = null; this.cssHtmlTree = null; this.removeEventListener("popupshown", SI_popupShown); @@ -184,6 +188,37 @@ var StyleInspector = { Services.obs.notifyObservers(null, "StyleInspector-closed", null); }; + /** + * Dim or undim a panel by setting or removing a dimmed attribute. + * + * @param aState + * true = dim, false = undim + */ + panel.dimTool = function SI_dimTool(aState) + { + if (!this.isOpen()) + return; + + if (aState) { + this.setAttribute("dimmed", "true"); + } else if (this.hasAttribute("dimmed")) { + this.removeAttribute("dimmed"); + } + }; + + panel.showTool = function SI_showTool(aSelection) + { + this.selectNode(aSelection); + let win = Services.wm.getMostRecentWindow("navigator:browser"); + this.openPopup(win.gBrowser.selectedBrowser, "end_before", 0, 0, + false, false); + }; + + panel.hideTool = function SI_hideTool() + { + this.hidePopup(); + }; + /** * Is the Style Inspector initialized? * @returns {Boolean} true or false diff --git a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js index aab2e211630..a68f8915eac 100644 --- a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js +++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js @@ -174,26 +174,18 @@ function styleInspectorClosedFromConsole1() { Services.obs.removeObserver(styleInspectorClosedFromConsole1, "StyleInspector-closed", false); - info("Style Inspector 1 closed"); - Services.obs.addObserver(styleInspectorClosedFromConsole2, - "StyleInspector-closed", false); -} - -function styleInspectorClosedFromConsole2() -{ - Services.obs.removeObserver(styleInspectorClosedFromConsole2, - "StyleInspector-closed", false); - info("Style Inspector 2 closed"); + info("Style Inspector 1 and 2 closed"); executeSoon(cleanUp); } function cleanUp() { - let popupSet = document.getElementById("mainPopupSet"); - ok(!popupSet.lastChild.hasAttribute("hudToolId"), + let panels = document.querySelector("panel[hudToolId]"); + ok(!panels, "all style inspector panels are now detached and ready for garbage collection"); info("cleaning up"); + doc = hudBox = stylePanels = jsterm = null; finishTest(); } diff --git a/browser/devtools/webconsole/HUDService.jsm b/browser/devtools/webconsole/HUDService.jsm index 394ac1f4d54..6956922f8c3 100644 --- a/browser/devtools/webconsole/HUDService.jsm +++ b/browser/devtools/webconsole/HUDService.jsm @@ -4438,7 +4438,7 @@ function JSTermHelper(aJSTerm) if (!errstr) { let stylePanel = StyleInspector.createPanel(); stylePanel.setAttribute("hudToolId", aJSTerm.hudId); - stylePanel.selectNode(aNode); + stylePanel.showTool(aNode); } else { aJSTerm.writeOutput(errstr + "\n", CATEGORY_OUTPUT, SEVERITY_ERROR); } diff --git a/browser/locales/en-US/chrome/browser/styleinspector.properties b/browser/locales/en-US/chrome/browser/styleinspector.properties index 1c4292b6043..22d1bae9ad3 100644 --- a/browser/locales/en-US/chrome/browser/styleinspector.properties +++ b/browser/locales/en-US/chrome/browser/styleinspector.properties @@ -41,3 +41,9 @@ group.Positioning_and_Page_Flow=Positioning and Page Flow group.Borders=Borders group.Lists=Lists group.Effects_and_Other=Effects and Other + +# LOCALIZATION NOTE (style.highlighter.button): These strings are used inside +# html tree of the highlighter for the style inspector button +style.highlighter.button.label=Style +style.highlighter.accesskey=S +style.highlighter.button.tooltip=Inspect element styles From 1432c515c110554b628e6f0245d05973814af331 Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Mon, 26 Sep 2011 13:56:57 -0300 Subject: [PATCH 03/10] Bug 688558 - Context Menu for Inspect Element can Create 2 Highlighters; r=msucan --- browser/devtools/highlighter/inspector.js | 6 +++ .../test/browser_inspector_initialization.js | 48 +++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/browser/devtools/highlighter/inspector.js b/browser/devtools/highlighter/inspector.js index 6c0fadd2529..7857b339aad 100644 --- a/browser/devtools/highlighter/inspector.js +++ b/browser/devtools/highlighter/inspector.js @@ -790,6 +790,12 @@ var InspectorUI = { */ openInspectorUI: function IUI_openInspectorUI(aNode) { + // InspectorUI is already up and running. Lock a node if asked (via context). + if (this.treeLoaded && this.highlighter && aNode) { + this.inspectNode(aNode); + this.stopInspecting(); + return; + } // Observer used to inspect the specified element from content after the // inspector UI has been opened. function inspectObserver(aElement) { diff --git a/browser/devtools/highlighter/test/browser_inspector_initialization.js b/browser/devtools/highlighter/test/browser_inspector_initialization.js index 657d32f42aa..52103225be9 100644 --- a/browser/devtools/highlighter/test/browser_inspector_initialization.js +++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js @@ -38,6 +38,7 @@ * ***** END LICENSE BLOCK ***** */ let doc; let salutation; +let closing; function createDocument() { @@ -106,20 +107,59 @@ function runContextMenuTest() function inspectNodesFromContextTest() { Services.obs.removeObserver(inspectNodesFromContextTest, INSPECTOR_NOTIFICATIONS.OPENED, false); - Services.obs.addObserver(finishInspectorTests, INSPECTOR_NOTIFICATIONS.CLOSED, false); + Services.obs.addObserver(openInspectorForContextTest, INSPECTOR_NOTIFICATIONS.CLOSED, false); ok(!InspectorUI.inspecting, "Inspector is not actively highlighting"); is(InspectorUI.selection, salutation, "Inspector is highlighting salutation"); ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); // TODO: These tests depend on the style inspector patches. todo(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open"); - todo(InspectorUI.isDOMPanelOpen, "Inspector DOM Panel is open"); - InspectorUI.closeInspectorUI(true); + executeSoon(function() { + InspectorUI.closeInspectorUI(true); + }); +} + +function openInspectorForContextTest() +{ + Services.obs.removeObserver(openInspectorForContextTest, INSPECTOR_NOTIFICATIONS.CLOSED); + Services.obs.addObserver(inspectNodesFromContextTestWhileOpen, INSPECTOR_NOTIFICATIONS.OPENED, false); + executeSoon(function() { + InspectorUI.openInspectorUI(salutation); + }); +} + +function inspectNodesFromContextTestWhileOpen() +{ + Services.obs.removeObserver(inspectNodesFromContextTestWhileOpen, INSPECTOR_NOTIFICATIONS.OPENED); + Services.obs.addObserver(inspectNodesFromContextTestTrap, INSPECTOR_NOTIFICATIONS.OPENED, false); + Services.obs.addObserver(inspectNodesFromContextTestHighlight, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false); + is(InspectorUI.selection, salutation, "Inspector is highlighting salutation"); + closing = doc.getElementById("closing"); + ok(closing, "we have the closing statement"); + executeSoon(function() { + InspectorUI.openInspectorUI(closing); + }); +} + +function inspectNodesFromContextTestHighlight() +{ + Services.obs.removeObserver(inspectNodesFromContextTestHighlight, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING); + Services.obs.addObserver(finishInspectorTests, INSPECTOR_NOTIFICATIONS.CLOSED, false); + is(InspectorUI.selection, closing, "InspectorUI.selection is header"); + executeSoon(function() { + InspectorUI.closeInspectorUI(true); + }); +} + +function inspectNodesFromContextTestTrap() +{ + Services.obs.removeObserver(inspectNodesFromContextTestTrap, INSPECTOR_NOTIFICATIONS.OPENED); + ok(false, "Inspector UI has been opened again. We Should Not Be Here!"); } function finishInspectorTests() { Services.obs.removeObserver(finishInspectorTests, - INSPECTOR_NOTIFICATIONS.CLOSED, false); + INSPECTOR_NOTIFICATIONS.CLOSED); ok(!InspectorUI.highlighter, "Highlighter is gone"); ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is closed"); From 79c2f842f4d0a40fb5ee968599ed8e445ea847d4 Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Mon, 26 Sep 2011 13:59:23 -0300 Subject: [PATCH 04/10] Bug 650794 - Disable HTML panel and make it use registerTools API; r=msucan --HG-- rename : browser/devtools/highlighter/insideOutBox.js => browser/devtools/highlighter/InsideOutBox.jsm --- browser/base/content/browser.xul | 1 - .../{insideOutBox.js => InsideOutBox.jsm} | 19 +- browser/devtools/highlighter/Makefile.in | 2 + browser/devtools/highlighter/TreePanel.jsm | 694 ++++++++++++++++++ browser/devtools/highlighter/inspector.js | 613 ++-------------- browser/devtools/highlighter/test/Makefile.in | 2 + ...r_inspector_bug_566084_location_changed.js | 7 +- .../test/browser_inspector_editor.js | 72 +- .../test/browser_inspector_iframeTest.js | 2 - .../test/browser_inspector_initialization.js | 31 +- .../test/browser_inspector_tab_switch.js | 61 +- .../test/browser_inspector_treePanel_click.js | 38 +- .../browser_inspector_treePanel_output.js | 19 +- browser/devtools/jar.mn | 1 + browser/devtools/scratchpad/Makefile.in | 10 +- .../en-US/chrome/browser/inspector.properties | 15 + 16 files changed, 942 insertions(+), 645 deletions(-) rename browser/devtools/highlighter/{insideOutBox.js => InsideOutBox.jsm} (97%) create mode 100644 browser/devtools/highlighter/TreePanel.jsm diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 2251cdb944c..dddbf04638d 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -232,7 +232,6 @@ noautohide="true" titlebar="normal" close="true" - onpopuphiding="InspectorUI.closeInspectorUI();" label="&inspectPanelTitle.label;"> diff --git a/browser/devtools/highlighter/insideOutBox.js b/browser/devtools/highlighter/InsideOutBox.jsm similarity index 97% rename from browser/devtools/highlighter/insideOutBox.js rename to browser/devtools/highlighter/InsideOutBox.jsm index f0556a8d33c..fd53647cef5 100644 --- a/browser/devtools/highlighter/insideOutBox.js +++ b/browser/devtools/highlighter/InsideOutBox.jsm @@ -125,6 +125,9 @@ InsideOutBoxView = { * The box object containing the InsideOutBox. Required to add/remove * children during box manipulation (toggling opened or closed). */ + +var EXPORTED_SYMBOLS = ["InsideOutBox"]; + function InsideOutBox(aView, aBox) { this.view = aView; @@ -450,7 +453,7 @@ InsideOutBox.prototype = try { this.box.removeChild(this.rootObjectBox); } catch (exc) { - InspectorUI._log("this.box.removeChild(this.rootObjectBox) FAILS " + + this.view._log("this.box.removeChild(this.rootObjectBox) FAILS " + this.box + " must not contain " + this.rootObjectBox); } } @@ -643,4 +646,18 @@ InsideOutBox.prototype = return node; }, + + /** + * Clean up our mess. + */ + destroy: function IOBox_destroy() + { + delete this.view; + delete this.box; + delete this.rootObject; + delete this.rootObjectBox; + delete this.selectedObjectBox; + delete this.highlightedObjectBox; + delete this.scrollIntoView; + } }; diff --git a/browser/devtools/highlighter/Makefile.in b/browser/devtools/highlighter/Makefile.in index 907231d3cee..51c637574a4 100644 --- a/browser/devtools/highlighter/Makefile.in +++ b/browser/devtools/highlighter/Makefile.in @@ -46,6 +46,8 @@ include $(DEPTH)/config/autoconf.mk EXTRA_JS_MODULES = \ domplate.jsm \ + InsideOutBox.jsm \ + TreePanel.jsm \ $(NULL) ifdef ENABLE_TESTS diff --git a/browser/devtools/highlighter/TreePanel.jsm b/browser/devtools/highlighter/TreePanel.jsm new file mode 100644 index 00000000000..41722a993e7 --- /dev/null +++ b/browser/devtools/highlighter/TreePanel.jsm @@ -0,0 +1,694 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** 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 Mozilla Tree Panel. + * + * 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): + * Rob Campbell (original author) + * Mihai Șucan + * Julian Viereck + * Paul Rouget + * Kyle Simpson + * + * 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 ***** */ + +const Cu = Components.utils; + +Cu.import("resource:///modules/domplate.jsm"); +Cu.import("resource:///modules/InsideOutBox.jsm"); +Cu.import("resource:///modules/Services.jsm"); + +var EXPORTED_SYMBOLS = ["TreePanel"]; + +/** + * TreePanel + * A container for the Inspector's HTML Tree Panel widget constructor function. + * @param aContext nsIDOMWindow (xulwindow) + * @param aIUI global InspectorUI object + */ +function TreePanel(aContext, aIUI) { + this._init(aContext, aIUI); +}; + +TreePanel.prototype = { + showTextNodesWithWhitespace: false, + id: "treepanel", // DO NOT LOCALIZE + + /** + * container. + * @returns xul:panel element + */ + get container() + { + return this.document.getElementById("inspector-tree-panel"); + }, + + /** + * Main TreePanel boot-strapping method. Initialize the TreePanel with the + * originating context and the InspectorUI global. + * @param aContext nsIDOMWindow (xulwindow) + * @param aIUI global InspectorUI object + */ + _init: function TP__init(aContext, aIUI) + { + this.IUI = aIUI; + this.window = aContext; + this.document = this.window.document; + + domplateUtils.setDOM(this.window); + + let isOpen = this.isOpen.bind(this); + + this.registrationObject = { + id: this.id, + label: this.IUI.strings.GetStringFromName("htmlPanel.label"), + tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"), + accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"), + context: this, + get isOpen() isOpen(), + show: this.open, + hide: this.close, + onSelect: this.select, + panel: this.container, + unregister: this.destroy, + }; + this.editingEvents = {}; + + this._boundClose = this.close.bind(this); + this.container.addEventListener("popuphiding", this._boundClose, false); + + // Register the HTML panel with the highlighter + this.IUI.registerTool(this.registrationObject); + }, + + /** + * Initialization function for the TreePanel. + */ + initializeIFrame: function TP_initializeIFrame() + { + if (!this.initializingTreePanel || this.treeLoaded) { + return; + } + this.treeBrowserDocument = this.treeIFrame.contentDocument; + this.treePanelDiv = this.treeBrowserDocument.createElement("div"); + this.treeBrowserDocument.body.appendChild(this.treePanelDiv); + this.treePanelDiv.ownerPanel = this; + this.ioBox = new InsideOutBox(this, this.treePanelDiv); + this.ioBox.createObjectBox(this.IUI.win.document.documentElement); + this.treeLoaded = true; + this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false); + this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false); + delete this.initializingTreePanel; + Services.obs.notifyObservers(null, + this.window.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null); + if (this.IUI.selection) + this.select(this.IUI.selection, true); + }, + + /** + * Open the inspector's tree panel and initialize it. + */ + open: function TP_open() + { + if (this.initializingTreePanel && !this.treeLoaded) { + return; + } + + this.initializingTreePanel = true; + this.container.hidden = false; + + this.treeIFrame = this.document.getElementById("inspector-tree-iframe"); + if (!this.treeIFrame) { + let resizerBox = this.document.getElementById("tree-panel-resizer-box"); + this.treeIFrame = this.document.createElement("iframe"); + this.treeIFrame.setAttribute("id", "inspector-tree-iframe"); + this.treeIFrame.setAttribute("flex", "1"); + this.treeIFrame.setAttribute("type", "content"); + this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox); + } + + let self = this; + this.container.addEventListener("popupshown", function treePanelShown() { + self.container.removeEventListener("popupshown", + treePanelShown, false); + + self.treeIFrame.addEventListener("load", + function loadedInitializeTreePanel() { + self.treeIFrame.removeEventListener("load", + loadedInitializeTreePanel, true); + self.initializeIFrame(); + }, true); + + let src = self.treeIFrame.getAttribute("src"); + if (src != "chrome://browser/content/inspector.html") { + self.treeIFrame.setAttribute("src", + "chrome://browser/content/inspector.html"); + } else { + self.treeIFrame.contentWindow.location.reload(); + } + }, false); + + const panelWidthRatio = 7 / 8; + const panelHeightRatio = 1 / 5; + + let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio); + let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio); + let y = Math.min(this.document.defaultView.screen.availHeight - height, + this.IUI.win.innerHeight); + + this.container.openPopup(this.browser, "overlap", 0, 0, + false, false); + + this.container.moveTo(80, y); + this.container.sizeTo(width, height); + }, + + /** + * Close the TreePanel. + */ + close: function TP_close() + { + if (this.treePanelDiv) { + this.treePanelDiv.ownerPanel = null; + let parent = this.treePanelDiv.parentNode; + parent.removeChild(this.treePanelDiv); + delete this.treePanelDiv; + delete this.treeBrowserDocument; + } + + this.treeLoaded = false; + this.container.hidePopup(); + }, + + /** + * Is the TreePanel open? + * @returns boolean + */ + isOpen: function TP_isOpen() + { + return this.treeLoaded && this.container.state == "open"; + }, + + /** + * Create the ObjectBox for the given object. + * @param object nsIDOMNode + * @param isRoot boolean - Is this the root object? + * @returns InsideOutBox + */ + createObjectBox: function TP_createObjectBox(object, isRoot) + { + let tag = domplateUtils.getNodeTag(object); + if (tag) + return tag.replace({object: object}, this.treeBrowserDocument); + }, + + getParentObject: function TP_getParentObject(node) + { + let parentNode = node ? node.parentNode : null; + + if (!parentNode) { + // Documents have no parentNode; Attr, Document, DocumentFragment, Entity, + // and Notation. top level windows have no parentNode + if (node && node == this.window.Node.DOCUMENT_NODE) { + // document type + if (node.defaultView) { + let embeddingFrame = node.defaultView.frameElement; + if (embeddingFrame) + return embeddingFrame.parentNode; + } + } + // a Document object without a parentNode or window + return null; // top level has no parent + } + + if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) { + if (parentNode.defaultView) { + return parentNode.defaultView.frameElement; + } + // parent is document element, but no window at defaultView. + return null; + } + + if (!parentNode.localName) + return null; + + return parentNode; + }, + + getChildObject: function TP_getChildObject(node, index, previousSibling) + { + if (!node) + return null; + + if (node.contentDocument) { + // then the node is a frame + if (index == 0) { + return node.contentDocument.documentElement; // the node's HTMLElement + } + return null; + } + + if (node instanceof this.window.GetSVGDocument) { + let svgDocument = node.getSVGDocument(); + if (svgDocument) { + // then the node is a frame + if (index == 0) { + return svgDocument.documentElement; // the node's SVGElement + } + return null; + } + } + + let child = null; + if (previousSibling) // then we are walking + child = this.getNextSibling(previousSibling); + else + child = this.getFirstChild(node); + + if (this.showTextNodesWithWhitespace) + return child; + + for (; child; child = this.getNextSibling(child)) { + if (!domplateUtils.isWhitespaceText(child)) + return child; + } + + return null; // we have no children worth showing. + }, + + getFirstChild: function TP_getFirstChild(node) + { + this.treeWalker = node.ownerDocument.createTreeWalker(node, + this.window.NodeFilter.SHOW_ALL, null, false); + return this.treeWalker.firstChild(); + }, + + getNextSibling: function TP_getNextSibling(node) + { + let next = this.treeWalker.nextSibling(); + + if (!next) + delete this.treeWalker; + + return next; + }, + + ///////////////////////////////////////////////////////////////////// + // Event Handling + + /** + * Handle click events in the html tree panel. + * @param aEvent + * The mouse event. + */ + onTreeClick: function TP_onTreeClick(aEvent) + { + let node; + let target = aEvent.target; + let hitTwisty = false; + if (this.hasClass(target, "twisty")) { + node = this.getRepObject(aEvent.target.nextSibling); + hitTwisty = true; + } else { + node = this.getRepObject(aEvent.target); + } + + if (node) { + if (hitTwisty) { + this.ioBox.toggleObject(node); + } else { + if (this.IUI.inspecting) { + this.IUI.stopInspecting(true); + } else { + this.IUI.select(node, true, false); + this.IUI.highlighter.highlightNode(node); + } + } + } + }, + + /** + * Handle double-click events in the html tree panel. + * (double-clicking an attribute value allows it to be edited) + * @param aEvent + * The mouse event. + */ + onTreeDblClick: function TP_onTreeDblClick(aEvent) + { + // if already editing an attribute value, double-clicking elsewhere + // in the tree is the same as a click, which dismisses the editor + if (this.editingContext) + this.closeEditor(); + + let target = aEvent.target; + + if (this.hasClass(target, "nodeValue")) { + let repObj = this.getRepObject(target); + let attrName = target.getAttribute("data-attributeName"); + let attrVal = target.innerHTML; + + this.editAttributeValue(target, repObj, attrName, attrVal); + } + }, + + /** + * Starts the editor for an attribute value. + * @param aAttrObj + * The DOM object representing the attribute value in the HTML Tree + * @param aRepObj + * The original DOM (target) object being inspected/edited + * @param aAttrName + * The name of the attribute being edited + * @param aAttrVal + * The current value of the attribute being edited + */ + editAttributeValue: + function TP_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal) + { + let editor = this.treeBrowserDocument.getElementById("attribute-editor"); + let editorInput = + this.treeBrowserDocument.getElementById("attribute-editor-input"); + let attrDims = aAttrObj.getBoundingClientRect(); + // figure out actual viewable viewport dimensions (sans scrollbars) + let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth; + let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight; + + // saves the editing context for use when the editor is saved/closed + this.editingContext = { + attrObj: aAttrObj, + repObj: aRepObj, + attrName: aAttrName + }; + + // highlight attribute-value node in tree while editing + this.addClass(aAttrObj, "editingAttributeValue"); + + // show the editor + this.addClass(editor, "editing"); + + // offset the editor below the attribute-value node being edited + let editorVeritcalOffset = 2; + + // keep the editor comfortably within the bounds of the viewport + let editorViewportBoundary = 5; + + // outer editor is sized based on the box inside it + editorInput.style.width = Math.min(attrDims.width, viewportWidth - + editorViewportBoundary) + "px"; + editorInput.style.height = Math.min(attrDims.height, viewportHeight - + editorViewportBoundary) + "px"; + let editorDims = editor.getBoundingClientRect(); + + // calculate position for the editor according to the attribute node + let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX - + // center the editor against the attribute value + ((editorDims.width - attrDims.width) / 2); + let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY + + attrDims.height + editorVeritcalOffset; + + // but, make sure the editor stays within the visible viewport + editorLeft = Math.max(0, Math.min( + (this.treeIFrame.contentWindow.scrollX + + viewportWidth - editorDims.width), + editorLeft) + ); + editorTop = Math.max(0, Math.min( + (this.treeIFrame.contentWindow.scrollY + + viewportHeight - editorDims.height), + editorTop) + ); + + // position the editor + editor.style.left = editorLeft + "px"; + editor.style.top = editorTop + "px"; + + // set and select the text + editorInput.value = aAttrVal; + editorInput.select(); + + // listen for editor specific events + this.bindEditorEvent(editor, "click", function(aEvent) { + aEvent.stopPropagation(); + }); + this.bindEditorEvent(editor, "dblclick", function(aEvent) { + aEvent.stopPropagation(); + }); + this.bindEditorEvent(editor, "keypress", + this.handleEditorKeypress.bind(this)); + + // event notification + Services.obs.notifyObservers(null, this.window.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, + null); + }, + + /** + * Handle binding an event handler for the editor. + * (saves the callback for easier unbinding later) + * @param aEditor + * The DOM object for the editor + * @param aEventName + * The name of the event to listen for + * @param aEventCallback + * The callback to bind to the event (and also to save for later + * unbinding) + */ + bindEditorEvent: + function TP_bindEditorEvent(aEditor, aEventName, aEventCallback) + { + this.editingEvents[aEventName] = aEventCallback; + aEditor.addEventListener(aEventName, aEventCallback, false); + }, + + /** + * Handle unbinding an event handler from the editor. + * (unbinds the previously bound and saved callback) + * @param aEditor + * The DOM object for the editor + * @param aEventName + * The name of the event being listened for + */ + unbindEditorEvent: function TP_unbindEditorEvent(aEditor, aEventName) + { + aEditor.removeEventListener(aEventName, this.editingEvents[aEventName], + false); + this.editingEvents[aEventName] = null; + }, + + /** + * Handle keypress events in the editor. + * @param aEvent + * The keyboard event. + */ + handleEditorKeypress: function TP_handleEditorKeypress(aEvent) + { + if (aEvent.which == this.window.KeyEvent.DOM_VK_RETURN) { + this.saveEditor(); + } else if (aEvent.keyCode == this.window.KeyEvent.DOM_VK_ESCAPE) { + this.closeEditor(); + } + }, + + /** + * Close the editor and cleanup. + */ + closeEditor: function TP_closeEditor() + { + let editor = this.treeBrowserDocument.getElementById("attribute-editor"); + let editorInput = + this.treeBrowserDocument.getElementById("attribute-editor-input"); + + // remove highlight from attribute-value node in tree + this.removeClass(this.editingContext.attrObj, "editingAttributeValue"); + + // hide editor + this.removeClass(editor, "editing"); + + // stop listening for editor specific events + this.unbindEditorEvent(editor, "click"); + this.unbindEditorEvent(editor, "dblclick"); + this.unbindEditorEvent(editor, "keypress"); + + // clean up after the editor + editorInput.value = ""; + editorInput.blur(); + this.editingContext = null; + this.editingEvents = {}; + + // event notification + Services.obs.notifyObservers(null, this.window.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, + null); + }, + + /** + * Commit the edits made in the editor, then close it. + */ + saveEditor: function TP_saveEditor() + { + let editorInput = + this.treeBrowserDocument.getElementById("attribute-editor-input"); + + // set the new attribute value on the original target DOM element + this.editingContext.repObj.setAttribute(this.editingContext.attrName, + editorInput.value); + + // update the HTML tree attribute value + this.editingContext.attrObj.innerHTML = editorInput.value; + + this.IUI.isDirty = true; + + // event notification + Services.obs.notifyObservers(null, this.window.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, + null); + + this.closeEditor(); + }, + + /** + * Simple tree select method. + * @param aNode the DOM node in the content document to select. + * @param aScroll boolean scroll to the visible node? + */ + select: function TP_select(aNode, aScroll) + { + if (this.ioBox) + this.ioBox.select(aNode, true, true, aScroll); + }, + + /////////////////////////////////////////////////////////////////////////// + //// Utility functions + + /** + * Does the given object have a class attribute? + * @param aNode + * the DOM node. + * @param aClass + * The class string. + * @returns boolean + */ + hasClass: function TP_hasClass(aNode, aClass) + { + if (!(aNode instanceof this.window.Element)) + return false; + return aNode.classList.contains(aClass); + }, + + /** + * Add the class name to the given object. + * @param aNode + * the DOM node. + * @param aClass + * The class string. + */ + addClass: function TP_addClass(aNode, aClass) + { + if (aNode instanceof this.window.Element) + aNode.classList.add(aClass); + }, + + /** + * Remove the class name from the given object + * @param aNode + * the DOM node. + * @param aClass + * The class string. + */ + removeClass: function TP_removeClass(aNode, aClass) + { + if (aNode instanceof this.window.Element) + aNode.classList.remove(aClass); + }, + + /** + * Get the "repObject" from the HTML panel's domplate-constructed DOM node. + * In this system, a "repObject" is the Object being Represented by the box + * object. It is the "real" object that we're building our facade around. + * + * @param element + * The element in the HTML panel the user clicked. + * @returns either a real node or null + */ + getRepObject: function TP_getRepObject(element) + { + let target = null; + for (let child = element; child; child = child.parentNode) { + if (this.hasClass(child, "repTarget")) + target = child; + + if (child.repObject) { + if (!target && this.hasClass(child.repObject, "repIgnore")) + break; + else + return child.repObject; + } + } + return null; + }, + + /** + * Destructor function. Cleanup. + */ + destroy: function TP_destroy() + { + if (this.isOpen()) { + this.close(); + } + + domplateUtils.setDOM(null); + + delete this.treeWalker; + + if (this.treePanelDiv) { + this.treePanelDiv.ownerPanel = null; + let parent = this.treePanelDiv.parentNode; + parent.removeChild(this.treePanelDiv); + delete this.treePanelDiv; + delete this.treeBrowserDocument; + } + + if (this.treeIFrame) { + this.treeIFrame.removeEventListener("dblclick", this.onTreeDblClick, false); + this.treeIFrame.removeEventListener("click", this.onTreeClick, false); + let parent = this.treeIFrame.parentNode; + parent.removeChild(this.treeIFrame); + delete this.treeIFrame; + } + + if (this.ioBox) { + this.ioBox.destroy(); + delete this.ioBox; + } + + this.container.removeEventListener("popuphiding", this._boundClose, false); + delete this._boundClose; + } +}; + diff --git a/browser/devtools/highlighter/inspector.js b/browser/devtools/highlighter/inspector.js index 7857b339aad..ec3746fa362 100644 --- a/browser/devtools/highlighter/inspector.js +++ b/browser/devtools/highlighter/inspector.js @@ -26,7 +26,7 @@ * Mihai Șucan * Julian Viereck * Paul Rouget - * Kyle Simpson + * Kyle Simpson * * 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 @@ -43,8 +43,6 @@ * ***** END LICENSE BLOCK ***** */ #endif -#include insideOutBox.js - const INSPECTOR_INVISIBLE_ELEMENTS = { "head": true, "base": true, @@ -72,6 +70,9 @@ const INSPECTOR_NOTIFICATIONS = { // Fires once the Inspector is closed. CLOSED: "inspector-closed", + // Fires when the Tree Panel is opened and initialized. + TREEPANELREADY: "inspector-treepanel-ready", + // Event notifications for the attribute-value editor EDITOR_OPENED: "inspector-editor-opened", EDITOR_CLOSED: "inspector-editor-closed", @@ -554,9 +555,8 @@ var InspectorUI = { browser: null, tools: {}, toolEvents: {}, - showTextNodesWithWhitespace: false, inspecting: false, - treeLoaded: false, + treePanelEnabled: true, get enabled() { return gPrefService.getBoolPref("devtools.inspector.enabled"); @@ -571,7 +571,7 @@ var InspectorUI = { */ toggleInspectorUI: function IUI_toggleInspectorUI(aEvent) { - if (this.isTreePanelOpen) { + if (this.isInspectorOpen) { this.closeInspectorUI(); } else { this.openInspectorUI(); @@ -592,13 +592,13 @@ var InspectorUI = { }, /** - * Is the tree panel open? + * Is the inspector UI open? Simply check if the toolbar is visible or not. * * @returns boolean */ - get isTreePanelOpen() + get isInspectorOpen() { - return this.treePanel && this.treePanel.state == "open"; + return this.toolbar && !this.toolbar.hidden && this.highlighter; }, /** @@ -610,176 +610,6 @@ var InspectorUI = { return doc.documentElement ? doc.documentElement.lastElementChild : null; }, - initializeTreePanel: function IUI_initializeTreePanel() - { - this.treeBrowserDocument = this.treeIFrame.contentDocument; - this.treePanelDiv = this.treeBrowserDocument.createElement("div"); - this.treeBrowserDocument.body.appendChild(this.treePanelDiv); - this.treePanelDiv.ownerPanel = this; - this.ioBox = new InsideOutBox(this, this.treePanelDiv); - this.ioBox.createObjectBox(this.win.document.documentElement); - this.treeLoaded = true; - this.editingContext = null; - this.editingEvents = {}; - - // initialize the highlighter - this.initializeHighlighter(); - }, - - /** - * Open the inspector's tree panel and initialize it. - */ - openTreePanel: function IUI_openTreePanel() - { - if (!this.treePanel) { - this.treePanel = document.getElementById("inspector-tree-panel"); - this.treePanel.hidden = false; - } - - this.treeIFrame = document.getElementById("inspector-tree-iframe"); - if (!this.treeIFrame) { - let resizerBox = document.getElementById("tree-panel-resizer-box"); - this.treeIFrame = document.createElement("iframe"); - this.treeIFrame.setAttribute("id", "inspector-tree-iframe"); - this.treeIFrame.setAttribute("flex", "1"); - this.treeIFrame.setAttribute("type", "content"); - this.treeIFrame.setAttribute("onclick", "InspectorUI.onTreeClick(event)"); - this.treeIFrame.setAttribute("ondblclick", "InspectorUI.onTreeDblClick(event);"); - this.treeIFrame = this.treePanel.insertBefore(this.treeIFrame, resizerBox); - } - - this.treePanel.addEventListener("popupshown", function treePanelShown() { - InspectorUI.treePanel.removeEventListener("popupshown", - treePanelShown, false); - - InspectorUI.treeIFrame.addEventListener("load", - function loadedInitializeTreePanel() { - InspectorUI.treeIFrame.removeEventListener("load", - loadedInitializeTreePanel, true); - InspectorUI.initializeTreePanel(); - }, true); - - let src = InspectorUI.treeIFrame.getAttribute("src"); - if (src != "chrome://browser/content/inspector.html") { - InspectorUI.treeIFrame.setAttribute("src", - "chrome://browser/content/inspector.html"); - } else { - InspectorUI.treeIFrame.contentWindow.location.reload(); - } - - }, false); - - const panelWidthRatio = 7 / 8; - const panelHeightRatio = 1 / 5; - - let width = parseInt(this.win.outerWidth * panelWidthRatio); - let height = parseInt(this.win.outerHeight * panelHeightRatio); - let y = Math.min(window.screen.availHeight - height, this.win.innerHeight); - - this.treePanel.openPopup(this.browser, "overlap", 0, 0, - false, false); - - this.treePanel.moveTo(80, y); - this.treePanel.sizeTo(width, height); - }, - - createObjectBox: function IUI_createObjectBox(object, isRoot) - { - let tag = this.domplateUtils.getNodeTag(object); - if (tag) - return tag.replace({object: object}, this.treeBrowserDocument); - }, - - getParentObject: function IUI_getParentObject(node) - { - let parentNode = node ? node.parentNode : null; - - if (!parentNode) { - // Documents have no parentNode; Attr, Document, DocumentFragment, Entity, - // and Notation. top level windows have no parentNode - if (node && node == Node.DOCUMENT_NODE) { - // document type - if (node.defaultView) { - let embeddingFrame = node.defaultView.frameElement; - if (embeddingFrame) - return embeddingFrame.parentNode; - } - } - // a Document object without a parentNode or window - return null; // top level has no parent - } - - if (parentNode.nodeType == Node.DOCUMENT_NODE) { - if (parentNode.defaultView) { - return parentNode.defaultView.frameElement; - } - // parent is document element, but no window at defaultView. - return null; - } - if (!parentNode.localName) { - return null; - } - return parentNode; - }, - - getChildObject: function IUI_getChildObject(node, index, previousSibling) - { - if (!node) - return null; - - if (node.contentDocument) { - // then the node is a frame - if (index == 0) { - return node.contentDocument.documentElement; // the node's HTMLElement - } - return null; - } - - if (node instanceof GetSVGDocument) { - let svgDocument = node.getSVGDocument(); - if (svgDocument) { - // then the node is a frame - if (index == 0) { - return svgDocument.documentElement; // the node's SVGElement - } - return null; - } - } - - let child = null; - if (previousSibling) // then we are walking - child = this.getNextSibling(previousSibling); - else - child = this.getFirstChild(node); - - if (this.showTextNodesWithWhitespace) - return child; - - for (; child; child = this.getNextSibling(child)) { - if (!this.domplateUtils.isWhitespaceText(child)) - return child; - } - - return null; // we have no children worth showing. - }, - - getFirstChild: function IUI_getFirstChild(node) - { - this.treeWalker = node.ownerDocument.createTreeWalker(node, - NodeFilter.SHOW_ALL, null, false); - return this.treeWalker.firstChild(); - }, - - getNextSibling: function IUI_getNextSibling(node) - { - let next = this.treeWalker.nextSibling(); - - if (!next) - delete this.treeWalker; - - return next; - }, - /** * Open inspector UI and HTML tree. Add listeners for document scrolling, * resize, tabContainer.TabSelect and others. If a node is provided, then @@ -791,11 +621,12 @@ var InspectorUI = { openInspectorUI: function IUI_openInspectorUI(aNode) { // InspectorUI is already up and running. Lock a node if asked (via context). - if (this.treeLoaded && this.highlighter && aNode) { + if (this.isInspectorOpen && aNode) { this.inspectNode(aNode); this.stopInspecting(); return; } + // Observer used to inspect the specified element from content after the // inspector UI has been opened. function inspectObserver(aElement) { @@ -805,6 +636,7 @@ var InspectorUI = { this.inspectNode(aElement); this.stopInspecting(); }; + var boundInspectObserver = inspectObserver.bind(this, aNode); if (aNode) { @@ -821,17 +653,20 @@ var InspectorUI = { this.initTools(); - if (!this.domplate) { - Cu.import("resource:///modules/domplate.jsm", this); - this.domplateUtils.setDOM(window); + if (!this.TreePanel && this.treePanelEnabled) { + Cu.import("resource:///modules/TreePanel.jsm", this); + this.treePanel = new this.TreePanel(window, this); } - this.openTreePanel(); - this.toolbar.hidden = false; - this.inspectCmd.setAttribute("checked", true); + this.inspectMenuitem.setAttribute("checked", true); + + this.isDirty = false; gBrowser.addProgressListener(InspectorProgressListener); + + // initialize the highlighter + this.initializeHighlighter(); }, /** @@ -885,11 +720,13 @@ var InspectorUI = { if (selectedNode) { this.inspectNode(selectedNode); } + this.isDirty = InspectorStore.getValue(this.winID, "isDirty"); } else { // First time inspecting, set state to no selection + live inspection. InspectorStore.addStore(this.winID); InspectorStore.setValue(this.winID, "selectedNode", null); InspectorStore.setValue(this.winID, "inspecting", true); + InspectorStore.setValue(this.winID, "isDirty", this.isDirty); this.win.addEventListener("pagehide", this, true); } }, @@ -908,8 +745,8 @@ var InspectorUI = { { // if currently editing an attribute value, closing the // highlighter/HTML panel dismisses the editor - if (this.editingContext) - this.closeEditor(); + if (this.treePanel && this.treePanel.editingContext) + this.treePanel.closeEditor(); if (this.closing || !this.win || !this.browser) { return; @@ -930,6 +767,7 @@ var InspectorUI = { this.selection); } InspectorStore.setValue(this.winID, "inspecting", this.inspecting); + InspectorStore.setValue(this.winID, "isDirty", this.isDirty); } if (InspectorStore.isEmpty()) { @@ -948,44 +786,18 @@ var InspectorUI = { this.highlighter = null; } - if (this.treePanelDiv) { - this.treePanelDiv.ownerPanel = null; - let parent = this.treePanelDiv.parentNode; - parent.removeChild(this.treePanelDiv); - delete this.treePanelDiv; - delete this.treeBrowserDocument; - } - - if (this.treeIFrame) { - let parent = this.treeIFrame.parentNode; - parent.removeChild(this.treeIFrame); - delete this.treeIFrame; - } - delete this.ioBox; - - if (this.domplate) { - this.domplateUtils.setDOM(null); - delete this.domplate; - delete this.HTMLTemplates; - delete this.domplateUtils; - } - - this.inspectCmd.setAttribute("checked", false); + this.inspectMenuitem.setAttribute("checked", false); this.browser = this.win = null; // null out references to browser and window this.winID = null; this.selection = null; - this.treeLoaded = false; + this.closing = false; + this.isDirty = false; - this.treePanel.addEventListener("popuphidden", function treePanelHidden() { - this.removeEventListener("popuphidden", treePanelHidden, false); - - InspectorUI.closing = false; - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null); - }, false); - - this.treePanel.hidePopup(); delete this.treePanel; delete this.stylePanel; + delete this.toolbar; + delete this.TreePanel; + Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null); }, /** @@ -996,10 +808,10 @@ var InspectorUI = { { // if currently editing an attribute value, starting // "live inspection" mode closes the editor - if (this.editingContext) - this.closeEditor(); + if (this.treePanel && this.treePanel.editingContext) + this.treePanel.closeEditor(); - document.getElementById("inspector-inspect-toolbutton").checked = true; + this.inspectToolbutton.checked = true; this.attachPageListeners(); this.inspecting = true; this.toolsDim(true); @@ -1018,7 +830,7 @@ var InspectorUI = { return; } - document.getElementById("inspector-inspect-toolbutton").checked = false; + this.inspectToolbutton.checked = false; this.detachPageListeners(); this.inspecting = false; this.toolsDim(false); @@ -1036,15 +848,15 @@ var InspectorUI = { * node to inspect * @param forceUpdate * force an update? - * @param aScroll - * force scroll? + * @param aScroll boolean + * scroll the tree panel? */ select: function IUI_select(aNode, forceUpdate, aScroll) { // if currently editing an attribute value, using the // highlighter dismisses the editor - if (this.editingContext) - this.closeEditor(); + if (this.treePanel && this.treePanel.editingContext) + this.treePanel.closeEditor(); if (!aNode) aNode = this.defaultSelection; @@ -1054,10 +866,9 @@ var InspectorUI = { if (!this.inspecting) { this.highlighter.highlightNode(this.selection); } - this.ioBox.select(this.selection, true, true, aScroll); } - this.toolsSelect(); + this.toolsSelect(aScroll); }, ///////////////////////////////////////////////////////////////////////// @@ -1093,7 +904,7 @@ var InspectorUI = { switch (event.type) { case "TabSelect": winID = this.getWindowID(gBrowser.selectedBrowser.contentWindow); - if (this.isTreePanelOpen && winID != this.winID) { + if (this.isInspectorOpen && winID != this.winID) { this.closeInspectorUI(true); inspectorClosed = true; } @@ -1148,265 +959,6 @@ var InspectorUI = { } }, - /** - * Handle click events in the html tree panel. - * @param aEvent - * The mouse event. - */ - onTreeClick: function IUI_onTreeClick(aEvent) - { - // if currently editing an attribute value, clicking outside - // the editor dismisses the editor - if (this.editingContext) { - this.closeEditor(); - - // clicking outside the editor ONLY closes the editor - // so, cancel the rest of the processing of this event - aEvent.preventDefault(); - return; - } - - let node; - let target = aEvent.target; - let hitTwisty = false; - if (this.hasClass(target, "twisty")) { - node = this.getRepObject(aEvent.target.nextSibling); - hitTwisty = true; - } else { - node = this.getRepObject(aEvent.target); - } - - if (node) { - if (hitTwisty) { - this.ioBox.toggleObject(node); - } else { - if (this.inspecting) { - this.toolsSelect(); - this.stopInspecting(true); - } else { - this.select(node, true, false); - this.highlighter.highlightNode(node); - this.toolsSelect(); - } - } - } - }, - - /** - * Handle double-click events in the html tree panel. - * (double-clicking an attribute value allows it to be edited) - * @param aEvent - * The mouse event. - */ - onTreeDblClick: function IUI_onTreeDblClick(aEvent) - { - // if already editing an attribute value, double-clicking elsewhere - // in the tree is the same as a click, which dismisses the editor - if (this.editingContext) - this.closeEditor(); - - let target = aEvent.target; - if (this.hasClass(target, "nodeValue")) { - let repObj = this.getRepObject(target); - let attrName = target.getAttribute("data-attributeName"); - let attrVal = target.innerHTML; - - this.editAttributeValue(target, repObj, attrName, attrVal); - } - }, - - /** - * Starts the editor for an attribute value. - * @param aAttrObj - * The DOM object representing the attribute value in the HTML Tree - * @param aRepObj - * The original DOM (target) object being inspected/edited - * @param aAttrName - * The name of the attribute being edited - * @param aAttrVal - * The current value of the attribute being edited - */ - editAttributeValue: - function IUI_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal) - { - let editor = this.treeBrowserDocument.getElementById("attribute-editor"); - let editorInput = - this.treeBrowserDocument.getElementById("attribute-editor-input"); - let attrDims = aAttrObj.getBoundingClientRect(); - // figure out actual viewable viewport dimensions (sans scrollbars) - let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth; - let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight; - - // saves the editing context for use when the editor is saved/closed - this.editingContext = { - attrObj: aAttrObj, - repObj: aRepObj, - attrName: aAttrName - }; - - // highlight attribute-value node in tree while editing - this.addClass(aAttrObj, "editingAttributeValue"); - - // show the editor - this.addClass(editor, "editing"); - - // offset the editor below the attribute-value node being edited - let editorVeritcalOffset = 2; - - // keep the editor comfortably within the bounds of the viewport - let editorViewportBoundary = 5; - - // outer editor is sized based on the box inside it - editorInput.style.width = Math.min(attrDims.width, viewportWidth - - editorViewportBoundary) + "px"; - editorInput.style.height = Math.min(attrDims.height, viewportHeight - - editorViewportBoundary) + "px"; - let editorDims = editor.getBoundingClientRect(); - - // calculate position for the editor according to the attribute node - let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX - - // center the editor against the attribute value - ((editorDims.width - attrDims.width) / 2); - let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY + - attrDims.height + editorVeritcalOffset; - - // but, make sure the editor stays within the visible viewport - editorLeft = Math.max(0, Math.min( - (this.treeIFrame.contentWindow.scrollX + - viewportWidth - editorDims.width), - editorLeft) - ); - editorTop = Math.max(0, Math.min( - (this.treeIFrame.contentWindow.scrollY + - viewportHeight - editorDims.height), - editorTop) - ); - - // position the editor - editor.style.left = editorLeft + "px"; - editor.style.top = editorTop + "px"; - - // set and select the text - editorInput.value = aAttrVal; - editorInput.select(); - - // listen for editor specific events - this.bindEditorEvent(editor, "click", function(aEvent) { - aEvent.stopPropagation(); - }); - this.bindEditorEvent(editor, "dblclick", function(aEvent) { - aEvent.stopPropagation(); - }); - this.bindEditorEvent(editor, "keypress", - this.handleEditorKeypress.bind(this)); - - // event notification - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, - null); - }, - - /** - * Handle binding an event handler for the editor. - * (saves the callback for easier unbinding later) - * @param aEditor - * The DOM object for the editor - * @param aEventName - * The name of the event to listen for - * @param aEventCallback - * The callback to bind to the event (and also to save for later - * unbinding) - */ - bindEditorEvent: - function IUI_bindEditorEvent(aEditor, aEventName, aEventCallback) - { - this.editingEvents[aEventName] = aEventCallback; - aEditor.addEventListener(aEventName, aEventCallback, false); - }, - - /** - * Handle unbinding an event handler from the editor. - * (unbinds the previously bound and saved callback) - * @param aEditor - * The DOM object for the editor - * @param aEventName - * The name of the event being listened for - */ - unbindEditorEvent: function IUI_unbindEditorEvent(aEditor, aEventName) - { - aEditor.removeEventListener(aEventName, this.editingEvents[aEventName], - false); - this.editingEvents[aEventName] = null; - }, - - /** - * Handle keypress events in the editor. - * @param aEvent - * The keyboard event. - */ - handleEditorKeypress: function IUI_handleEditorKeypress(aEvent) - { - if (aEvent.which == KeyEvent.DOM_VK_RETURN) { - this.saveEditor(); - } else if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) { - this.closeEditor(); - } - }, - - /** - * Close the editor and cleanup. - */ - closeEditor: function IUI_closeEditor() - { - let editor = this.treeBrowserDocument.getElementById("attribute-editor"); - let editorInput = - this.treeBrowserDocument.getElementById("attribute-editor-input"); - - // remove highlight from attribute-value node in tree - this.removeClass(this.editingContext.attrObj, "editingAttributeValue"); - - // hide editor - this.removeClass(editor, "editing"); - - // stop listening for editor specific events - this.unbindEditorEvent(editor, "click"); - this.unbindEditorEvent(editor, "dblclick"); - this.unbindEditorEvent(editor, "keypress"); - - // clean up after the editor - editorInput.value = ""; - editorInput.blur(); - this.editingContext = null; - this.editingEvents = {}; - - // event notification - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, - null); - }, - - /** - * Commit the edits made in the editor, then close it. - */ - saveEditor: function IUI_saveEditor() - { - let editorInput = - this.treeBrowserDocument.getElementById("attribute-editor-input"); - - // set the new attribute value on the original target DOM element - this.editingContext.repObj.setAttribute(this.editingContext.attrName, - editorInput.value); - - // update the HTML tree attribute value - this.editingContext.attrObj.innerHTML = editorInput.value; - - this.isDirty = true; - - // event notification - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, - null); - - this.closeEditor(); - }, - /** * Attach event listeners to content window and child windows to enable * highlighting and click to stop inspection. @@ -1511,47 +1063,6 @@ var InspectorUI = { return [borderTop + paddingTop, borderLeft + paddingLeft]; }, - /** - * Does the given object have a class attribute? - * @param aNode - * the DOM node. - * @param aClass - * The class string. - * @returns boolean - */ - hasClass: function IUI_hasClass(aNode, aClass) - { - if (!(aNode instanceof Element)) - return false; - return aNode.classList.contains(aClass); - }, - - /** - * Add the class name to the given object. - * @param aNode - * the DOM node. - * @param aClass - * The class string. - */ - addClass: function IUI_addClass(aNode, aClass) - { - if (aNode instanceof Element) - aNode.classList.add(aClass); - }, - - /** - * Remove the class name from the given object - * @param aNode - * the DOM node. - * @param aClass - * The class string. - */ - removeClass: function IUI_removeClass(aNode, aClass) - { - if (aNode instanceof Element) - aNode.classList.remove(aClass); - }, - /** * Retrieve the unique ID of a window object. * @@ -1574,32 +1085,6 @@ var InspectorUI = { return util.currentInnerWindowID; }, - /** - * Get the "repObject" from the HTML panel's domplate-constructed DOM node. - * In this system, a "repObject" is the Object being Represented by the box - * object. It is the "real" object that we're building our facade around. - * - * @param element - * The element in the HTML panel the user clicked. - * @returns either a real node or null - */ - getRepObject: function IUI_getRepObject(element) - { - let target = null; - for (let child = element; child; child = child.parentNode) { - if (this.hasClass(child, "repTarget")) - target = child; - - if (child.repObject) { - if (!target && this.hasClass(child.repObject, "repIgnore")) - break; - else - return child.repObject; - } - } - return null; - }, - /** * @param msg * text message to send to the log @@ -1808,12 +1293,14 @@ var InspectorUI = { /** * For each tool in the tools collection select the current node that is * selected in the highlighter + * @param aScroll boolean + * Do you want to scroll the treepanel? */ - toolsSelect: function IUI_toolsSelect() + toolsSelect: function IUI_toolsSelect(aScroll) { this.toolsDo(function IUI_toolsOnSelect(aTool) { if (aTool.isOpen) { - aTool.onSelect.call(aTool.context, InspectorUI.selection); + aTool.onSelect.call(aTool.context, InspectorUI.selection, aScroll); } }); }, @@ -1991,7 +1478,7 @@ var InspectorProgressListener = { function IPL_onStateChange(aProgress, aRequest, aFlag, aStatus) { // Remove myself if the Inspector is no longer open. - if (!InspectorUI.isTreePanelOpen) { + if (!InspectorUI.isInspectorOpen) { gBrowser.removeProgressListener(InspectorProgressListener); return; } @@ -2089,10 +1576,14 @@ var InspectorProgressListener = { ///////////////////////////////////////////////////////////////////////// //// Initializers -XPCOMUtils.defineLazyGetter(InspectorUI, "inspectCmd", function () { +XPCOMUtils.defineLazyGetter(InspectorUI, "inspectMenuitem", function () { return document.getElementById("Tools:Inspect"); }); +XPCOMUtils.defineLazyGetter(InspectorUI, "inspectToolbutton", function () { + return document.getElementById("inspector-inspect-toolbutton"); +}); + XPCOMUtils.defineLazyGetter(InspectorUI, "strings", function () { return Services.strings. createBundle("chrome://browser/locale/inspector.properties"); diff --git a/browser/devtools/highlighter/test/Makefile.in b/browser/devtools/highlighter/test/Makefile.in index cf991dfa0f5..9e27978e36f 100644 --- a/browser/devtools/highlighter/test/Makefile.in +++ b/browser/devtools/highlighter/test/Makefile.in @@ -61,5 +61,7 @@ _BROWSER_FILES = \ browser_inspector_bug_566084_location_changed.js \ $(NULL) +# browser_inspector_treePanel_click.js \ + libs:: $(_BROWSER_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) diff --git a/browser/devtools/highlighter/test/browser_inspector_bug_566084_location_changed.js b/browser/devtools/highlighter/test/browser_inspector_bug_566084_location_changed.js index e7abe3d3e3d..d1244ae5935 100644 --- a/browser/devtools/highlighter/test/browser_inspector_bug_566084_location_changed.js +++ b/browser/devtools/highlighter/test/browser_inspector_bug_566084_location_changed.js @@ -17,7 +17,7 @@ function runInspectorTests() { is(para.textContent, "init", "paragraph content is correct"); ok(InspectorUI.inspecting, "Inspector is highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Panel is open"); + ok(InspectorUI.isInspectorOpen, "Inspector is open"); InspectorUI.isDirty = true; @@ -56,7 +56,7 @@ function onPageLoad() { is(para.textContent, "test2", "paragraph content is correct"); ok(!InspectorUI.inspecting, "Inspector is not highlighting"); - ok(!InspectorUI.isTreePanelOpen, "Inspector Panel is not open"); + ok(!InspectorUI.isInspectorOpen, "Inspector Panel is not open"); testEnd(); } @@ -68,7 +68,7 @@ function locationTest2() { is(para.textContent, "init", "paragraph content is correct"); ok(InspectorUI.inspecting, "Inspector is highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Panel is open"); + ok(InspectorUI.isInspectorOpen, "Inspector Panel is open"); notificationBox.addEventListener("AlertActive", alertActive2, false); @@ -102,7 +102,6 @@ function alertActive2() { function testEnd() { notificationBox = null; - InspectorUI.isDirty = false; gBrowser.removeCurrentTab(); executeSoon(finish); } diff --git a/browser/devtools/highlighter/test/browser_inspector_editor.js b/browser/devtools/highlighter/test/browser_inspector_editor.js index ca2378f80c4..793704e9f0f 100644 --- a/browser/devtools/highlighter/test/browser_inspector_editor.js +++ b/browser/devtools/highlighter/test/browser_inspector_editor.js @@ -3,11 +3,11 @@ /* ***** BEGIN LICENSE BLOCK ***** /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ - * + * * Contributor(s): * Rob Campbell * Mihai Sucan - * Kyle Simpson + * Kyle Simpson * * ***** END LICENSE BLOCK ***** */ @@ -25,19 +25,26 @@ function setupEditorTests() div.setAttribute("id", "foobar"); div.setAttribute("class", "barbaz"); doc.body.appendChild(div); - - Services.obs.addObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.OPENED, false); + + Services.obs.addObserver(setupHTMLPanel, INSPECTOR_NOTIFICATIONS.OPENED, false); InspectorUI.toggleInspectorUI(); } +function setupHTMLPanel() +{ + Services.obs.removeObserver(setupHTMLPanel, INSPECTOR_NOTIFICATIONS.OPENED); + Services.obs.addObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); + InspectorUI.treePanel.open(); +} + function runEditorTests() { - Services.obs.removeObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.OPENED, false); + Services.obs.removeObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.TREEPANELREADY); InspectorUI.stopInspecting(); - + // setup generator for async test steps editorTestSteps = doEditorTestSteps(); - + // add step listeners Services.obs.addObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false); Services.obs.addObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false); @@ -49,16 +56,17 @@ function runEditorTests() function doEditorTestSteps() { - let editor = InspectorUI.treeBrowserDocument.getElementById("attribute-editor"); - let editorInput = InspectorUI.treeBrowserDocument.getElementById("attribute-editor-input"); + let treePanel = InspectorUI.treePanel; + let editor = treePanel.treeBrowserDocument.getElementById("attribute-editor"); + let editorInput = treePanel.treeBrowserDocument.getElementById("attribute-editor-input"); // Step 1: grab and test the attribute-value nodes in the HTML panel, then open editor - let attrValNode_id = InspectorUI.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='id']")[0]; - let attrValNode_class = InspectorUI.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='class']")[0]; + let attrValNode_id = treePanel.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='id']")[0]; + let attrValNode_class = treePanel.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='class']")[0]; is(attrValNode_id.innerHTML, "foobar", "Step 1: we have the correct `id` attribute-value node in the HTML panel"); is(attrValNode_class.innerHTML, "barbaz", "we have the correct `class` attribute-value node in the HTML panel"); - + // double-click the `id` attribute-value node to open the editor executeSoon(function() { // firing 2 clicks right in a row to simulate a double-click @@ -69,7 +77,7 @@ function doEditorTestSteps() // Step 2: validate editing session, enter new attribute value into editor, and save input - ok(InspectorUI.editingContext, "Step 2: editor session started"); + ok(InspectorUI.treePanel.editingContext, "Step 2: editor session started"); let editorVisible = editor.classList.contains("editing"); ok(editorVisible, "editor popup visible"); @@ -88,13 +96,13 @@ function doEditorTestSteps() let attrValNodeHighlighted = attrValNode_id.classList.contains("editingAttributeValue"); ok(attrValNodeHighlighted, "`id` attribute-value node is editor-highlighted"); - is(InspectorUI.editingContext.repObj, div, "editor session has correct reference to div"); - is(InspectorUI.editingContext.attrObj, attrValNode_id, "editor session has correct reference to `id` attribute-value node in HTML panel"); - is(InspectorUI.editingContext.attrName, "id", "editor session knows correct attribute-name"); + is(treePanel.editingContext.repObj, div, "editor session has correct reference to div"); + is(treePanel.editingContext.attrObj, attrValNode_id, "editor session has correct reference to `id` attribute-value node in HTML panel"); + is(treePanel.editingContext.attrName, "id", "editor session knows correct attribute-name"); editorInput.value = "Hello World"; editorInput.focus(); - + // hit to save the inputted value executeSoon(function() { EventUtils.synthesizeKey("VK_RETURN", {}, attrValNode_id.ownerDocument.defaultView); @@ -106,7 +114,7 @@ function doEditorTestSteps() // Step 3: validate that the previous editing session saved correctly, then open editor on `class` attribute value - ok(!InspectorUI.editingContext, "Step 3: editor session ended"); + ok(!treePanel.editingContext, "Step 3: editor session ended"); editorVisible = editor.classList.contains("editing"); ok(!editorVisible, "editor popup hidden"); attrValNodeHighlighted = attrValNode_id.classList.contains("editingAttributeValue"); @@ -124,16 +132,16 @@ function doEditorTestSteps() // Step 4: enter value into editor, then hit to discard it - ok(InspectorUI.editingContext, "Step 4: editor session started"); + ok(treePanel.editingContext, "Step 4: editor session started"); editorVisible = editor.classList.contains("editing"); ok(editorVisible, "editor popup visible"); - - is(InspectorUI.editingContext.attrObj, attrValNode_class, "editor session has correct reference to `class` attribute-value node in HTML panel"); - is(InspectorUI.editingContext.attrName, "class", "editor session knows correct attribute-name"); + + is(treePanel.editingContext.attrObj, attrValNode_class, "editor session has correct reference to `class` attribute-value node in HTML panel"); + is(treePanel.editingContext.attrName, "class", "editor session knows correct attribute-name"); editorInput.value = "Hello World"; editorInput.focus(); - + // hit to discard the inputted value executeSoon(function() { EventUtils.synthesizeKey("VK_ESCAPE", {}, attrValNode_class.ownerDocument.defaultView); @@ -143,7 +151,7 @@ function doEditorTestSteps() // Step 5: validate that the previous editing session discarded correctly, then open editor on `id` attribute value again - ok(!InspectorUI.editingContext, "Step 5: editor session ended"); + ok(!treePanel.editingContext, "Step 5: editor session ended"); editorVisible = editor.classList.contains("editing"); ok(!editorVisible, "editor popup hidden"); is(div.getAttribute("class"), "barbaz", "`class` attribute-value *not* updated"); @@ -159,15 +167,15 @@ function doEditorTestSteps() // Step 6: validate that editor opened again, then test double-click inside of editor (should do nothing) - ok(InspectorUI.editingContext, "Step 6: editor session started"); + ok(treePanel.editingContext, "Step 6: editor session started"); editorVisible = editor.classList.contains("editing"); ok(editorVisible, "editor popup visible"); - + // double-click on the editor input box executeSoon(function() { // firing 2 clicks right in a row to simulate a double-click EventUtils.synthesizeMouse(editorInput, 2, 2, {clickCount: 2}, editorInput.ownerDocument.defaultView); - + // since the previous double-click is supposed to do nothing, // wait a brief moment, then move on to the next step executeSoon(function() { @@ -178,12 +186,12 @@ function doEditorTestSteps() yield; // End of Step 6 - // Step 7: validate that editing session is still correct, then enter a value and try a click + // Step 7: validate that editing session is still correct, then enter a value and try a click // outside of editor (should cancel the editing session) - ok(InspectorUI.editingContext, "Step 7: editor session still going"); + ok(treePanel.editingContext, "Step 7: editor session still going"); editorVisible = editor.classList.contains("editing"); ok(editorVisible, "editor popup still visible"); - + editorInput.value = "all your base are belong to us"; // single-click the `class` attribute-value node @@ -195,12 +203,12 @@ function doEditorTestSteps() // Step 8: validate that the editor was closed and that the editing was not saved - ok(!InspectorUI.editingContext, "Step 8: editor session ended"); + ok(!treePanel.editingContext, "Step 8: editor session ended"); editorVisible = editor.classList.contains("editing"); ok(!editorVisible, "editor popup hidden"); is(div.getAttribute("id"), "Hello World", "`id` attribute-value *not* updated"); is(attrValNode_id.innerHTML, "Hello World", "attribute-value node in HTML panel *not* updated"); - + // End of Step 8 // end of all steps, so clean up diff --git a/browser/devtools/highlighter/test/browser_inspector_iframeTest.js b/browser/devtools/highlighter/test/browser_inspector_iframeTest.js index afd9a933a82..865c35d8dab 100644 --- a/browser/devtools/highlighter/test/browser_inspector_iframeTest.js +++ b/browser/devtools/highlighter/test/browser_inspector_iframeTest.js @@ -43,7 +43,6 @@ let div1; let div2; let iframe1; let iframe2; -let highlighterFrame; function createDocument() { @@ -99,7 +98,6 @@ function runIframeTests() Services.obs.addObserver(performTestComparisons1, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false); - highlighterFrame = InspectorUI.highlighter.iframe; executeSoon(moveMouseOver.bind(this, div1)); } diff --git a/browser/devtools/highlighter/test/browser_inspector_initialization.js b/browser/devtools/highlighter/test/browser_inspector_initialization.js index 52103225be9..dc8ceeee083 100644 --- a/browser/devtools/highlighter/test/browser_inspector_initialization.js +++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js @@ -68,16 +68,27 @@ function startInspectorTests() function runInspectorTests() { Services.obs.removeObserver(runInspectorTests, - INSPECTOR_NOTIFICATIONS.OPENED, false); + INSPECTOR_NOTIFICATIONS.OPENED); + Services.obs.addObserver(treePanelTests, + INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); + + ok(InspectorUI.toolbar, "we have the toolbar."); + ok(!InspectorUI.toolbar.hidden, "toolbar is visible"); + ok(InspectorUI.inspecting, "Inspector is inspecting"); + ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); + ok(InspectorUI.highlighter, "Highlighter is up"); + + InspectorUI.treePanel.open(); +} + +function treePanelTests() +{ + Services.obs.removeObserver(treePanelTests, + INSPECTOR_NOTIFICATIONS.TREEPANELREADY); Services.obs.addObserver(runContextMenuTest, INSPECTOR_NOTIFICATIONS.CLOSED, false); - ok(!InspectorUI.toolbar.hidden, "toolbar is visible"); - let iframe = document.getElementById("inspector-tree-iframe"); - is(InspectorUI.treeIFrame, iframe, "Inspector IFrame matches"); - ok(InspectorUI.inspecting, "Inspector is inspecting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); - ok(InspectorUI.highlighter, "Highlighter is up"); + ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open"); executeSoon(function() { InspectorUI.closeInspectorUI(); @@ -110,7 +121,7 @@ function inspectNodesFromContextTest() Services.obs.addObserver(openInspectorForContextTest, INSPECTOR_NOTIFICATIONS.CLOSED, false); ok(!InspectorUI.inspecting, "Inspector is not actively highlighting"); is(InspectorUI.selection, salutation, "Inspector is highlighting salutation"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); + ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is closed"); // TODO: These tests depend on the style inspector patches. todo(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open"); executeSoon(function() { @@ -162,9 +173,9 @@ function finishInspectorTests() INSPECTOR_NOTIFICATIONS.CLOSED); ok(!InspectorUI.highlighter, "Highlighter is gone"); - ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is closed"); + ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed"); ok(!InspectorUI.inspecting, "Inspector is not inspecting"); - ok(InspectorUI.toolbar.hidden, "toolbar is hidden"); + ok(!InspectorUI.toolbar, "toolbar is hidden"); gBrowser.removeCurrentTab(); finish(); diff --git a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js index f40d430e5b3..44ac6675aaa 100644 --- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js +++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js @@ -61,7 +61,7 @@ function inspectorUIOpen1() // Make sure the inspector is open. ok(InspectorUI.inspecting, "Inspector is highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); + ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); ok(!InspectorStore.isEmpty(), "InspectorStore is not empty"); is(InspectorStore.length, 1, "InspectorStore.length = 1"); @@ -87,7 +87,7 @@ function inspectorTabOpen2() { // Make sure the inspector is closed. ok(!InspectorUI.inspecting, "Inspector is not highlighting"); - ok(!InspectorUI.isPanelOpen, "Inspector Tree Panel is closed"); + ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed"); is(InspectorStore.length, 1, "InspectorStore.length = 1"); // Activate the inspector again. @@ -105,7 +105,7 @@ function inspectorUIOpen2() // Make sure the inspector is open. ok(InspectorUI.inspecting, "Inspector is highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); + ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); is(InspectorStore.length, 2, "InspectorStore.length = 2"); // Disable highlighting. @@ -127,7 +127,23 @@ function inspectorFocusTab1() // Make sure the inspector is still open. ok(InspectorUI.inspecting, "Inspector is highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); + ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); + is(InspectorStore.length, 2, "InspectorStore.length = 2"); + is(InspectorUI.selection, div, "selection matches the div element"); + + Services.obs.addObserver(inspectorOpenTreePanelTab1, + INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); + + InspectorUI.treePanel.open(); +} + +function inspectorOpenTreePanelTab1() +{ + Services.obs.removeObserver(inspectorOpenTreePanelTab1, + INSPECTOR_NOTIFICATIONS.TREEPANELREADY); + + ok(InspectorUI.inspecting, "Inspector is highlighting"); + ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open"); is(InspectorStore.length, 2, "InspectorStore.length = 2"); is(InspectorUI.selection, div, "selection matches the div element"); @@ -144,7 +160,40 @@ function inspectorFocusTab2() // Make sure the inspector is still open. ok(!InspectorUI.inspecting, "Inspector is not highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); + ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); + is(InspectorStore.length, 2, "InspectorStore.length = 2"); + isnot(InspectorUI.selection, div, "selection does not match the div element"); + + // Switch back to tab 1. + Services.obs.addObserver(inspectorSecondFocusTab1, + INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); + gBrowser.selectedTab = tab1; +} + +function inspectorSecondFocusTab1() +{ + Services.obs.removeObserver(inspectorSecondFocusTab1, + INSPECTOR_NOTIFICATIONS.TREEPANELREADY); + + ok(InspectorUI.inspecting, "Inspector is highlighting"); + ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open"); + is(InspectorStore.length, 2, "InspectorStore.length = 2"); + is(InspectorUI.selection, div, "selection matches the div element"); + + // Switch back to tab 2. + Services.obs.addObserver(inspectorSecondFocusTab2, + INSPECTOR_NOTIFICATIONS.OPENED, false); + gBrowser.selectedTab = tab2; +} + +function inspectorSecondFocusTab2() +{ + Services.obs.removeObserver(inspectorSecondFocusTab2, + INSPECTOR_NOTIFICATIONS.OPENED); + + // Make sure the inspector is still open. + ok(!InspectorUI.inspecting, "Inspector is not highlighting"); + ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); is(InspectorStore.length, 2, "InspectorStore.length = 2"); isnot(InspectorUI.selection, div, "selection does not match the div element"); @@ -161,7 +210,7 @@ function inspectorTabUnload1(evt) // Make sure the Inspector is still open and that the state is correct. ok(!InspectorUI.inspecting, "Inspector is not highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); + ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); is(InspectorStore.length, 1, "InspectorStore.length = 1"); InspectorUI.closeInspectorUI(); diff --git a/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js b/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js index fb66981f915..de23efed7e2 100644 --- a/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js +++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js @@ -17,7 +17,7 @@ function test() { waitForFocus(setupTest, content); }, true); - content.location = "data:text/html,

"; + content.location = 'data:text/html,

'; function setupTest() { node1 = doc.querySelector("div"); @@ -28,28 +28,32 @@ function test() { function runTests() { Services.obs.removeObserver(runTests, INSPECTOR_NOTIFICATIONS.OPENED); - testNode1(); + Services.obs.addObserver(testNode1, INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); + InspectorUI.select(node1, true, true, true); + InspectorUI.openTreePanel(); } function testNode1() { - let box = InspectorUI.ioBox.createObjectBox(node1); - box.click(); - executeSoon(function() { - is(InspectorUI.selection, node1, "selection matches node"); - is(InspectorUI.highlighter.node, node1, "selection matches node"); - testNode2(); - }); + dump("testNode1\n"); + Services.obs.removeObserver(testNode1, INSPECTOR_NOTIFICATIONS.TREEPANELREADY); + is(InspectorUI.selection, node1, "selection matches node"); + is(InspectorUI.highlighter.node, node1, "selection matches node"); + testNode2(); } function testNode2() { - let box = InspectorUI.ioBox.createObjectBox(node2); - box.click(); - executeSoon(function() { - is(InspectorUI.selection, node2, "selection matches node"); - is(InspectorUI.highlighter.node, node2, "selection matches node"); - Services.obs.addObserver(finishUp, INSPECTOR_NOTIFICATIONS.CLOSED, false); - InspectorUI.closeInspectorUI(); - }); + dump("testNode2\n") + Services.obs.addObserver(testHighlightingNode2, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false); + InspectorUI.treePanelSelect("node2"); + } + + function testHighlightingNode2() { + dump("testHighlightingNode2\n") + Services.obs.removeObserver(testHighlightingNode2, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING); + is(InspectorUI.selection, node2, "selection matches node"); + is(InspectorUI.highlighter.node, node2, "selection matches node"); + Services.obs.addObserver(finishUp, INSPECTOR_NOTIFICATIONS.CLOSED, false); + InspectorUI.closeInspectorUI(); } function finishUp() { diff --git a/browser/devtools/highlighter/test/browser_inspector_treePanel_output.js b/browser/devtools/highlighter/test/browser_inspector_treePanel_output.js index 51a27fa7aab..21247eb9091 100644 --- a/browser/devtools/highlighter/test/browser_inspector_treePanel_output.js +++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_output.js @@ -71,10 +71,19 @@ function xhr_onReadyStateChange() { function inspectorOpened() { Services.obs.removeObserver(inspectorOpened, - INSPECTOR_NOTIFICATIONS.OPENED, false); + INSPECTOR_NOTIFICATIONS.OPENED); + + Services.obs.addObserver(treePanelOpened, INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); + InspectorUI.treePanel.open(); +} + +function treePanelOpened() +{ + Services.obs.removeObserver(treePanelOpened, + INSPECTOR_NOTIFICATIONS.TREEPANELREADY); ok(InspectorUI.inspecting, "Inspector is highlighting"); - ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open"); + ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open"); InspectorUI.stopInspecting(); ok(!InspectorUI.inspecting, "Inspector is not highlighting"); @@ -91,8 +100,8 @@ function inspectorOpened() ok(iframeDiv, "Found the div element inside the iframe"); InspectorUI.inspectNode(iframeDiv); - ok(InspectorUI.treePanelDiv, "InspectorUI.treePanelDiv is available"); - is(InspectorUI.treePanelDiv.innerHTML.replace(/^\s+|\s+$/mg, ''), + ok(InspectorUI.treePanel.treePanelDiv, "InspectorUI.treePanelDiv is available"); + is(InspectorUI.treePanel.treePanelDiv.innerHTML.replace(/^\s+|\s+$/mg, ''), expectedResult, "treePanelDiv.innerHTML is correct"); expectedResult = null; @@ -107,7 +116,7 @@ function inspectorClosed() INSPECTOR_NOTIFICATIONS.CLOSED, false); ok(!InspectorUI.inspecting, "Inspector is not highlighting"); - ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is not open"); + ok(!InspectorUI.treePanel, "Inspector Tree Panel is not open"); gBrowser.removeCurrentTab(); finish(); diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn index 1c18a2b5273..f7c7a7d6ef4 100644 --- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -1,4 +1,5 @@ browser.jar: +* content/browser/inspector.html (highlighter/inspector.html) content/browser/NetworkPanel.xhtml (webconsole/NetworkPanel.xhtml) * content/browser/scratchpad.xul (scratchpad/scratchpad.xul) * content/browser/scratchpad.js (scratchpad/scratchpad.js) diff --git a/browser/devtools/scratchpad/Makefile.in b/browser/devtools/scratchpad/Makefile.in index 3c073560ec6..bf57db4e4f2 100644 --- a/browser/devtools/scratchpad/Makefile.in +++ b/browser/devtools/scratchpad/Makefile.in @@ -12,11 +12,11 @@ # for the specific language governing rights and limitations under the # License. # -# The Original Code is HUDService code. +# The Original Code is Scratchpad Build Code. # -# The Initial Developer of the Original Code is Mozilla Corporation. -# -# Portions created by the Initial Developer are Copyright (C) 2010 +# 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): @@ -44,9 +44,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk ifdef ENABLE_TESTS -ifneq (mobile,$(MOZ_BUILD_APP)) DIRS += test endif -endif include $(topsrcdir)/config/rules.mk diff --git a/browser/locales/en-US/chrome/browser/inspector.properties b/browser/locales/en-US/chrome/browser/inspector.properties index 9629f3374e8..32a409c7729 100644 --- a/browser/locales/en-US/chrome/browser/inspector.properties +++ b/browser/locales/en-US/chrome/browser/inspector.properties @@ -7,3 +7,18 @@ confirmNavigationAway.buttonLeaveAccesskey=L confirmNavigationAway.buttonStay=Stay on Page confirmNavigationAway.buttonStayAccesskey=S +# LOCALIZATION NOTE (htmlPanel): Used in the Inspector tool's openInspectorUI +# method when registering the HTML panel. + +# LOCALIZATION NOTE (htmlPanel.label): A button label that appears on the +# InspectorUI's toolbar. +htmlPanel.label=HTML + +# LOCALIZATION NOTE (htmlPanel.tooltiptext): The text that appears when a user +# hovers over the HTML panel's toolbar button. +htmlPanel.tooltiptext=HTML panel + +# LOCALIZATION NOTE (htmlPanel.accesskey): The key bound to the HTML panel's +# toolbar button. +htmlPanel.accesskey=H + From 6b9a6c0f978f84717bcc1c50bcbb5405db7c13da Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Mon, 26 Sep 2011 14:05:40 -0300 Subject: [PATCH 05/10] Bug 672002 - Move HTML panel to a docked panel in the main browser window (highlighter); r=msucan --- browser/devtools/highlighter/TreePanel.jsm | 141 ++++++++++++++---- .../themes/gnomestripe/browser/browser.css | 9 ++ browser/themes/pinstripe/browser/browser.css | 10 ++ browser/themes/winstripe/browser/browser.css | 10 ++ 4 files changed, 142 insertions(+), 28 deletions(-) diff --git a/browser/devtools/highlighter/TreePanel.jsm b/browser/devtools/highlighter/TreePanel.jsm index 41722a993e7..e2a3fb30eae 100644 --- a/browser/devtools/highlighter/TreePanel.jsm +++ b/browser/devtools/highlighter/TreePanel.jsm @@ -49,6 +49,8 @@ Cu.import("resource:///modules/Services.jsm"); var EXPORTED_SYMBOLS = ["TreePanel"]; +const INSPECTOR_URI = "chrome://browser/content/inspector.html"; + /** * TreePanel * A container for the Inspector's HTML Tree Panel widget constructor function. @@ -62,13 +64,21 @@ function TreePanel(aContext, aIUI) { TreePanel.prototype = { showTextNodesWithWhitespace: false, id: "treepanel", // DO NOT LOCALIZE + openInDock: true, /** - * container. - * @returns xul:panel element + * The tree panel container element. + * @returns xul:panel|xul:vbox|null + * xul:panel is returned when the tree panel is not docked, or + * xul:vbox when when the tree panel is docked. + * null is returned when no container is available. */ get container() { + if (this.openInDock) { + return this.document.getElementById("inspector-tree-box"); + } + return this.document.getElementById("inspector-tree-panel"); }, @@ -98,13 +108,15 @@ TreePanel.prototype = { show: this.open, hide: this.close, onSelect: this.select, - panel: this.container, + panel: this.openInDock ? null : this.container, unregister: this.destroy, }; this.editingEvents = {}; - this._boundClose = this.close.bind(this); - this.container.addEventListener("popuphiding", this._boundClose, false); + if (!this.openInDock) { + this._boundClose = this.close.bind(this); + this.container.addEventListener("popuphiding", this._boundClose, false); + } // Register the HTML panel with the highlighter this.IUI.registerTool(this.registrationObject); @@ -144,38 +156,49 @@ TreePanel.prototype = { } this.initializingTreePanel = true; - this.container.hidden = false; + if (!this.openInDock) + this.container.hidden = false; this.treeIFrame = this.document.getElementById("inspector-tree-iframe"); if (!this.treeIFrame) { - let resizerBox = this.document.getElementById("tree-panel-resizer-box"); this.treeIFrame = this.document.createElement("iframe"); this.treeIFrame.setAttribute("id", "inspector-tree-iframe"); - this.treeIFrame.setAttribute("flex", "1"); + this.treeIFrame.flex = 1; this.treeIFrame.setAttribute("type", "content"); - this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox); } - let self = this; - this.container.addEventListener("popupshown", function treePanelShown() { - self.container.removeEventListener("popupshown", - treePanelShown, false); + if (this.openInDock) { // Create vbox + this.openDocked(); + return; + } - self.treeIFrame.addEventListener("load", - function loadedInitializeTreePanel() { - self.treeIFrame.removeEventListener("load", - loadedInitializeTreePanel, true); - self.initializeIFrame(); - }, true); + let resizerBox = this.document.getElementById("tree-panel-resizer-box"); + this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox); - let src = self.treeIFrame.getAttribute("src"); - if (src != "chrome://browser/content/inspector.html") { - self.treeIFrame.setAttribute("src", - "chrome://browser/content/inspector.html"); + let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel() + { + this.treeIFrame.removeEventListener("load", + boundLoadedInitializeTreePanel, true); + this.initializeIFrame(); + }.bind(this); + + let boundTreePanelShown = function treePanelShown() + { + this.container.removeEventListener("popupshown", + boundTreePanelShown, false); + + this.treeIFrame.addEventListener("load", + boundLoadedInitializeTreePanel, true); + + let src = this.treeIFrame.getAttribute("src"); + if (src != INSPECTOR_URI) { + this.treeIFrame.setAttribute("src", INSPECTOR_URI); } else { - self.treeIFrame.contentWindow.location.reload(); + this.treeIFrame.contentWindow.location.reload(); } - }, false); + }.bind(this); + + this.container.addEventListener("popupshown", boundTreePanelShown, false); const panelWidthRatio = 7 / 8; const panelHeightRatio = 1 / 5; @@ -192,11 +215,68 @@ TreePanel.prototype = { this.container.sizeTo(width, height); }, + openDocked: function TP_openDocked() + { + let treeBox = null; + let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically + let toolbarParent = + this.IUI.browser.ownerDocument.getElementById("browser-bottombox"); + treeBox = this.document.createElement("vbox"); + treeBox.id = "inspector-tree-box"; + treeBox.state = "open"; // for the registerTools API. + treeBox.minHeight = 10; + treeBox.flex = 1; + toolbarParent.insertBefore(treeBox, toolbar); + this.createResizer(); + treeBox.appendChild(this.treeIFrame); + + let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel() + { + this.treeIFrame.removeEventListener("load", + boundLoadedInitializeTreePanel, true); + this.initializeIFrame(); + }.bind(this); + + this.treeIFrame.addEventListener("load", + boundLoadedInitializeTreePanel, true); + + let src = this.treeIFrame.getAttribute("src"); + if (src != INSPECTOR_URI) { + this.treeIFrame.setAttribute("src", INSPECTOR_URI); + } else { + this.treeIFrame.contentWindow.location.reload(); + } + }, + + /** + * Lame resizer on the toolbar. + */ + createResizer: function TP_createResizer() + { + let resizer = this.document.createElement("resizer"); + resizer.id = "inspector-horizontal-splitter"; + resizer.setAttribute("dir", "top"); + resizer.flex = 1; + resizer.setAttribute("element", "inspector-tree-box"); + resizer.height = 24; + this.IUI.toolbar.appendChild(resizer); + this.resizer = resizer; + }, + /** * Close the TreePanel. */ close: function TP_close() { + if (this.openInDock) { + this.IUI.toolbar.removeChild(this.resizer); + let treeBox = this.container; + let treeBoxParent = treeBox.parentNode; + treeBoxParent.removeChild(treeBox); + } else { + this.container.hidePopup(); + } + if (this.treePanelDiv) { this.treePanelDiv.ownerPanel = null; let parent = this.treePanelDiv.parentNode; @@ -206,7 +286,6 @@ TreePanel.prototype = { } this.treeLoaded = false; - this.container.hidePopup(); }, /** @@ -215,6 +294,9 @@ TreePanel.prototype = { */ isOpen: function TP_isOpen() { + if (this.openInDock) + return this.treeLoaded && this.container; + return this.treeLoaded && this.container.state == "open"; }, @@ -664,6 +746,7 @@ TreePanel.prototype = { domplateUtils.setDOM(null); + delete this.resizer; delete this.treeWalker; if (this.treePanelDiv) { @@ -687,8 +770,10 @@ TreePanel.prototype = { delete this.ioBox; } - this.container.removeEventListener("popuphiding", this._boundClose, false); - delete this._boundClose; + if (!this.openInDock) { + this.container.removeEventListener("popuphiding", this._boundClose, false); + delete this._boundClose; + } } }; diff --git a/browser/themes/gnomestripe/browser/browser.css b/browser/themes/gnomestripe/browser/browser.css index ba5b10038e5..530ff54a33a 100644 --- a/browser/themes/gnomestripe/browser/browser.css +++ b/browser/themes/gnomestripe/browser/browser.css @@ -1942,3 +1942,12 @@ panel[dimmed="true"] { box-shadow: 0 0 0 1px black; outline-color: white; } + +/* + * need a "bumpy" background image for this! + */ +#inspector-horizontal-splitter { + background: none !important; + -moz-appearance: none; + cursor: n-resize; +} diff --git a/browser/themes/pinstripe/browser/browser.css b/browser/themes/pinstripe/browser/browser.css index 497999a5d01..d3c2fe68c81 100644 --- a/browser/themes/pinstripe/browser/browser.css +++ b/browser/themes/pinstripe/browser/browser.css @@ -2602,3 +2602,13 @@ panel[dimmed="true"] { #inspector-tools > toolbarbutton[checked]:hover:active { background-color: hsla(210,8%,5%,.2); } + +/* + * need a "bumpy" background image for this! + */ +#inspector-horizontal-splitter { + background: none !important; + -moz-appearance: none; + cursor: n-resize; +} + diff --git a/browser/themes/winstripe/browser/browser.css b/browser/themes/winstripe/browser/browser.css index 4e752e512c7..7a522317561 100644 --- a/browser/themes/winstripe/browser/browser.css +++ b/browser/themes/winstripe/browser/browser.css @@ -2558,3 +2558,13 @@ panel[dimmed="true"] { box-shadow: 0 0 0 1px black; outline-color: white; } + +/* + * need a "bumpy" background image for this! + */ +#inspector-horizontal-splitter { + background: none !important; + -moz-appearance: none; + cursor: n-resize; +} + From 3aa29cdf844dc6fd81c99098bad233f282693633 Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Mon, 26 Sep 2011 14:11:22 -0300 Subject: [PATCH 06/10] Bug 687854 - Move the Inspector code to browser/devtools; r=gavin,robcee --HG-- rename : browser/devtools/highlighter/inspector.js => browser/devtools/highlighter/inspector.jsm --- browser/base/content/browser.js | 13 +- browser/devtools/highlighter/Makefile.in | 1 + browser/devtools/highlighter/TreePanel.jsm | 8 +- .../{inspector.js => inspector.jsm} | 333 +++++++++++------- browser/devtools/highlighter/test/Makefile.in | 1 + ...r_inspector_bug_566084_location_changed.js | 4 +- .../test/browser_inspector_bug_665880.js | 12 +- .../test/browser_inspector_bug_674871.js | 16 +- .../test/browser_inspector_editor.js | 20 +- .../test/browser_inspector_highlighter.js | 8 +- .../test/browser_inspector_iframeTest.js | 12 +- .../test/browser_inspector_initialization.js | 38 +- .../test/browser_inspector_registertools.js | 16 +- .../test/browser_inspector_scrolling.js | 8 +- .../test/browser_inspector_store.js | 4 +- .../test/browser_inspector_tab_switch.js | 50 +-- .../test/browser_inspector_treePanel_click.js | 16 +- .../browser_inspector_treePanel_output.js | 12 +- .../test/browser_inspector_treeSelection.js | 8 +- ...e_bug_653531_highlighter_console_helper.js | 8 +- .../en-US/chrome/browser/inspector.properties | 4 +- 21 files changed, 335 insertions(+), 257 deletions(-) rename browser/devtools/highlighter/{inspector.js => inspector.jsm} (83%) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index f276ddf5daa..b28b9af113b 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -173,6 +173,12 @@ XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () { } }); +XPCOMUtils.defineLazyGetter(this, "InspectorUI", function() { + let tmp = {}; + Cu.import("resource:///modules/inspector.jsm", tmp); + return new tmp.InspectorUI(window); +}); + let gInitialPages = [ "about:blank", "about:privatebrowsing", @@ -180,7 +186,6 @@ let gInitialPages = [ ]; #include browser-fullZoom.js -#include ../../devtools/highlighter/inspector.js #include browser-places.js #include browser-tabPreviews.js #include browser-tabview.js @@ -1676,7 +1681,8 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) { TabView.init(); // Enable Inspector? - if (InspectorUI.enabled) { + 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 @@ -1724,6 +1730,9 @@ function BrowserShutdown() { if (!gStartupRan) return; + if (!__lookupGetter__("InspectorUI")) + InspectorUI.destroy(); + // First clean up services initialized in BrowserStartup (or those whose // uninit methods don't depend on the services having been initialized). allTabs.uninit(); diff --git a/browser/devtools/highlighter/Makefile.in b/browser/devtools/highlighter/Makefile.in index 51c637574a4..333d3ea56de 100644 --- a/browser/devtools/highlighter/Makefile.in +++ b/browser/devtools/highlighter/Makefile.in @@ -45,6 +45,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk EXTRA_JS_MODULES = \ + inspector.jsm \ domplate.jsm \ InsideOutBox.jsm \ TreePanel.jsm \ diff --git a/browser/devtools/highlighter/TreePanel.jsm b/browser/devtools/highlighter/TreePanel.jsm index e2a3fb30eae..3c1ad949f79 100644 --- a/browser/devtools/highlighter/TreePanel.jsm +++ b/browser/devtools/highlighter/TreePanel.jsm @@ -141,7 +141,7 @@ TreePanel.prototype = { this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false); delete this.initializingTreePanel; Services.obs.notifyObservers(null, - this.window.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null); + this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null); if (this.IUI.selection) this.select(this.IUI.selection, true); }, @@ -548,7 +548,7 @@ TreePanel.prototype = { this.handleEditorKeypress.bind(this)); // event notification - Services.obs.notifyObservers(null, this.window.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, + Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, null); }, @@ -626,7 +626,7 @@ TreePanel.prototype = { this.editingEvents = {}; // event notification - Services.obs.notifyObservers(null, this.window.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, + Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, null); }, @@ -648,7 +648,7 @@ TreePanel.prototype = { this.IUI.isDirty = true; // event notification - Services.obs.notifyObservers(null, this.window.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, + Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, null); this.closeEditor(); diff --git a/browser/devtools/highlighter/inspector.js b/browser/devtools/highlighter/inspector.jsm similarity index 83% rename from browser/devtools/highlighter/inspector.js rename to browser/devtools/highlighter/inspector.jsm index ec3746fa362..c3a92f76d45 100644 --- a/browser/devtools/highlighter/inspector.js +++ b/browser/devtools/highlighter/inspector.jsm @@ -1,6 +1,5 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ -#ifdef 0 /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -41,7 +40,15 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -#endif + +const Cu = Components.utils; +const Ci = Components.interfaces; +const Cr = Components.results; + +var EXPORTED_SYMBOLS = ["InspectorUI"]; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); const INSPECTOR_INVISIBLE_ELEMENTS = { "head": true, @@ -91,30 +98,32 @@ const INSPECTOR_NOTIFICATIONS = { * Inspect UI's HTML Tree Panel. htmlPanel.label=HTML # LOCALIZATION NOTE (htmlPanel.tooltiptext): The text that appears when a user From 1f2e52b86112e81a395b9c5bd01c026f729c7a84 Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Mon, 26 Sep 2011 14:13:22 -0300 Subject: [PATCH 07/10] Bug 689164 - Disable Style Inspector from Highlighter UI; r=msucan --- browser/devtools/highlighter/inspector.jsm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser/devtools/highlighter/inspector.jsm b/browser/devtools/highlighter/inspector.jsm index c3a92f76d45..d156cbc065b 100644 --- a/browser/devtools/highlighter/inspector.jsm +++ b/browser/devtools/highlighter/inspector.jsm @@ -700,7 +700,8 @@ InspectorUI.prototype = { initTools: function IUI_initTools() { // Style inspector - if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") && + // XXX bug 689164, remove /false &&/ from below when bug 689160 fixed. + if (false && Services.prefs.getBoolPref("devtools.styleinspector.enabled") && !this.toolRegistered("styleinspector")) { let stylePanel = StyleInspector.createPanel(true); this.registerTool({ From ce532c2b0e889df758ea9bf471c2d139ebfe13b7 Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Mon, 26 Sep 2011 15:58:28 -0300 Subject: [PATCH 08/10] Bug 687854 - Move the Inspector code to browser/devtools; r=msucan --- browser/devtools/highlighter/inspector.jsm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/devtools/highlighter/inspector.jsm b/browser/devtools/highlighter/inspector.jsm index d156cbc065b..0e7433e6b25 100644 --- a/browser/devtools/highlighter/inspector.jsm +++ b/browser/devtools/highlighter/inspector.jsm @@ -392,7 +392,7 @@ Highlighter.prototype = { this.veilMiddleBox.style.height = 0; this.veilTransparentBox.style.width = 0; Services.obs.notifyObservers(null, - InspectorUI.INSPECTOR_NOTIFICATIONS.UNHIGHLIGHTING, null); + INSPECTOR_NOTIFICATIONS.UNHIGHLIGHTING, null); }, /** From 6635bcd31c77964f1fdc4ec7380ef2b5b427d935 Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Tue, 14 Jun 2011 14:38:11 +0300 Subject: [PATCH 09/10] Bug 664131 - Expand console object with group methods that indent future console messages in order to create separate blocks of visually combined output; r=gavin --- browser/devtools/webconsole/HUDService.jsm | 48 ++++++++++++++--- .../webconsole/test/browser/Makefile.in | 1 + ...browser_webconsole_bug_642108_pruneTest.js | 6 ++- ...ser_webconsole_bug_664131_console_group.js | 48 +++++++++++++++++ .../test/browser/test-console-extras.html | 3 -- dom/base/ConsoleAPI.js | 24 ++++++++- dom/tests/browser/browser_ConsoleAPITests.js | 52 +++++++++++++++++-- dom/tests/browser/test-console-api.html | 7 +++ .../mochitest/general/test_consoleAPI.html | 3 ++ 9 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 browser/devtools/webconsole/test/browser/browser_webconsole_bug_664131_console_group.js diff --git a/browser/devtools/webconsole/HUDService.jsm b/browser/devtools/webconsole/HUDService.jsm index 6956922f8c3..4427c6abac0 100644 --- a/browser/devtools/webconsole/HUDService.jsm +++ b/browser/devtools/webconsole/HUDService.jsm @@ -165,7 +165,10 @@ const LEVELS = { info: SEVERITY_INFO, log: SEVERITY_LOG, trace: SEVERITY_LOG, - dir: SEVERITY_LOG + dir: SEVERITY_LOG, + group: SEVERITY_LOG, + groupCollapsed: SEVERITY_LOG, + groupEnd: SEVERITY_LOG }; // The lowest HTTP response code (inclusive) that is considered an error. @@ -252,6 +255,9 @@ const ERRORS = { LOG_MESSAGE_MISSING_ARGS: LOG_OUTPUT_FAILED: "Log Failure: Could not append messageNode to outputNode", }; +// The indent of a console group in pixels. +const GROUP_INDENT = 12; + /** * Implements the nsIStreamListener and nsIRequestObserver interface. Used * within the HS_httpObserverFactory function to get the response body of @@ -2005,6 +2011,20 @@ HUD_SERVICE.prototype = sourceLine = aMessage.lineNumber; break; + case "group": + case "groupCollapsed": + clipboardText = body = formatResult(args); + sourceURL = aMessage.filename; + sourceLine = aMessage.lineNumber; + hud.groupDepth++; + break; + + case "groupEnd": + if (hud.groupDepth > 0) { + hud.groupDepth--; + } + return; + default: Cu.reportError("Unknown Console API log level: " + level); return; @@ -2014,6 +2034,7 @@ HUD_SERVICE.prototype = CATEGORY_WEBDEV, LEVELS[level], body, + aHUDId, sourceURL, sourceLine, clipboardText, @@ -2076,7 +2097,8 @@ HUD_SERVICE.prototype = let chromeDocument = hud.HUDBox.ownerDocument; let message = stringBundle.GetStringFromName("ConsoleAPIDisabled"); let node = ConsoleUtils.createMessageNode(chromeDocument, CATEGORY_JS, - SEVERITY_WARNING, message); + SEVERITY_WARNING, message, + aHUDId); ConsoleUtils.outputMessageNode(node, aHUDId); }, @@ -2116,6 +2138,7 @@ HUD_SERVICE.prototype = aCategory, severity, aScriptError.errorMessage, + hudId, aScriptError.sourceName, aScriptError.lineNumber); @@ -2576,6 +2599,7 @@ HUD_SERVICE.prototype = CATEGORY_NETWORK, SEVERITY_LOG, msgNode, + hudId, null, null, clipboardText); @@ -3081,6 +3105,11 @@ HeadsUpDisplay.prototype = { consolePanel: null, + /** + * The nesting depth of the currently active console group. + */ + groupDepth: 0, + get mainPopupSet() { return this.chromeDocument.getElementById("mainPopupSet"); @@ -4752,7 +4781,8 @@ JSTerm.prototype = { let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument, CATEGORY_OUTPUT, SEVERITY_LOG, - aOutputString); + aOutputString, + this.hudId); let linkNode = node.querySelector(".webconsole-msg-body"); @@ -4799,7 +4829,7 @@ JSTerm.prototype = { { let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument, aCategory, aSeverity, - aOutputMessage); + aOutputMessage, this.hudId); ConsoleUtils.outputMessageNode(node, this.hudId); }, @@ -5540,6 +5570,8 @@ ConsoleUtils = { * The severity of the message: one of the SEVERITY_* constants; * @param string|nsIDOMNode aBody * The body of the message, either a simple string or a DOM node. + * @param number aHUDId + * The HeadsUpDisplay ID. * @param string aSourceURL [optional] * The URL of the source file that emitted the error. * @param number aSourceLine [optional] @@ -5557,8 +5589,8 @@ ConsoleUtils = { */ createMessageNode: function ConsoleUtils_createMessageNode(aDocument, aCategory, aSeverity, - aBody, aSourceURL, aSourceLine, - aClipboardText, aLevel) { + aBody, aHUDId, aSourceURL, + aSourceLine, aClipboardText, aLevel) { if (aBody instanceof Ci.nsIDOMNode && aClipboardText == null) { throw new Error("HUDService.createMessageNode(): DOM node supplied " + "without any clipboard text"); @@ -5569,6 +5601,9 @@ ConsoleUtils = { // long multi-line messages. let iconContainer = aDocument.createElementNS(XUL_NS, "vbox"); iconContainer.classList.add("webconsole-msg-icon-container"); + // Apply the curent group by indenting appropriately. + let hud = HUDService.getHudReferenceById(aHUDId); + iconContainer.style.marginLeft = hud.groupDepth * GROUP_INDENT + "px"; // Make the icon node. It's sprited and the actual region of the image is // determined by CSS rules. @@ -6669,6 +6704,7 @@ ConsoleProgressListener.prototype = { CATEGORY_NETWORK, SEVERITY_LOG, msgNode, + this.hudId, null, null, uri.spec); diff --git a/browser/devtools/webconsole/test/browser/Makefile.in b/browser/devtools/webconsole/test/browser/Makefile.in index edad7719b01..29fb8618054 100644 --- a/browser/devtools/webconsole/test/browser/Makefile.in +++ b/browser/devtools/webconsole/test/browser/Makefile.in @@ -145,6 +145,7 @@ _BROWSER_TEST_FILES = \ browser_webconsole_bug_653531_highlighter_console_helper.js \ browser_webconsole_bug_659907_console_dir.js \ browser_webconsole_bug_678816.js \ + browser_webconsole_bug_664131_console_group.js \ head.js \ $(NULL) diff --git a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_642108_pruneTest.js b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_642108_pruneTest.js index e580697e7eb..e2ba415b870 100644 --- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_642108_pruneTest.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_642108_pruneTest.js @@ -25,7 +25,8 @@ function populateConsoleRepeats(aHudRef) { let node = ConsoleUtils.createMessageNode(hud.ownerDocument, CATEGORY_CSS, SEVERITY_WARNING, - "css log x"); + "css log x", + aHudRef.hudId); ConsoleUtils.outputMessageNode(node, aHudRef.hudId); } } @@ -38,7 +39,8 @@ function populateConsole(aHudRef) { let node = ConsoleUtils.createMessageNode(hud.ownerDocument, CATEGORY_CSS, SEVERITY_WARNING, - "css log " + i); + "css log " + i, + aHudRef.hudId); ConsoleUtils.outputMessageNode(node, aHudRef.hudId); } } diff --git a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_664131_console_group.js b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_664131_console_group.js new file mode 100644 index 00000000000..b6fbdc5c8c3 --- /dev/null +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_664131_console_group.js @@ -0,0 +1,48 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that console.group/groupEnd works as intended. +const GROUP_INDENT = 12; + +function test() { + addTab("data:text/html,Web Console test for bug 664131: Expand console " + + "object with group methods"); + browser.addEventListener("load", onLoad, true); +} + +function onLoad(aEvent) { + browser.removeEventListener(aEvent.type, arguments.callee, true); + + openConsole(); + let hudId = HUDService.getHudIdByWindow(content); + let hud = HUDService.hudReferences[hudId]; + outputNode = hud.outputNode; + + content.console.group("a"); + findLogEntry("a"); + let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container"); + is(msg.length, 1, "one message node displayed"); + is(msg[0].style.marginLeft, GROUP_INDENT + "px", "correct group indent found"); + content.console.log("inside"); + findLogEntry("inside"); + let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container"); + is(msg.length, 2, "two message nodes displayed"); + is(msg[1].style.marginLeft, GROUP_INDENT + "px", "correct group indent found"); + content.console.groupEnd("a"); + content.console.log("outside"); + findLogEntry("outside"); + let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container"); + is(msg.length, 3, "three message nodes displayed"); + is(msg[2].style.marginLeft, "0px", "correct group indent found"); + content.console.groupCollapsed("b"); + findLogEntry("b"); + let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container"); + is(msg.length, 4, "four message nodes displayed"); + is(msg[3].style.marginLeft, GROUP_INDENT + "px", "correct group indent found"); + + finishTest(); +} + diff --git a/browser/devtools/webconsole/test/browser/test-console-extras.html b/browser/devtools/webconsole/test/browser/test-console-extras.html index 706a1a2daaf..ff23b644211 100644 --- a/browser/devtools/webconsole/test/browser/test-console-extras.html +++ b/browser/devtools/webconsole/test/browser/test-console-extras.html @@ -10,9 +10,6 @@ console.assert() console.clear() console.dirxml() - console.group() - console.groupCollapsed() - console.groupEnd() console.profile() console.profileEnd() console.count() diff --git a/dom/base/ConsoleAPI.js b/dom/base/ConsoleAPI.js index f0b80ac5d77..146c6733d2a 100644 --- a/dom/base/ConsoleAPI.js +++ b/dom/base/ConsoleAPI.js @@ -94,6 +94,15 @@ ConsoleAPI.prototype = { dir: function CA_dir() { self.notifyObservers(outerID, innerID, "dir", arguments); }, + group: function CA_group() { + self.notifyObservers(outerID, innerID, "group", self.beginGroup(arguments)); + }, + groupCollapsed: function CA_groupCollapsed() { + self.notifyObservers(outerID, innerID, "groupCollapsed", self.beginGroup(arguments)); + }, + groupEnd: function CA_groupEnd() { + self.notifyObservers(outerID, innerID, "groupEnd", arguments); + }, __exposedProps__: { log: "r", info: "r", @@ -101,7 +110,10 @@ ConsoleAPI.prototype = { error: "r", debug: "r", trace: "r", - dir: "r" + dir: "r", + group: "r", + groupCollapsed: "r", + groupEnd: "r" } }; @@ -120,6 +132,9 @@ ConsoleAPI.prototype = { debug: genPropDesc('debug'), trace: genPropDesc('trace'), dir: genPropDesc('dir'), + group: genPropDesc('group'), + groupCollapsed: genPropDesc('groupCollapsed'), + groupEnd: genPropDesc('groupEnd'), __noSuchMethod__: { enumerable: true, configurable: true, writable: true, value: function() {} }, __mozillaConsole__: { value: true } @@ -229,6 +244,13 @@ ConsoleAPI.prototype = { } return stack; + }, + + /** + * Begin a new group for logging output together. + **/ + beginGroup: function CA_beginGroup() { + return Array.prototype.join.call(arguments[0], " "); } }; diff --git a/dom/tests/browser/browser_ConsoleAPITests.js b/dom/tests/browser/browser_ConsoleAPITests.js index 9307e14c985..a77ea6fbe42 100644 --- a/dom/tests/browser/browser_ConsoleAPITests.js +++ b/dom/tests/browser/browser_ConsoleAPITests.js @@ -108,9 +108,52 @@ function testLocationData(aMessageObject) { is(aMessageObject.arguments[i], a, "correct arg " + i); }); - // Test finished - ConsoleObserver.destroy(); - finish(); + startGroupTest(); +} + +function startGroupTest() { + // Reset the observer function to cope with the fabricated test data. + ConsoleObserver.observe = function CO_observe(aSubject, aTopic, aData) { + try { + testConsoleGroup(aSubject.wrappedJSObject); + } catch (ex) { + // XXX Exceptions in this function currently aren't reported, because of + // some XPConnect weirdness, so report them manually + ok(false, "Exception thrown in CO_observe: " + ex); + } + }; + let button = gWindow.document.getElementById("test-groups"); + ok(button, "found #test-groups button"); + EventUtils.synthesizeMouse(button, 2, 2, {}, gWindow); +} + +function testConsoleGroup(aMessageObject) { + let messageWindow = getWindowByWindowId(aMessageObject.ID); + is(messageWindow, gWindow, "found correct window by window ID"); + + ok(aMessageObject.level == "group" || + aMessageObject.level == "groupCollapsed" || + aMessageObject.level == "groupEnd", + "expected level received"); + + is(aMessageObject.functionName, "testGroups", "functionName matches"); + ok(aMessageObject.lineNumber >= 32 && aMessageObject.lineNumber <= 34, + "lineNumber matches"); + if (aMessageObject.level == "groupCollapsed") { + ok(aMessageObject.arguments == "a group", "groupCollapsed arguments matches"); + } + else if (aMessageObject.level == "group") { + ok(aMessageObject.arguments == "b group", "group arguments matches"); + } + else if (aMessageObject.level == "groupEnd") { + ok(Array.prototype.join.call(aMessageObject.arguments, " ") == "b group", "groupEnd arguments matches"); + } + + if (aMessageObject.level == "groupEnd") { + // Test finished + ConsoleObserver.destroy(); + finish(); + } } function startTraceTest() { @@ -195,6 +238,9 @@ function consoleAPISanityTest() { ok(win.console.error, "console.error is here"); ok(win.console.trace, "console.trace is here"); ok(win.console.dir, "console.dir is here"); + ok(win.console.group, "console.group is here"); + ok(win.console.groupCollapsed, "console.groupCollapsed is here"); + ok(win.console.groupEnd, "console.groupEnd is here"); } var ConsoleObserver = { diff --git a/dom/tests/browser/test-console-api.html b/dom/tests/browser/test-console-api.html index 58dc264750f..96066ae2477 100644 --- a/dom/tests/browser/test-console-api.html +++ b/dom/tests/browser/test-console-api.html @@ -27,6 +27,12 @@ console.warn(str); console.error(str); } + + function testGroups() { + console.groupCollapsed("a", "group"); + console.group("b", "group"); + console.groupEnd("b", "group"); + } @@ -34,5 +40,6 @@ + diff --git a/dom/tests/mochitest/general/test_consoleAPI.html b/dom/tests/mochitest/general/test_consoleAPI.html index 6ebc02fafbf..a82ede0d7c2 100644 --- a/dom/tests/mochitest/general/test_consoleAPI.html +++ b/dom/tests/mochitest/general/test_consoleAPI.html @@ -27,6 +27,9 @@ function doTest() { "debug": "function", "trace": "function", "dir": "function", + "group": "function", + "groupCollapsed": "function", + "groupEnd": "function", "__noSuchMethod__": "function" }; From 25a7ac3dfb9346b61739f7eeca4efe8f3e52c8f3 Mon Sep 17 00:00:00 2001 From: Blair McBride Date: Tue, 27 Sep 2011 14:37:53 +1300 Subject: [PATCH 10/10] Bug 679994 - Checkboxes in "Disable add-ons you no longer use" dialog have no labels, names aren't accessible. r=rstrong, ui-r=Boriss --- toolkit/mozapps/extensions/content/selectAddons.xml | 1 + .../themes/gnomestripe/mozapps/extensions/selectAddons.css | 4 ++++ toolkit/themes/winstripe/mozapps/extensions/selectAddons.css | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/toolkit/mozapps/extensions/content/selectAddons.xml b/toolkit/mozapps/extensions/content/selectAddons.xml index 1813df6fe25..2b93a7ec27a 100644 --- a/toolkit/mozapps/extensions/content/selectAddons.xml +++ b/toolkit/mozapps/extensions/content/selectAddons.xml @@ -53,6 +53,7 @@ diff --git a/toolkit/themes/gnomestripe/mozapps/extensions/selectAddons.css b/toolkit/themes/gnomestripe/mozapps/extensions/selectAddons.css index 370474b7106..d9666d36e52 100644 --- a/toolkit/themes/gnomestripe/mozapps/extensions/selectAddons.css +++ b/toolkit/themes/gnomestripe/mozapps/extensions/selectAddons.css @@ -114,6 +114,10 @@ display: none; } +.select-keep .addon-keep-checkbox:-moz-focusring { + outline: 1px dotted ThreeDDarkShadow; +} + .select-icon { width: 20px; } diff --git a/toolkit/themes/winstripe/mozapps/extensions/selectAddons.css b/toolkit/themes/winstripe/mozapps/extensions/selectAddons.css index 0933ee0af2d..cbbbf204227 100644 --- a/toolkit/themes/winstripe/mozapps/extensions/selectAddons.css +++ b/toolkit/themes/winstripe/mozapps/extensions/selectAddons.css @@ -124,6 +124,10 @@ width: 13px; } +.select-keep .addon-keep-checkbox:-moz-focusring { + outline: 1px dotted ThreeDDarkShadow; +} + .select-keep .checkbox-label-box { display: none; }