diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index cc49fbfa525..8435cff2ae9 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1028,6 +1028,9 @@ pref("devtools.errorconsole.enabled", false); // Enable the Inspector pref("devtools.inspector.enabled", true); pref("devtools.inspector.htmlHeight", 112); +pref("devtools.inspector.htmlPanelOpen", false); +pref("devtools.inspector.sidebarOpen", false); +pref("devtools.inspector.activeSidebar", "ruleview"); // Enable the Debugger pref("devtools.debugger.enabled", false); diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc index 6b6c8bf6829..ec347b0c6f8 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -25,6 +25,7 @@ # Shawn Wilsher # Ehsan Akhgari # Rob Campbell +# Paul Rouget # # 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 @@ -153,6 +154,14 @@ oncommand="InspectorUI.toggleSidebar();"/> + + + + @@ -167,7 +176,7 @@ type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/history/history-panel.xul" oncommand="toggleSidebar('viewHistorySidebar');"/> - + diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 47ecd0c4a9c..5519b06568e 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -38,6 +38,7 @@ # David Dahl # Frank Yan # Victor Porof +# Paul Rouget # # 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 @@ -227,20 +228,21 @@ - + + + + + + @@ -994,45 +996,44 @@ class="devtools-toolbar" nowindowdrag="true" hidden="true"> - - - #ifdef XP_MACOSX - + #endif - - - - + + + + + #ifndef XP_MACOSX - + #endif - - vbox > #inspector-top-resizer { - display: -moz-box; -} - /* * Node Infobar */ diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index 6b1c5c2a397..ca76b07fc2e 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -349,7 +349,7 @@ DebuggerView.Properties = { } // compute the id of the element if not specified - aId = aId || (aName + "-scope"); + aId = aId || (aName.toLowerCase().trim().replace(" ", "-") + "-scope"); // contains generic nodes and functionality let element = this._createPropertyElement(aName, aId, "scope", this._vars); @@ -541,12 +541,12 @@ DebuggerView.Properties = { * An array containing the key and grip properties, specifying * the value and/or type & class of the variable (if the type * is not specified, it will be inferred from the value). - * e.g. ["someProp0": 42] - * ["someProp1": true] - * ["someProp2": "nasu"] - * ["someProp3": { type: "undefined" }] - * ["someProp4": { type: "null" }] - * ["someProp5": { type: "object", class: "Object" }] + * e.g. ["someProp0", 42] + * ["someProp1", true] + * ["someProp2", "nasu"] + * ["someProp3", { type: "undefined" }] + * ["someProp4", { type: "null" }] + * ["someProp5", { type: "object", class: "Object" }] * @param string aName * Optional, the property name. * @paarm string aId diff --git a/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js b/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js index 520a8e53cda..a6a8fd7ca6b 100644 --- a/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js +++ b/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js @@ -34,7 +34,8 @@ function test_early_debugger_statement(aActor) gClient.addListener("paused", paused); // This should continue without nesting an event loop and calling // the onPaused hook, because we haven't attached yet. - gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement(); + // TODO: uncomment this when bug 723563 is fixed. + //gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement(); gClient.removeListener("paused", paused); diff --git a/browser/devtools/debugger/test/browser_dbg_listtabs.js b/browser/devtools/debugger/test/browser_dbg_listtabs.js index 8cca2953590..9c48ad8c66b 100644 --- a/browser/devtools/debugger/test/browser_dbg_listtabs.js +++ b/browser/devtools/debugger/test/browser_dbg_listtabs.js @@ -87,7 +87,7 @@ function test_attach_removed_tab() }); gClient.request({ to: gTab2Actor, type: "attach" }, function(aResponse) { - is(aResponse.type, "exited", "Tab should consider itself exited."); + is(aResponse.error, "noSuchActor", "Tab should be gone."); finish_test(); }); } diff --git a/browser/devtools/highlighter/TreePanel.jsm b/browser/devtools/highlighter/TreePanel.jsm index 4a3b39f57d8..1fddd77a6e8 100644 --- a/browser/devtools/highlighter/TreePanel.jsm +++ b/browser/devtools/highlighter/TreePanel.jsm @@ -64,7 +64,7 @@ function TreePanel(aContext, aIUI) { TreePanel.prototype = { showTextNodesWithWhitespace: false, id: "treepanel", // DO NOT LOCALIZE - openInDock: true, + _open: false, /** * The tree panel container element. @@ -75,11 +75,7 @@ TreePanel.prototype = { */ get container() { - if (this.openInDock) { - return this.document.getElementById("inspector-tree-box"); - } - - return this.document.getElementById("inspector-tree-panel"); + return this.document.getElementById("inspector-tree-box"); }, /** @@ -93,6 +89,8 @@ TreePanel.prototype = { this.IUI = aIUI; this.window = aContext; this.document = this.window.document; + this.button = + this.IUI.chromeDoc.getElementById("inspector-treepanel-toolbutton"); domplateUtils.setDOM(this.window); @@ -100,28 +98,7 @@ TreePanel.prototype = { 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.openInDock ? null : this.container, - unregister: this.destroy, - }; this.editingEvents = {}; - - 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); }, /** @@ -154,13 +131,14 @@ TreePanel.prototype = { */ open: function TP_open() { - if (this.initializingTreePanel && !this.treeLoaded) { + if (this._open) { return; } + this._open = true; + + this.button.setAttribute("checked", true); this.initializingTreePanel = true; - if (!this.openInDock) - this.container.hidden = false; this.treeIFrame = this.document.getElementById("inspector-tree-iframe"); if (!this.treeIFrame) { @@ -168,65 +146,13 @@ TreePanel.prototype = { this.treeIFrame.setAttribute("id", "inspector-tree-iframe"); this.treeIFrame.flex = 1; this.treeIFrame.setAttribute("type", "content"); + this.treeIFrame.setAttribute("context", "inspector-node-popup"); } - if (this.openInDock) { // Create vbox - this.openDocked(); - return; - } - - let resizerBox = this.document.getElementById("tree-panel-resizer-box"); - this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox); - - 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 { - this.treeIFrame.contentWindow.location.reload(); - } - }.bind(this); - - this.container.addEventListener("popupshown", boundTreePanelShown, 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); - }, - - 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.state = "open"; try { treeBox.height = Services.prefs.getIntPref("devtools.inspector.htmlHeight"); @@ -235,22 +161,26 @@ TreePanel.prototype = { } treeBox.minHeight = 64; - treeBox.flex = 1; - toolbarParent.insertBefore(treeBox, toolbar); - this.IUI.toolbar.setAttribute("treepanel-open", "true"); + this.splitter = this.document.createElement("splitter"); + this.splitter.id = "inspector-tree-splitter"; + + let container = this.document.getElementById("appcontent"); + container.appendChild(this.splitter); + container.appendChild(treeBox); treeBox.appendChild(this.treeIFrame); - let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel() + this._boundLoadedInitializeTreePanel = function loadedInitializeTreePanel() { this.treeIFrame.removeEventListener("load", - boundLoadedInitializeTreePanel, true); + this._boundLoadedInitializeTreePanel, true); + delete this._boundLoadedInitializeTreePanel; this.initializeIFrame(); }.bind(this); this.treeIFrame.addEventListener("load", - boundLoadedInitializeTreePanel, true); + this._boundLoadedInitializeTreePanel, true); let src = this.treeIFrame.getAttribute("src"); if (src != INSPECTOR_URI) { @@ -265,17 +195,22 @@ TreePanel.prototype = { */ close: function TP_close() { - if (this.openInDock) { - this.IUI.toolbar.removeAttribute("treepanel-open"); + this._open = false; - let treeBox = this.container; - Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height); - let treeBoxParent = treeBox.parentNode; - treeBoxParent.removeChild(treeBox); - } else { - this.container.hidePopup(); + // Stop caring about the tree iframe load if it's in progress. + if (this._boundLoadedInitializeTreePanel) { + this.treeIFrame.removeEventListener("load", + this._boundLoadedInitializeTreePanel, true); + delete this._boundLoadedInitializeTreePanel; } + this.button.removeAttribute("checked"); + let treeBox = this.container; + Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height); + let treeBoxParent = treeBox.parentNode; + treeBoxParent.removeChild(this.splitter); + treeBoxParent.removeChild(treeBox); + if (this.treePanelDiv) { this.treePanelDiv.ownerPanel = null; let parent = this.treePanelDiv.parentNode; @@ -293,10 +228,15 @@ TreePanel.prototype = { */ isOpen: function TP_isOpen() { - if (this.openInDock) - return this.treeLoaded && this.container; + return this._open; + }, - return this.treeLoaded && this.container.state == "open"; + /** + * Toggle the TreePanel. + */ + toggle: function TP_toggle() + { + this.isOpen() ? this.close() : this.open(); }, /** @@ -669,6 +609,20 @@ TreePanel.prototype = { return null; }, + /** + * Remove a node box from the tree view. + * @param aElement + * The DOM node to remove from the HTML IOBox. + */ + deleteChildBox: function TP_deleteChildBox(aElement) + { + let childBox = this.ioBox.findObjectBox(aElement); + if (!childBox) { + return; + } + childBox.parentNode.removeChild(childBox); + }, + /** * Destructor function. Cleanup. */ @@ -705,11 +659,6 @@ TreePanel.prototype = { this.ioBox.destroy(); delete this.ioBox; } - - if (!this.openInDock) { - this.container.removeEventListener("popuphiding", this._boundClose, false); - delete this._boundClose; - } } }; diff --git a/browser/devtools/highlighter/highlighter.jsm b/browser/devtools/highlighter/highlighter.jsm index 1b3f8a77866..d479c82a6f5 100644 --- a/browser/devtools/highlighter/highlighter.jsm +++ b/browser/devtools/highlighter/highlighter.jsm @@ -43,7 +43,11 @@ * ***** END LICENSE BLOCK ***** */ const Cu = Components.utils; +const Cc = Components.classes; +const Ci = Components.interfaces; + Cu.import("resource:///modules/devtools/LayoutHelpers.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); var EXPORTED_SYMBOLS = ["Highlighter"]; @@ -59,6 +63,9 @@ const INSPECTOR_INVISIBLE_ELEMENTS = { "title": true, }; +const PSEUDO_CLASSES = [":hover", ":active", ":focus"]; + // add ":visited" and ":link" after bug 713106 is fixed + /** * A highlighter mechanism. * @@ -109,6 +116,8 @@ const INSPECTOR_INVISIBLE_ELEMENTS = { * "highlighting" - Highlighter is highlighting * "locked" - The selected node has been locked * "unlocked" - The selected ndoe has been unlocked + * "pseudoclasstoggled" - A pseudo-class lock has changed on the selected node + * * Structure: * @@ -238,6 +247,17 @@ Highlighter.prototype = { } }, + /** + * Notify that a pseudo-class lock was toggled on the highlighted element + * + * @param aPseudo - The pseudo-class to toggle, e.g. ":hover". + */ + pseudoClassLockToggled: function Highlighter_pseudoClassLockToggled(aPseudo) + { + this.emitEvent("pseudoclasstoggled", [aPseudo]); + this.updateInfobar(); + }, + /** * Update the highlighter size and position. */ @@ -446,29 +466,80 @@ Highlighter.prototype = { let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span"); classesBox.id = "highlighter-nodeinfobar-classes"; + + let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span"); + pseudoClassesBox.id = "highlighter-nodeinfobar-pseudo-classes"; + // Add some content to force a better boundingClientRect down below. - classesBox.textContent = " "; + pseudoClassesBox.textContent = " "; nodeInfobar.appendChild(tagNameLabel); nodeInfobar.appendChild(idLabel); nodeInfobar.appendChild(classesBox); + nodeInfobar.appendChild(pseudoClassesBox); container.appendChild(arrowBoxTop); container.appendChild(nodeInfobar); container.appendChild(arrowBoxBottom); aParent.appendChild(container); + nodeInfobar.onclick = (function _onInfobarRightClick(aEvent) { + if (aEvent.button == 2) { + this.openPseudoClassMenu(); + } + }).bind(this); + let barHeight = container.getBoundingClientRect().height; this.nodeInfo = { tagNameLabel: tagNameLabel, idLabel: idLabel, classesBox: classesBox, + pseudoClassesBox: pseudoClassesBox, container: container, barHeight: barHeight, }; }, + /** + * Open the infobar's pseudo-class context menu. + */ + openPseudoClassMenu: function Highlighter_openPseudoClassMenu() + { + let menu = this.chromeDoc.createElement("menupopup"); + menu.id = "infobar-context-menu"; + + let popupSet = this.chromeDoc.getElementById("mainPopupSet"); + popupSet.appendChild(menu); + + let fragment = this.buildPseudoClassMenu(); + menu.appendChild(fragment); + + menu.openPopup(this.nodeInfo.pseudoClassesBox, "end_before", 0, 0, true, false); + }, + + /** + * Create the menuitems for toggling the selection's pseudo-class state + * + * @returns DocumentFragment. The menuitems for toggling pseudo-classes. + */ + buildPseudoClassMenu: function IUI_buildPseudoClassesMenu() + { + let fragment = this.chromeDoc.createDocumentFragment(); + for (let i = 0; i < PSEUDO_CLASSES.length; i++) { + let pseudo = PSEUDO_CLASSES[i]; + let item = this.chromeDoc.createElement("menuitem"); + item.setAttribute("type", "checkbox"); + item.setAttribute("label", pseudo); + item.addEventListener("command", + this.pseudoClassLockToggled.bind(this, pseudo), false); + item.setAttribute("checked", DOMUtils.hasPseudoClassLock(this.node, + pseudo)); + fragment.appendChild(item); + } + return fragment; + }, + /** * Highlight a rectangular region. * @@ -543,6 +614,14 @@ Highlighter.prototype = { classes.textContent = this.node.classList.length ? "." + Array.join(this.node.classList, ".") : ""; + + // Pseudo-classes + let pseudos = PSEUDO_CLASSES.filter(function(pseudo) { + return DOMUtils.hasPseudoClassLock(this.node, pseudo); + }, this); + + let pseudoBox = this.nodeInfo.pseudoClassesBox; + pseudoBox.textContent = pseudos.join(""); }, /** @@ -617,8 +696,8 @@ Highlighter.prototype = { */ computeZoomFactor: function Highlighter_computeZoomFactor() { this.zoom = - this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) - .getInterface(Components.interfaces.nsIDOMWindowUtils) + this.win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) .screenPixelsPerCSSPixel; }, @@ -805,3 +884,6 @@ Highlighter.prototype = { /////////////////////////////////////////////////////////////////////////// +XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () { + return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils) +}); diff --git a/browser/devtools/highlighter/inspector.jsm b/browser/devtools/highlighter/inspector.jsm index 8191e658689..1cc4c4639d0 100644 --- a/browser/devtools/highlighter/inspector.jsm +++ b/browser/devtools/highlighter/inspector.jsm @@ -42,6 +42,7 @@ * * ***** END LICENSE BLOCK ***** */ +const Cc = Components.classes; const Cu = Components.utils; const Ci = Components.interfaces; const Cr = Components.results; @@ -82,6 +83,8 @@ const INSPECTOR_NOTIFICATIONS = { EDITOR_SAVED: "inspector-editor-saved", }; +const PSEUDO_CLASSES = [":hover", ":active", ":focus"]; + /////////////////////////////////////////////////////////////////////////// //// InspectorUI @@ -108,7 +111,6 @@ InspectorUI.prototype = { tools: null, toolEvents: null, inspecting: false, - treePanelEnabled: true, ruleViewEnabled: true, isDirty: false, store: null, @@ -137,25 +139,44 @@ InspectorUI.prototype = { this.sidebarSplitter.removeAttribute("hidden"); this.stylingButton.checked = true; - // Activate the first tool in the sidebar, only if none previously- - // selected. We'll want to do a followup to remember selected tool-states. + // If no tool is already selected, show the last-used sidebar if available, + // otherwise just show the first. + if (!Array.some(this.sidebarToolbar.children, function(btn) btn.hasAttribute("checked"))) { - let firstButtonId = this.getToolbarButtonId(this.sidebarTools[0].id); - this.chromeDoc.getElementById(firstButtonId).click(); + + let activePanel = this.sidebarTools[0]; + let activeId = this.store.getValue(this.winID, "activeSidebar"); + if (activeId && this.tools[activeId]) { + activePanel = this.tools[activeId]; + } + this.activateSidebarPanel(activePanel.id); } + + this.store.setValue(this.winID, "sidebarOpen", true); + Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true); }, /** - * Hide the Sidebar. + * Tear down the sidebar. */ - hideSidebar: function IUI_hideSidebar() + _destroySidebar: function IUI_destroySidebar() { this.sidebarBox.setAttribute("hidden", "true"); this.sidebarSplitter.setAttribute("hidden", "true"); this.stylingButton.checked = false; }, + /** + * Hide the sidebar. + */ + hideSidebar: function IUI_hideSidebar() + { + this._destroySidebar(); + this.store.setValue(this.winID, "sidebarOpen", false); + Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", false); + }, + /** * Show or hide the sidebar. Called from the Styling button on the * highlighter toolbar. @@ -169,6 +190,25 @@ InspectorUI.prototype = { } }, + /** + * Activate a sidebar panel by id. + */ + activateSidebarPanel: function IUI_activateSidebarPanel(aID) + { + let buttonId = this.getToolbarButtonId(aID); + this.chromeDoc.getElementById(buttonId).click(); + }, + + get activeSidebarPanel() + { + for each (let tool in this.sidebarTools) { + if (this.sidebarDeck.selectedPanel == this.getToolIframe(tool)) { + return tool.id; + } + } + return null; + }, + /** * Getter to test if the Sidebar is open or not. */ @@ -192,6 +232,22 @@ InspectorUI.prototype = { } }, + /** + * Toggle the TreePanel. + */ + toggleHTMLPanel: function TP_toggle() + { + if (this.treePanel.isOpen()) { + this.treePanel.close(); + Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false); + this.store.setValue(this.winID, "htmlPanelOpen", false); + } else { + this.treePanel.open(); + Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true); + this.store.setValue(this.winID, "htmlPanelOpen", true); + } + }, + /** * Is the inspector UI open? Simply check if the toolbar is visible or not. * @@ -260,9 +316,7 @@ InspectorUI.prototype = { this.initTools(); this.chromeWin.Tilt.setup(); - if (this.treePanelEnabled) { - this.treePanel = new TreePanel(this.chromeWin, this); - } + this.treePanel = new TreePanel(this.chromeWin, this); if (Services.prefs.getBoolPref("devtools.ruleview.enabled") && !this.toolRegistered("ruleview")) { @@ -310,6 +364,7 @@ InspectorUI.prototype = { show: this.openRuleView, hide: this.closeRuleView, onSelect: this.selectInRuleView, + onChanged: this.changeInRuleView, panel: null, unregister: this.destroyRuleView, sidebar: true, @@ -349,6 +404,16 @@ InspectorUI.prototype = { this.store.setValue(this.winID, "selectedNode", null); this.store.setValue(this.winID, "inspecting", true); this.store.setValue(this.winID, "isDirty", this.isDirty); + + this.store.setValue(this.winID, "htmlPanelOpen", + Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen")); + + this.store.setValue(this.winID, "sidebarOpen", + Services.prefs.getBoolPref("devtools.inspector.sidebarOpen")); + + this.store.setValue(this.winID, "activeSidebar", + Services.prefs.getCharPref("devtools.inspector.activeSidebar")); + this.win.addEventListener("pagehide", this, true); } }, @@ -400,6 +465,8 @@ InspectorUI.prototype = { if (this.treePanel && this.treePanel.editingContext) this.treePanel.closeEditor(); + this.treePanel.destroy(); + if (this.closing || !this.win || !this.browser) { return; } @@ -417,6 +484,7 @@ InspectorUI.prototype = { if (!aKeepStore) { this.store.deleteStore(this.winID); this.win.removeEventListener("pagehide", this, true); + this.clearPseudoClassLocks(); } else { // Update the store before closing. if (this.selection) { @@ -435,13 +503,12 @@ InspectorUI.prototype = { this.stopInspecting(); - this.saveToolState(this.winID); this.toolsDo(function IUI_toolsHide(aTool) { this.unregisterTool(aTool); }.bind(this)); // close the sidebar - this.hideSidebar(); + this._destroySidebar(); if (this.highlighter) { this.highlighter.destroy(); @@ -503,7 +570,7 @@ InspectorUI.prototype = { this.inspecting = false; this.toolsDim(false); if (this.highlighter.getNode()) { - this.select(this.highlighter.getNode(), true, true, !aPreventScroll); + this.select(this.highlighter.getNode(), true, !aPreventScroll); } else { this.select(null, true, true); } @@ -511,15 +578,17 @@ InspectorUI.prototype = { }, /** - * Select an object in the tree view. + * Select an object in the inspector. * @param aNode * node to inspect * @param forceUpdate * force an update? * @param aScroll boolean * scroll the tree panel? + * @param aFrom [optional] string + * which part of the UI the selection occured from */ - select: function IUI_select(aNode, forceUpdate, aScroll) + select: function IUI_select(aNode, forceUpdate, aScroll, aFrom) { // if currently editing an attribute value, using the // highlighter dismisses the editor @@ -530,6 +599,10 @@ InspectorUI.prototype = { aNode = this.defaultSelection; if (forceUpdate || aNode != this.selection) { + if (aFrom != "breadcrumbs") { + this.clearPseudoClassLocks(); + } + this.selection = aNode; if (!this.inspecting) { this.highlighter.highlight(this.selection); @@ -538,9 +611,45 @@ InspectorUI.prototype = { this.breadcrumbs.update(); this.chromeWin.Tilt.update(aNode); + this.treePanel.select(aNode, aScroll); this.toolsSelect(aScroll); }, + + /** + * Toggle the pseudo-class lock on the currently inspected element. If the + * pseudo-class is :hover or :active, that pseudo-class will also be toggled + * on every ancestor of the element, mirroring real :hover and :active + * behavior. + * + * @param aPseudo the pseudo-class lock to toggle, e.g. ":hover" + */ + togglePseudoClassLock: function IUI_togglePseudoClassLock(aPseudo) + { + if (DOMUtils.hasPseudoClassLock(this.selection, aPseudo)) { + this.breadcrumbs.nodeHierarchy.forEach(function(crumb) { + DOMUtils.removePseudoClassLock(crumb.node, aPseudo); + }); + } else { + let hierarchical = aPseudo == ":hover" || aPseudo == ":active"; + let node = this.selection; + do { + DOMUtils.addPseudoClassLock(node, aPseudo); + node = node.parentNode; + } while (hierarchical && node.parentNode) + } + this.nodeChanged(); + }, + + /** + * Clear all pseudo-class locks applied to elements in the node hierarchy + */ + clearPseudoClassLocks: function IUI_clearPseudoClassLocks() + { + this.breadcrumbs.nodeHierarchy.forEach(function(crumb) { + DOMUtils.clearPseudoClassLocks(crumb.node); + }); + }, /** * Called when the highlighted node is changed by a tool. @@ -552,6 +661,7 @@ InspectorUI.prototype = { nodeChanged: function IUI_nodeChanged(aUpdater) { this.highlighter.invalidateSize(); + this.breadcrumbs.updateSelectors(); this.toolsOnChanged(aUpdater); }, @@ -577,17 +687,32 @@ InspectorUI.prototype = { self.select(self.highlighter.getNode(), false, false); }); + this.highlighter.addListener("pseudoclasstoggled", function(aPseudo) { + self.togglePseudoClassLock(aPseudo); + }); + if (this.store.getValue(this.winID, "inspecting")) { this.startInspecting(); + this.highlighter.unlock(); + } else { + this.highlighter.lock(); } - this.restoreToolState(this.winID); + Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null); this.win.focus(); + this.highlighter.highlight(); + + if (this.store.getValue(this.winID, "htmlPanelOpen")) { + this.treePanel.open(); + } + + if (this.store.getValue(this.winID, "sidebarOpen")) { + this.showSidebar(); + } + Services.obs.notifyObservers({wrappedJSObject: this}, INSPECTOR_NOTIFICATIONS.OPENED, null); - - this.highlighter.highlight(); }, /** @@ -714,6 +839,46 @@ InspectorUI.prototype = { } }, + /** + * Copy the innerHTML of the selected Node to the clipboard. Called via the + * Inspector:CopyInner command. + */ + copyInnerHTML: function IUI_copyInnerHTML() + { + let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. + getService(Ci.nsIClipboardHelper); + clipboard.copyString(this.selection.innerHTML); + }, + + /** + * Copy the outerHTML of the selected Node to the clipboard. Called via the + * Inspector:CopyOuter command. + */ + copyOuterHTML: function IUI_copyOuterHTML() + { + let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. + getService(Ci.nsIClipboardHelper); + clipboard.copyString(this.selection.outerHTML); + }, + + /** + * Delete the selected node. Called via the Inspector:DeleteNode command. + */ + deleteNode: function IUI_deleteNode() + { + let selection = this.selection; + let parent = this.selection.parentNode; + + // remove the node from the treepanel + this.treePanel.deleteChildBox(selection); + + // remove the node from content + parent.removeChild(selection); + this.breadcrumbs.invalidateHierarchy(); + + // select the parent node in the highlighter, treepanel, breadcrumbs + this.inspectNode(parent); + }, ///////////////////////////////////////////////////////////////////////// //// CssRuleView methods @@ -796,6 +961,15 @@ InspectorUI.prototype = { if (this.ruleView) this.ruleView.highlight(aNode); }, + + /** + * Update the rules for the current node in the Css Rule View. + */ + changeInRuleView: function IUI_selectInRuleView() + { + if (this.ruleView) + this.ruleView.nodeChanged(); + }, ruleViewChanged: function IUI_ruleViewChanged() { @@ -1087,6 +1261,8 @@ InspectorUI.prototype = { let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)); btn.setAttribute("checked", "true"); if (aTool.sidebar) { + Services.prefs.setCharPref("devtools.inspector.activeSidebar", aTool.id); + this.store.setValue(this.winID, "activeSidebar", aTool.id); this.sidebarDeck.selectedPanel = this.getToolIframe(aTool); this.sidebarTools.forEach(function(other) { if (other != aTool) @@ -1180,57 +1356,6 @@ InspectorUI.prototype = { 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.isOpen) { - openTools[aTool.id] = true; - } - }); - this.store.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. - */ - restoreToolState: function IUI_restoreToolState(aWinID) - { - let openTools = this.store.getValue(aWinID, "openTools"); - let activeSidebarTool; - if (openTools) { - this.toolsDo(function IUI_toolsOnShow(aTool) { - if (aTool.id in openTools) { - if (aTool.sidebar && !this.isSidebarOpen) { - this.showSidebar(); - activeSidebarTool = aTool; - } - this.toolShow(aTool); - } - }.bind(this)); - this.sidebarTools.forEach(function(tool) { - if (tool != activeSidebarTool) - this.chromeDoc.getElementById( - this.getToolbarButtonId(tool.id)).removeAttribute("checked"); - }.bind(this)); - } - if (this.store.getValue(this.winID, "inspecting")) { - this.highlighter.unlock(); - } else { - this.highlighter.lock(); - } - - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null); - }, - /** * For each tool in the tools collection select the current node that is * selected in the highlighter @@ -1254,7 +1379,7 @@ InspectorUI.prototype = { toolsDim: function IUI_toolsDim(aState) { this.toolsDo(function IUI_toolsDim(aTool) { - if (aTool.isOpen && "dim" in aTool) { + if ("dim" in aTool) { aTool.dim.call(aTool.context, aState); } }); @@ -1270,7 +1395,7 @@ InspectorUI.prototype = { toolsOnChanged: function IUI_toolsChanged(aUpdater) { this.toolsDo(function IUI_toolsOnChanged(aTool) { - if (aTool.isOpen && ("onChanged" in aTool) && aTool != aUpdater) { + if (("onChanged" in aTool) && aTool != aUpdater) { aTool.onChanged.call(aTool.context); } }); @@ -1664,6 +1789,13 @@ HTMLBreadcrumbs.prototype = { for (let i = 0; i < aNode.classList.length; i++) { text += "." + aNode.classList[i]; } + for (let i = 0; i < PSEUDO_CLASSES.length; i++) { + let pseudo = PSEUDO_CLASSES[i]; + if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) { + text += pseudo; + } + } + return text; }, @@ -1689,6 +1821,9 @@ HTMLBreadcrumbs.prototype = { let classesLabel = this.IUI.chromeDoc.createElement("label"); classesLabel.className = "inspector-breadcrumbs-classes plain"; + + let pseudosLabel = this.IUI.chromeDoc.createElement("label"); + pseudosLabel.className = "inspector-breadcrumbs-pseudo-classes plain"; tagLabel.textContent = aNode.tagName.toLowerCase(); idLabel.textContent = aNode.id ? ("#" + aNode.id) : ""; @@ -1699,9 +1834,15 @@ HTMLBreadcrumbs.prototype = { } classesLabel.textContent = classesText; + let pseudos = PSEUDO_CLASSES.filter(function(pseudo) { + return DOMUtils.hasPseudoClassLock(aNode, pseudo); + }, this); + pseudosLabel.textContent = pseudos.join(""); + fragment.appendChild(tagLabel); fragment.appendChild(idLabel); fragment.appendChild(classesLabel); + fragment.appendChild(pseudosLabel); return fragment; }, @@ -1741,7 +1882,7 @@ HTMLBreadcrumbs.prototype = { item.onmouseup = (function(aNode) { return function() { - inspector.select(aNode, true, true); + inspector.select(aNode, true, true, "breadcrumbs"); } })(nodes[i]); @@ -1895,7 +2036,7 @@ HTMLBreadcrumbs.prototype = { button.onBreadcrumbsClick = function onBreadcrumbsClick() { inspector.stopInspecting(); - inspector.select(aNode, true, true); + inspector.select(aNode, true, true, "breadcrumbs"); }; button.onclick = (function _onBreadcrumbsRightClick(aEvent) { @@ -2010,6 +2151,20 @@ HTMLBreadcrumbs.prototype = { let element = this.nodeHierarchy[this.currentIndex].button; scrollbox.ensureElementIsVisible(element); }, + + updateSelectors: function BC_updateSelectors() + { + for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) { + let crumb = this.nodeHierarchy[i]; + let button = crumb.button; + + while(button.hasChildNodes()) { + button.removeChild(button.firstChild); + } + button.appendChild(this.prettyPrintNodeAsXUL(crumb.node)); + button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(crumb.node)); + } + }, /** * Update the breadcrumbs display when a new node is selected. @@ -2051,6 +2206,8 @@ HTMLBreadcrumbs.prototype = { // Make sure the selected node and its neighbours are visible. this.scroll(); + + this.updateSelectors(); }, } @@ -2070,3 +2227,6 @@ XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () { return obj.StyleInspector; }); +XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () { + return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); +}); diff --git a/browser/devtools/highlighter/test/Makefile.in b/browser/devtools/highlighter/test/Makefile.in index 193cea7a5bc..a76b018f175 100644 --- a/browser/devtools/highlighter/test/Makefile.in +++ b/browser/devtools/highlighter/test/Makefile.in @@ -70,6 +70,9 @@ _BROWSER_FILES = \ browser_inspector_ruleviewstore.js \ browser_inspector_duplicate_ruleview.js \ browser_inspector_invalidate.js \ + browser_inspector_sidebarstate.js \ + browser_inspector_treePanel_menu.js \ + browser_inspector_pseudoclass_lock.js \ head.js \ $(NULL) diff --git a/browser/devtools/highlighter/test/browser_inspector_editor.js b/browser/devtools/highlighter/test/browser_inspector_editor.js index de80603c98f..26bdeeb1a4d 100644 --- a/browser/devtools/highlighter/test/browser_inspector_editor.js +++ b/browser/devtools/highlighter/test/browser_inspector_editor.js @@ -34,7 +34,7 @@ function setupHTMLPanel() { Services.obs.removeObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED); Services.obs.addObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); - InspectorUI.toolShow(InspectorUI.treePanel.registrationObject); + InspectorUI.toggleHTMLPanel(); } function runEditorTests() diff --git a/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js b/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js new file mode 100644 index 00000000000..e5067896b3c --- /dev/null +++ b/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js @@ -0,0 +1,154 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); + +let doc; +let div; + +let pseudo = ":hover"; + +function test() +{ + waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + doc = content.document; + waitForFocus(createDocument, content); + }, true); + + content.location = "data:text/html,pseudo-class lock tests"; +} + +function createDocument() +{ + div = doc.createElement("div"); + div.textContent = "test div"; + + let head = doc.getElementsByTagName('head')[0]; + let style = doc.createElement('style'); + let rules = doc.createTextNode('div { color: red; } div:hover { color: blue; }'); + + style.appendChild(rules); + head.appendChild(style); + doc.body.appendChild(div); + + setupTests(); +} + +function setupTests() +{ + Services.obs.addObserver(selectNode, + InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false); + InspectorUI.openInspectorUI(); +} + +function selectNode() +{ + Services.obs.removeObserver(selectNode, + InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED); + + executeSoon(function() { + InspectorUI.highlighter.addListener("nodeselected", openRuleView); + InspectorUI.inspectNode(div); + }); +} + +function openRuleView() +{ + Services.obs.addObserver(performTests, + InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false); + + InspectorUI.showSidebar(); + InspectorUI.openRuleView(); +} + +function performTests() +{ + Services.obs.removeObserver(performTests, + InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY); + + InspectorUI.highlighter.removeListener("nodeselected", performTests); + + // toggle the class + InspectorUI.highlighter.pseudoClassLockToggled(pseudo); + + testAdded(); + + // toggle the lock off + InspectorUI.highlighter.pseudoClassLockToggled(pseudo); + + testRemoved(); + testRemovedFromUI(); + + // toggle it back on + InspectorUI.highlighter.pseudoClassLockToggled(pseudo); + + // close the inspector + Services.obs.addObserver(testInspectorClosed, + InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false); + InspectorUI.closeInspectorUI(); +} + +function testAdded() +{ + // lock is applied to it and ancestors + let node = div; + do { + is(DOMUtils.hasPseudoClassLock(node, pseudo), true, + "pseudo-class lock has been applied"); + node = node.parentNode; + } while (node.parentNode) + + // infobar selector contains pseudo-class + let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes"); + is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector"); + + // ruleview contains pseudo-class rule + is(InspectorUI.ruleView.element.children.length, 3, + "rule view is showing 3 rules for pseudo-class locked div"); + + is(InspectorUI.ruleView.element.children[1]._ruleEditor.rule.selectorText, + "div:hover", "rule view is showing " + pseudo + " rule"); +} + +function testRemoved() +{ + // lock removed from node and ancestors + let node = div; + do { + is(DOMUtils.hasPseudoClassLock(node, pseudo), false, + "pseudo-class lock has been removed"); + node = node.parentNode; + } while (node.parentNode) +} + +function testRemovedFromUI() +{ + // infobar selector doesn't contain pseudo-class + let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes"); + is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector"); + + // ruleview no longer contains pseudo-class rule + is(InspectorUI.ruleView.element.children.length, 2, + "rule view is showing 2 rules after removing lock"); +} + +function testInspectorClosed() +{ + Services.obs.removeObserver(testInspectorClosed, + InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED); + + testRemoved(); + + finishUp(); +} + +function finishUp() +{ + doc = div = null; + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/devtools/highlighter/test/browser_inspector_registertools.js b/browser/devtools/highlighter/test/browser_inspector_registertools.js index da71006c432..7a178e4c103 100644 --- a/browser/devtools/highlighter/test/browser_inspector_registertools.js +++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js @@ -148,45 +148,6 @@ function startToolTests(evt) ok(!tool2.isOpen, "Panel 2 is closed"); ok(tool3.isOpen, "Panel 3 is open"); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - waitForFocus(testSecondTab, content); - }, true); - - content.location = "data:text/html,registertool new tab test for inspector"; -} - -function testSecondTab() -{ - info("Opened second tab"); - info("Checking panel states 5"); - - 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, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false); - gBrowser.removeCurrentTab(); -} - -function testOriginalTab() -{ - Services.obs.removeObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED); - info("Checking panel states 6"); - - 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, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false); InspectorUI.closeInspectorUI(true); } diff --git a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js index fd481a3314c..72bb67fdc58 100644 --- a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js +++ b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js @@ -128,8 +128,8 @@ function ruleViewOpened2() is(prop.name, "background-color", "First prop is the background color prop."); ok(!prop.enabled, "First prop should be disabled."); - gBrowser.removeCurrentTab(); InspectorUI.closeInspectorUI(); + gBrowser.removeCurrentTab(); finish(); } diff --git a/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js b/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js new file mode 100644 index 00000000000..f5f137473df --- /dev/null +++ b/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let doc; + +function createDocument() +{ + doc.body.innerHTML = '

Sidebar state test

'; + doc.title = "Sidebar State Test"; + + // Open the sidebar and wait for the default view (the rule view) to show. + Services.obs.addObserver(inspectorRuleViewOpened, + InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false); + + InspectorUI.openInspectorUI(); + InspectorUI.showSidebar(); +} + +function inspectorRuleViewOpened() +{ + Services.obs.removeObserver(inspectorRuleViewOpened, + InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY); + is(InspectorUI.activeSidebarPanel, "ruleview", "Rule View is selected by default"); + + // Select the computed view and turn off the inspector. + InspectorUI.activateSidebarPanel("styleinspector"); + + Services.obs.addObserver(inspectorClosed, + InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false); + InspectorUI.closeInspectorUI(); +} + +function inspectorClosed() +{ + // Reopen the inspector, expect the computed view to be loaded. + Services.obs.removeObserver(inspectorClosed, + InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED); + + Services.obs.addObserver(computedViewPopulated, + "StyleInspector-populated", false); + + InspectorUI.openInspectorUI(); +} + +function computedViewPopulated() +{ + Services.obs.removeObserver(computedViewPopulated, + "StyleInspector-populated"); + is(InspectorUI.activeSidebarPanel, "styleinspector", "Computed view is selected by default."); + + finishTest(); +} + + +function finishTest() +{ + InspectorUI.closeInspectorUI(); + gBrowser.removeCurrentTab(); + finish(); +} + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + doc = content.document; + waitForFocus(createDocument, content); + }, true); + + content.location = "data:text/html,basic tests for inspector"; +} + diff --git a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js index 2b9410e49e0..5d0f0186f45 100644 --- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js +++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js @@ -96,6 +96,7 @@ function inspectorTabOpen2() executeSoon(function() { Services.obs.addObserver(inspectorUIOpen2, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false); + clearUserPrefs(); InspectorUI.openInspectorUI(); }); } @@ -136,7 +137,7 @@ function inspectorFocusTab1() Services.obs.addObserver(inspectorOpenTreePanelTab1, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); - InspectorUI.treePanel.open(); + InspectorUI.toggleHTMLPanel(); } function inspectorOpenTreePanelTab1() @@ -153,7 +154,7 @@ function inspectorOpenTreePanelTab1() executeSoon(function() { InspectorUI.showSidebar(); - InspectorUI.toolShow(InspectorUI.stylePanel.registrationObject); + InspectorUI.activateSidebarPanel("styleinspector"); }); } diff --git a/browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js b/browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js new file mode 100644 index 00000000000..44aab3bbc04 --- /dev/null +++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js @@ -0,0 +1,97 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + + +function test() { + + waitForExplicitFinish(); + + let doc; + let node1; + let div; + + function createDocument() { + div = doc.createElement("div"); + let h1 = doc.createElement("h1"); + let p1 = doc.createElement("p"); + let p2 = doc.createElement("p"); + doc.title = "Inspector Tree Menu Test"; + h1.textContent = "Inspector Tree Menu Test"; + p1.textContent = "This is some example text"; + div.appendChild(h1); + div.appendChild(p1); + doc.body.appendChild(div); + node1 = p1; + setupTest(); + } + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onload() { + gBrowser.selectedBrowser.removeEventListener("load", onload, true); + doc = content.document; + waitForFocus(createDocument, content); + }, true); + + content.location = content.location = "data:text/html,basic tests for inspector";; + + function setupTest() { + Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false); + InspectorUI.toggleInspectorUI(); + } + + function runTests() { + Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED); + Services.obs.addObserver(testCopyInnerMenu, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false); + InspectorUI.stopInspecting(); + InspectorUI.inspectNode(node1, true); + InspectorUI.treePanel.open(); + } + + function testCopyInnerMenu() { + let copyInner = document.getElementById("inspectorHTMLCopyInner"); + ok(copyInner, "the popup menu has a copy inner html menu item"); + + waitForClipboard("This is some example text", + function() { copyInner.doCommand(); }, + testCopyOuterMenu, testCopyOuterMenu); + } + + function testCopyOuterMenu() { + let copyOuter = document.getElementById("inspectorHTMLCopyOuter"); + ok(copyOuter, "the popup menu has a copy outer html menu item"); + + waitForClipboard("

This is some example text

", + function() { copyOuter.doCommand(); }, + testDeleteNode, testDeleteNode); + } + + function testDeleteNode() { + let deleteNode = document.getElementById("inspectorHTMLDelete"); + ok(deleteNode, "the popup menu has a delete menu item"); + + InspectorUI.highlighter.addListener("nodeselected", deleteTest); + + let commandEvent = document.createEvent("XULCommandEvent"); + commandEvent.initCommandEvent("command", true, true, window, 0, false, false, + false, false, null); + deleteNode.dispatchEvent(commandEvent); + } + + function deleteTest() { + InspectorUI.highlighter.removeListener("nodeSelected", deleteTest); + Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false); + is(InspectorUI.selection, div, "parent node selected"); + let p = doc.querySelector("P"); + is(p, null, "node deleted"); + executeSoon(function() { + InspectorUI.closeInspectorUI(); + }); + } + + function finishUp() { + Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED); + doc = node1 = div = null; + gBrowser.removeCurrentTab(); + finish(); + } +} diff --git a/browser/devtools/highlighter/test/head.js b/browser/devtools/highlighter/test/head.js index 305b78d2472..0ca4bfe3d29 100644 --- a/browser/devtools/highlighter/test/head.js +++ b/browser/devtools/highlighter/test/head.js @@ -41,6 +41,16 @@ let tempScope = {}; Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope); let LayoutHelpers = tempScope.LayoutHelpers; +// Clear preferences that may be set during the course of tests. +function clearUserPrefs() +{ + Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen"); + Services.prefs.clearUserPref("devtools.inspector.sidebarOpen"); + Services.prefs.clearUserPref("devtools.inspector.activeSidebar"); +} + +registerCleanupFunction(clearUserPrefs); + function isHighlighting() { let veil = InspectorUI.highlighter.veilTransparentBox; @@ -78,3 +88,4 @@ function midPoint(aPointA, aPointB) pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y; return pointC; } + diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index 93634beeea7..025d1d4e07a 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -441,7 +441,8 @@ var Scratchpad = { }, /** - * Write out a value at the current insertion point as a block comment + * Write out a value at the next line from the current insertion point. + * The comment block will always be preceded by a newline character. * @param object aValue * The Object to write out as a string */ @@ -452,7 +453,7 @@ var Scratchpad = { selection.end : // after selected text this.editor.getCharCount(); // after text end - let newComment = "/*\n" + aValue + "\n*/"; + let newComment = "\n/*\n" + aValue + "\n*/"; this.setText(newComment, insertionPoint, insertionPoint); diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js index 760d4cdd5f0..f1c942aa5c9 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js @@ -21,7 +21,7 @@ function runTests() var scratchpad = gScratchpadWindow.Scratchpad; var message = "\"Hello World!\"" - var openComment = "/*\n"; + var openComment = "\n/*\n"; var closeComment = "\n*/"; var error = "throw new Error(\"Ouch!\")"; let messageArray = {}; diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js index e19754fb4f2..e23103a9be2 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js @@ -30,25 +30,25 @@ function verifyFalsies(sp) { sp.setText("undefined"); sp.display(); - is(sp.selectedText, "/*\nundefined\n*/", "'undefined' is displayed"); + is(sp.selectedText, "\n/*\nundefined\n*/", "'undefined' is displayed"); sp.setText("false"); sp.display(); - is(sp.selectedText, "/*\nfalse\n*/", "'false' is displayed"); + is(sp.selectedText, "\n/*\nfalse\n*/", "'false' is displayed"); sp.setText("0"); sp.display(); - is(sp.selectedText, "/*\n0\n*/", "'0' is displayed"); + is(sp.selectedText, "\n/*\n0\n*/", "'0' is displayed"); sp.setText("null"); sp.display(); - is(sp.selectedText, "/*\nnull\n*/", "'null' is displayed"); + is(sp.selectedText, "\n/*\nnull\n*/", "'null' is displayed"); sp.setText("NaN"); sp.display(); - is(sp.selectedText, "/*\nNaN\n*/", "'NaN' is displayed"); + is(sp.selectedText, "\n/*\nNaN\n*/", "'NaN' is displayed"); sp.setText("''"); sp.display(); - is(sp.selectedText, "/*\n\n*/", "empty string is displayed"); + is(sp.selectedText, "\n/*\n\n*/", "empty string is displayed"); } diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js index 5278fcffc32..f1c24766028 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js @@ -40,13 +40,13 @@ function runTests() is(content.wrappedJSObject.foobarBug636725, 3, "display() updated window.foobarBug636725"); - is(sp.getText(), "++window.foobarBug636725/*\n3\n*/", + is(sp.getText(), "++window.foobarBug636725\n/*\n3\n*/", "display() shows evaluation result in the textbox"); - is(sp.selectedText, "/*\n3\n*/", "selectedText is correct"); + is(sp.selectedText, "\n/*\n3\n*/", "selectedText is correct"); let selection = sp.getSelectionRange(); is(selection.start, 24, "selection.start is correct"); - is(selection.end, 31, "selection.end is correct"); + is(selection.end, 32, "selection.end is correct"); // Test selection run() and display(). @@ -94,16 +94,16 @@ function runTests() "display() worked for the selected range"); is(sp.getText(), "window.foobarBug636725" + - "/*\na\n*/" + + "\n/*\na\n*/" + " = 'c';\n" + "window.foobarBug636725 = 'b';", "display() shows evaluation result in the textbox"); - is(sp.selectedText, "/*\na\n*/", "selectedText is correct"); + is(sp.selectedText, "\n/*\na\n*/", "selectedText is correct"); selection = sp.getSelectionRange(); is(selection.start, 22, "selection.start is correct"); - is(selection.end, 29, "selection.end is correct"); + is(selection.end, 30, "selection.end is correct"); sp.deselect(); diff --git a/browser/devtools/styleinspector/CssRuleView.jsm b/browser/devtools/styleinspector/CssRuleView.jsm index f001b49f9d6..74832d6d93d 100644 --- a/browser/devtools/styleinspector/CssRuleView.jsm +++ b/browser/devtools/styleinspector/CssRuleView.jsm @@ -116,7 +116,7 @@ function ElementStyle(aElement, aStore) // how their .style attribute reflects them as computed values. this.dummyElement = doc.createElementNS(this.element.namespaceURI, this.element.tagName); - this._populate(); + this.populate(); } // We're exporting _ElementStyle for unit tests. var _ElementStyle = ElementStyle; @@ -147,7 +147,7 @@ ElementStyle.prototype = { * Refresh the list of rules to be displayed for the active element. * Upon completion, this.rules[] will hold a list of Rule objects. */ - _populate: function ElementStyle_populate() + populate: function ElementStyle_populate() { this.rules = []; @@ -713,15 +713,33 @@ CssRuleView.prototype = { this._createEditors(); }, + + /** + * Update the rules for the currently highlighted element. + */ + nodeChanged: function CssRuleView_nodeChanged() + { + this._clearRules(); + this._elementStyle.populate(); + this._createEditors(); + }, + + /** + * Clear the rules. + */ + _clearRules: function CssRuleView_clearRules() + { + while (this.element.hasChildNodes()) { + this.element.removeChild(this.element.lastChild); + } + }, /** * Clear the rule view. */ clear: function CssRuleView_clear() { - while (this.element.hasChildNodes()) { - this.element.removeChild(this.element.lastChild); - } + this._clearRules(); this._viewedElement = null; this._elementStyle = null; }, diff --git a/browser/devtools/styleinspector/StyleInspector.jsm b/browser/devtools/styleinspector/StyleInspector.jsm index b45eb6a45f6..663ee442a6f 100644 --- a/browser/devtools/styleinspector/StyleInspector.jsm +++ b/browser/devtools/styleinspector/StyleInspector.jsm @@ -120,6 +120,11 @@ StyleInspector.prototype = { this.cssHtmlTree.highlight(selectedNode); this.iframe.removeEventListener("load", boundIframeOnLoad, true); this.iframeReady = true; + + // Now that we've loaded, select any node we were previously asked + // to show. + this.selectNode(this.selectedNode); + Services.obs.notifyObservers(null, "StyleInspector-opened", null); } }.bind(this); @@ -215,11 +220,16 @@ StyleInspector.prototype = { */ isOpen: function SI_isOpen() { - return this.openDocked ? this.iframeReady && this.IUI.isSidebarOpen && + return this.openDocked ? this.IUI.isSidebarOpen && (this.IUI.sidebarDeck.selectedPanel == this.iframe) : this.panel && this.panel.state && this.panel.state == "open"; }, + isLoaded: function SI_isLoaded() + { + return this.openDocked ? this.iframeReady : this.iframeReady && this.panelReady; + }, + /** * Select from Path (via CssHtmlTree_pathClick) * @param aNode The node to inspect. @@ -242,7 +252,7 @@ StyleInspector.prototype = { selectNode: function SI_selectNode(aNode) { this.selectedNode = aNode; - if (this.isOpen() && !this.dimmed) { + if (this.isLoaded() && !this.dimmed) { this.cssLogic.highlight(aNode); this.cssHtmlTree.highlight(aNode); } @@ -253,7 +263,7 @@ StyleInspector.prototype = { */ updateNode: function SI_updateNode() { - if (this.isOpen() && !this.dimmed) { + if (this.isLoaded() && !this.dimmed) { this.cssLogic.highlight(this.selectedNode); this.cssHtmlTree.refreshPanel(); } diff --git a/browser/devtools/tilt/Tilt.jsm b/browser/devtools/tilt/Tilt.jsm index f5b999d3e33..ff795005220 100644 --- a/browser/devtools/tilt/Tilt.jsm +++ b/browser/devtools/tilt/Tilt.jsm @@ -127,7 +127,6 @@ Tilt.prototype = { chromeWindow: this.chromeWindow, contentWindow: this.chromeWindow.gBrowser.selectedBrowser.contentWindow, parentNode: this.chromeWindow.gBrowser.selectedBrowser.parentNode, - requestAnimationFrame: this.chromeWindow.mozRequestAnimationFrame, notifications: this.NOTIFICATIONS }); @@ -141,7 +140,7 @@ Tilt.prototype = { }, /** - * Destroys a specific instance of the visualizer. + * Starts destroying a specific instance of the visualizer. * * @param {String} aId * the identifier of the instance in the visualizers array @@ -150,43 +149,49 @@ Tilt.prototype = { */ destroy: function T_destroy(aId, aAnimateFlag) { - // if the visualizer is already destroyed, don't do anything - if (!this.visualizers[aId]) { + // if the visualizer is destroyed or destroying, don't do anything + if (!this.visualizers[aId] || this._isDestroying) { + return; + } + this._isDestroying = true; + + let controller = this.visualizers[aId].controller; + let presenter = this.visualizers[aId].presenter; + + let content = presenter.contentWindow; + let pageXOffset = content.pageXOffset * presenter.transforms.zoom; + let pageYOffset = content.pageYOffset * presenter.transforms.zoom; + TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom); + + // if we're not doing any outro animation, just finish destruction directly + if (!aAnimateFlag) { + this._finish(aId); return; } - if (!this.isDestroying) { - this.isDestroying = true; + // otherwise, trigger the outro animation and notify necessary observers + Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null); - let finalize = function T_finalize(aId) { - this.visualizers[aId].removeOverlay(); - this.visualizers[aId].cleanup(); - this.visualizers[aId] = null; + controller.removeEventListeners(); + controller.arcball.reset([-pageXOffset, -pageYOffset]); + presenter.executeDestruction(this._finish.bind(this, aId)); + }, - this.isDestroying = false; - this.chromeWindow.gBrowser.selectedBrowser.focus(); - Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null); - }; + /** + * Finishes detroying a specific instance of the visualizer. + * + * @param {String} aId + * the identifier of the instance in the visualizers array + */ + _finish: function T__finish(aId) + { + this.visualizers[aId].removeOverlay(); + this.visualizers[aId].cleanup(); + this.visualizers[aId] = null; - if (!aAnimateFlag) { - finalize.call(this, aId); - return; - } - - let controller = this.visualizers[aId].controller; - let presenter = this.visualizers[aId].presenter; - - let content = presenter.contentWindow; - let pageXOffset = content.pageXOffset * presenter.transforms.zoom; - let pageYOffset = content.pageYOffset * presenter.transforms.zoom; - - Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null); - TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom); - - controller.removeEventListeners(); - controller.arcball.reset([-pageXOffset, -pageYOffset]); - presenter.executeDestruction(finalize.bind(this, aId)); - } + this._isDestroying = false; + this.chromeWindow.gBrowser.selectedBrowser.focus(); + Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null); }, /** @@ -286,16 +291,18 @@ Tilt.prototype = { // FIXME: this shouldn't be done here, see bug #705131 let onOpened = function() { - if (this.currentInstance) { - this.chromeWindow.InspectorUI.stopInspecting(); - this.inspectButton.disabled = true; - this.highlighterContainer.style.display = "none"; + if (this.inspector && this.highlighter && this.currentInstance) { + this.inspector.stopInspecting(); + this.inspector.inspectToolbutton.disabled = true; + this.highlighter.hide(); } }.bind(this); let onClosed = function() { - this.inspectButton.disabled = false; - this.highlighterContainer.style.display = ""; + if (this.inspector && this.highlighter) { + this.inspector.inspectToolbutton.disabled = false; + this.highlighter.show(); + } }.bind(this); Services.obs.addObserver(onOpened, @@ -337,32 +344,27 @@ Tilt.prototype = { return this.visualizers[this.currentWindowId]; }, + /** + * Gets the current InspectorUI instance. + */ + get inspector() + { + return this.chromeWindow.InspectorUI; + }, + + /** + * Gets the current Highlighter instance from the InspectorUI. + */ + get highlighter() + { + return this.inspector.highlighter; + }, + /** * Gets the Tilt button in the Inspector toolbar. */ get tiltButton() { - return this.chromeWindow.document.getElementById( - "inspector-3D-button"); - }, - - /** - * Gets the Inspect button in the Inspector toolbar. - * FIXME: this shouldn't be needed here, remove after bug #705131 - */ - get inspectButton() - { - return this.chromeWindow.document.getElementById( - "inspector-inspect-toolbutton"); - }, - - /** - * Gets the Highlighter contaniner stack. - * FIXME: this shouldn't be needed here, remove after bug #705131 - */ - get highlighterContainer() - { - return this.chromeWindow.document.getElementById( - "highlighter-container"); + return this.chromeWindow.document.getElementById("inspector-3D-button"); } }; diff --git a/browser/devtools/tilt/TiltGL.jsm b/browser/devtools/tilt/TiltGL.jsm index 4e081e2ad27..37e4416d94f 100644 --- a/browser/devtools/tilt/TiltGL.jsm +++ b/browser/devtools/tilt/TiltGL.jsm @@ -92,6 +92,8 @@ TiltGL.Renderer = function TGL_Renderer(aCanvas, onError, onLoad) */ this.width = aCanvas.width; this.height = aCanvas.height; + this.initialWidth = this.width; + this.initialHeight = this.height; /** * The current model view matrix. @@ -864,21 +866,29 @@ TiltGL.Program.prototype = { // use the the program if it wasn't already set this._context.useProgram(this._ref); + this.cleanupVertexAttrib(); - // check if the required vertex attributes aren't already set - if (utils._enabledAttributes < this._attributes.length) { - utils._enabledAttributes = this._attributes.length; - - // enable any necessary vertex attributes using the cache - for (let i in this._attributes) { - if (this._attributes.hasOwnProperty(i)) { - this._context.enableVertexAttribArray(this._attributes[i]); - } - } + // enable any necessary vertex attributes using the cache + for each (let attribute in this._attributes) { + this._context.enableVertexAttribArray(attribute); + utils._enabledAttributes.push(attribute); } } }, + /** + * Disables all currently enabled vertex attribute arrays. + */ + cleanupVertexAttrib: function TGLP_cleanupVertexAttrib() + { + let utils = TiltGL.ProgramUtils; + + for each (let attribute in utils._enabledAttributes) { + this._context.disableVertexAttribArray(attribute); + } + utils._enabledAttributes = []; + }, + /** * Binds a vertex buffer as an array buffer for a specific shader attribute. * @@ -949,9 +959,9 @@ TiltGL.Program.prototype = { { let gl = this._context; - gl.uniform1i(this._uniforms[aSampler], 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, aTexture._ref); + gl.uniform1i(this._uniforms[aSampler], 0); }, /** @@ -1177,7 +1187,7 @@ TiltGL.ProgramUtils = { /** * Represents the current enabled attributes. */ - _enabledAttributes: -1 + _enabledAttributes: [] }; /** @@ -1415,7 +1425,7 @@ TiltGL.TextureUtils = { /** * This shim renders a content window to a canvas element, but clamps the - * maximum width and height of the canvas to half the WebGL MAX_TEXTURE_SIZE. + * maximum width and height of the canvas to the WebGL MAX_TEXTURE_SIZE. * * @param {Window} aContentWindow * the content window to get a texture from @@ -1615,5 +1625,5 @@ TiltGL.create3DContext = function TGL_create3DContext(aCanvas, aFlags) TiltGL.clearCache = function TGL_clearCache() { TiltGL.ProgramUtils._activeProgram = -1; - TiltGL.ProgramUtils._enabledAttributes = -1; + TiltGL.ProgramUtils._enabledAttributes = []; }; diff --git a/browser/devtools/tilt/TiltUtils.jsm b/browser/devtools/tilt/TiltUtils.jsm index c7c86589221..2b87aa5adb8 100644 --- a/browser/devtools/tilt/TiltUtils.jsm +++ b/browser/devtools/tilt/TiltUtils.jsm @@ -518,8 +518,8 @@ TiltUtils.destroyObject = function TU_destroyObject(aScope) } // objects in Tilt usually use a function to handle internal destruction - if ("function" === typeof aScope.finalize) { - aScope.finalize(); + if ("function" === typeof aScope._finalize) { + aScope._finalize(); } for (let i in aScope) { if (aScope.hasOwnProperty(i)) { diff --git a/browser/devtools/tilt/TiltVisualizer.jsm b/browser/devtools/tilt/TiltVisualizer.jsm index 17945b2ec4a..7d429f349c2 100644 --- a/browser/devtools/tilt/TiltVisualizer.jsm +++ b/browser/devtools/tilt/TiltVisualizer.jsm @@ -55,23 +55,30 @@ const INVISIBLE_ELEMENTS = { "title": true }; +// a node is represented in the visualization mesh as a rectangular stack +// of 5 quads composed of 12 vertices; we draw these as triangles using an +// index buffer of 12 unsigned int elements, obviously one for each vertex; +// if a webpage has enough nodes to overflow the index buffer elements size, +// weird things may happen; thus, when necessary, we'll split into groups +const MAX_GROUP_NODES = Math.pow(2, Uint16Array.BYTES_PER_ELEMENT * 8) / 12 - 1; + const STACK_THICKNESS = 15; const WIREFRAME_COLOR = [0, 0, 0, 0.25]; -const INTRO_TRANSITION_DURATION = 50; -const OUTRO_TRANSITION_DURATION = 40; +const INTRO_TRANSITION_DURATION = 1000; +const OUTRO_TRANSITION_DURATION = 800; const INITIAL_Z_TRANSLATION = 400; const MOVE_INTO_VIEW_ACCURACY = 50; const MOUSE_CLICK_THRESHOLD = 10; -const MOUSE_INTRO_DELAY = 10; +const MOUSE_INTRO_DELAY = 200; const ARCBALL_SENSITIVITY = 0.5; const ARCBALL_ROTATION_STEP = 0.15; const ARCBALL_TRANSLATION_STEP = 35; const ARCBALL_ZOOM_STEP = 0.1; const ARCBALL_ZOOM_MIN = -3000; const ARCBALL_ZOOM_MAX = 500; -const ARCBALL_RESET_FACTOR = 0.9; -const ARCBALL_RESET_INTERVAL = 1000 / 60; +const ARCBALL_RESET_SPHERICAL_FACTOR = 0.1; +const ARCBALL_RESET_LINEAR_FACTOR = 0.01; const TILT_CRAFTER = "resource:///modules/devtools/TiltWorkerCrafter.js"; const TILT_PICKER = "resource:///modules/devtools/TiltWorkerPicker.js"; @@ -92,7 +99,6 @@ let EXPORTED_SYMBOLS = ["TiltVisualizer"]; * {Window} chromeWindow: a reference to the top level window * {Window} contentWindow: the content window holding the visualized doc * {Element} parentNode: the parent node to hold the visualization - * {Function} requestAnimationFrame: responsible with scheduling loops * {Object} notifications: necessary notifications for Tilt * {Function} onError: optional, function called if initialization failed * {Function} onLoad: optional, function called if initialization worked @@ -121,7 +127,6 @@ function TiltVisualizer(aProperties) this.presenter = new TiltVisualizer.Presenter(this.canvas, aProperties.chromeWindow, aProperties.contentWindow, - aProperties.requestAnimationFrame, aProperties.notifications, aProperties.onError || null, aProperties.onLoad || null); @@ -184,8 +189,6 @@ TiltVisualizer.prototype = { * a reference to the top-level window * @param {Window} aContentWindow * the content window holding the document to be visualized - * @param {Function} aRequestAnimationFrame - * function responsible with scheduling loop frames * @param {Object} aNotifications * necessary notifications for Tilt * @param {Function} onError @@ -194,8 +197,7 @@ TiltVisualizer.prototype = { * function called if initialization worked */ TiltVisualizer.Presenter = function TV_Presenter( - aCanvas, aChromeWindow, aContentWindow, aRequestAnimationFrame, aNotifications, - onError, onLoad) + aCanvas, aChromeWindow, aContentWindow, aNotifications, onError, onLoad) { /** * A canvas overlay used for drawing the visualization. @@ -220,25 +222,26 @@ TiltVisualizer.Presenter = function TV_Presenter( /** * Create the renderer, containing useful functions for easy drawing. */ - this.renderer = new TiltGL.Renderer(aCanvas, onError, onLoad); + this._renderer = new TiltGL.Renderer(aCanvas, onError, onLoad); /** * A custom shader used for drawing the visualization mesh. */ - this.visualizationProgram = null; + this._visualizationProgram = null; /** * The combined mesh representing the document visualization. */ - this.texture = null; - this.meshStacks = null; - this.meshWireframe = null; - this.traverseData = null; + this._texture = null; + this._meshData = null; + this._meshStacks = null; + this._meshWireframe = null; + this._traverseData = null; /** * A highlight quad drawn over a stacked dom node. */ - this.highlight = { + this._highlight = { disabled: true, v0: vec3.create(), v1: vec3.create(), @@ -268,19 +271,35 @@ TiltVisualizer.Presenter = function TV_Presenter( * Variable specifying if the scene should be redrawn. * This should happen usually when the visualization is translated/rotated. */ - this.redraw = true; + this._redraw = true; /** - * A frame counter, incremented each time the scene is redrawn. + * Total time passed since the rendering started. + * If the rendering is paused, this property won't get updated. */ - this.frames = 0; + this._time = 0; + + /** + * Frame delta time (the ammount of time passed for each frame). + * This is used to smoothly interpolate animation transfroms. + */ + this._delta = 0; + this._prevFrameTime = 0; + this._currFrameTime = 0; + + + this._setup(); + this._loop(); +}; + +TiltVisualizer.Presenter.prototype = { /** * The initialization logic. */ - let setup = function TVP_setup() + _setup: function TVP__setup() { - let renderer = this.renderer; + let renderer = this._renderer; let inspector = this.chromeWindow.InspectorUI; // if the renderer was destroyed, don't continue setup @@ -289,7 +308,7 @@ TiltVisualizer.Presenter = function TV_Presenter( } // create the visualization shaders and program to draw the stacks mesh - this.visualizationProgram = new renderer.Program({ + this._visualizationProgram = new renderer.Program({ vs: TiltVisualizer.MeshShader.vs, fs: TiltVisualizer.MeshShader.fs, attributes: ["vertexPosition", "vertexTexCoord", "vertexColor"], @@ -301,18 +320,22 @@ TiltVisualizer.Presenter = function TV_Presenter( this.transforms.zoom = inspector.highlighter.zoom; } - this.setupTexture(); - this.setupMeshData(); - this.setupEventListeners(); + // bind the owner object to the necessary functions + TiltUtils.bindObjectFunc(this, "^_on"); + TiltUtils.bindObjectFunc(this, "_loop"); + + this._setupTexture(); + this._setupMeshData(); + this._setupEventListeners(); this.canvas.focus(); - }.bind(this); + }, /** * The animation logic. */ - let loop = function TVP_loop() + _loop: function TVP__loop() { - let renderer = this.renderer; + let renderer = this._renderer; // if the renderer was destroyed, don't continue rendering if (!renderer || !renderer.context) { @@ -320,40 +343,46 @@ TiltVisualizer.Presenter = function TV_Presenter( } // prepare for the next frame of the animation loop - aRequestAnimationFrame(loop); + this.chromeWindow.mozRequestAnimationFrame(this._loop); // only redraw if we really have to - if (this.redraw) { - this.redraw = false; - this.drawVisualization(); + if (this._redraw) { + this._redraw = false; + this._drawVisualization(); } - // call the attached ondraw function and handle all keyframe notifications - if ("function" === typeof this.ondraw) { - this.ondraw(this.frames); + // update the current presenter transfroms from the controller + if ("function" === typeof this._controllerUpdate) { + this._controllerUpdate(this._time, this._delta); } - this.handleKeyframeNotifications(); - }.bind(this); + this._handleFrameDelta(); + this._handleKeyframeNotifications(); + }, - setup(); - loop(); -}; - -TiltVisualizer.Presenter.prototype = { + /** + * Calculates the current frame delta time. + */ + _handleFrameDelta: function TVP__handleFrameDelta() + { + this._prevFrameTime = this._currFrameTime; + this._currFrameTime = this.chromeWindow.mozAnimationStartTime; + this._delta = this._currFrameTime - this._prevFrameTime; + }, /** * Draws the visualization mesh and highlight quad. */ - drawVisualization: function TVP_drawVisualization() + _drawVisualization: function TVP__drawVisualization() { - let renderer = this.renderer; + let renderer = this._renderer; let transforms = this.transforms; let w = renderer.width; let h = renderer.height; + let ih = renderer.initialHeight; // if the mesh wasn't created yet, don't continue rendering - if (!this.meshStacks || !this.meshWireframe) { + if (!this._meshStacks || !this._meshWireframe) { return; } @@ -364,16 +393,16 @@ TiltVisualizer.Presenter.prototype = { // apply a transition transformation using an ortho and perspective matrix let ortho = mat4.ortho(0, w, h, 0, -1000, 1000); - if (!this.isExecutingDestruction) { - let f = this.frames / INTRO_TRANSITION_DURATION; + if (!this._isExecutingDestruction) { + let f = this._time / INTRO_TRANSITION_DURATION; renderer.lerp(renderer.projMatrix, ortho, f, 8); } else { - let f = this.frames / OUTRO_TRANSITION_DURATION; + let f = this._time / OUTRO_TRANSITION_DURATION; renderer.lerp(renderer.projMatrix, ortho, 1 - f, 8); } // apply the preliminary transformations to the model view - renderer.translate(w * 0.5, h * 0.5, -INITIAL_Z_TRANSLATION); + renderer.translate(w * 0.5, ih * 0.5, -INITIAL_Z_TRANSLATION); // calculate the camera matrix using the rotation and translation renderer.translate(transforms.translation[0], 0, @@ -390,45 +419,49 @@ TiltVisualizer.Presenter.prototype = { // draw the visualization mesh renderer.strokeWeight(2); renderer.depthTest(true); - this.drawMeshStacks(); - this.drawMeshWireframe(); - this.drawHighlight(); + this._drawMeshStacks(); + this._drawMeshWireframe(); + this._drawHighlight(); // make sure the initial transition is drawn until finished - if (this.frames < INTRO_TRANSITION_DURATION || - this.frames < OUTRO_TRANSITION_DURATION) { - this.redraw = true; + if (this._time < INTRO_TRANSITION_DURATION || + this._time < OUTRO_TRANSITION_DURATION) { + this._redraw = true; } - this.frames++; + this._time += this._delta; }, /** * Draws the meshStacks object. */ - drawMeshStacks: function TVP_drawMeshStacks() + _drawMeshStacks: function TVP__drawMeshStacks() { - let renderer = this.renderer; - let mesh = this.meshStacks; + let renderer = this._renderer; + let mesh = this._meshStacks; - let visualizationProgram = this.visualizationProgram; - let texture = this.texture; + let visualizationProgram = this._visualizationProgram; + let texture = this._texture; let mvMatrix = renderer.mvMatrix; let projMatrix = renderer.projMatrix; // use the necessary shader visualizationProgram.use(); - // bind the attributes and uniforms as necessary - visualizationProgram.bindVertexBuffer("vertexPosition", mesh.vertices); - visualizationProgram.bindVertexBuffer("vertexTexCoord", mesh.texCoord); - visualizationProgram.bindVertexBuffer("vertexColor", mesh.color); + for (let i = 0, len = mesh.length; i < len; i++) { + let group = mesh[i]; - visualizationProgram.bindUniformMatrix("mvMatrix", mvMatrix); - visualizationProgram.bindUniformMatrix("projMatrix", projMatrix); - visualizationProgram.bindTexture("sampler", texture); + // bind the attributes and uniforms as necessary + visualizationProgram.bindVertexBuffer("vertexPosition", group.vertices); + visualizationProgram.bindVertexBuffer("vertexTexCoord", group.texCoord); + visualizationProgram.bindVertexBuffer("vertexColor", group.color); - // draw the vertices as TRIANGLES indexed elements - renderer.drawIndexedVertices(renderer.context.TRIANGLES, mesh.indices); + visualizationProgram.bindUniformMatrix("mvMatrix", mvMatrix); + visualizationProgram.bindUniformMatrix("projMatrix", projMatrix); + visualizationProgram.bindTexture("sampler", texture); + + // draw the vertices as TRIANGLES indexed elements + renderer.drawIndexedVertices(renderer.context.TRIANGLES, group.indices); + } // save the current model view and projection matrices mesh.mvMatrix = mat4.create(mvMatrix); @@ -438,29 +471,33 @@ TiltVisualizer.Presenter.prototype = { /** * Draws the meshWireframe object. */ - drawMeshWireframe: function TVP_drawMeshWireframe() + _drawMeshWireframe: function TVP__drawMeshWireframe() { - let renderer = this.renderer; - let mesh = this.meshWireframe; + let renderer = this._renderer; + let mesh = this._meshWireframe; - // use the necessary shader - renderer.useColorShader(mesh.vertices, WIREFRAME_COLOR); + for (let i = 0, len = mesh.length; i < len; i++) { + let group = mesh[i]; - // draw the vertices as LINES indexed elements - renderer.drawIndexedVertices(renderer.context.LINES, mesh.indices); + // use the necessary shader + renderer.useColorShader(group.vertices, WIREFRAME_COLOR); + + // draw the vertices as LINES indexed elements + renderer.drawIndexedVertices(renderer.context.LINES, group.indices); + } }, /** * Draws a highlighted quad around a currently selected node. */ - drawHighlight: function TVP_drawHighlight() + _drawHighlight: function TVP__drawHighlight() { // check if there's anything to highlight (i.e any node is selected) - if (!this.highlight.disabled) { + if (!this._highlight.disabled) { // set the corresponding state to draw the highlight quad - let renderer = this.renderer; - let highlight = this.highlight; + let renderer = this._renderer; + let highlight = this._highlight; renderer.depthTest(false); renderer.fill(highlight.fill, 0.5); @@ -473,12 +510,12 @@ TiltVisualizer.Presenter.prototype = { /** * Creates or refreshes the texture applied to the visualization mesh. */ - setupTexture: function TVP_setupTexture() + _setupTexture: function TVP__setupTexture() { - let renderer = this.renderer; + let renderer = this._renderer; // destroy any previously created texture - TiltUtils.destroyObject(this.texture); + TiltUtils.destroyObject(this._texture); this._texture = null; // if the renderer was destroyed, don't continue setup if (!renderer || !renderer.context) { @@ -486,22 +523,22 @@ TiltVisualizer.Presenter.prototype = { } // get the maximum texture size - this.maxTextureSize = + this._maxTextureSize = renderer.context.getParameter(renderer.context.MAX_TEXTURE_SIZE); // use a simple shim to get the image representation of the document // this will be removed once the MOZ_window_region_texture bug #653656 // is finished; currently just converting the document image to a texture // applied to the mesh - this.texture = new renderer.Texture({ + this._texture = new renderer.Texture({ source: TiltGL.TextureUtils.createContentImage(this.contentWindow, - this.maxTextureSize), + this._maxTextureSize), format: "RGB" }); - if ("function" === typeof this.onSetupTexture) { - this.onSetupTexture(); - this.onSetupTexture = null; + if ("function" === typeof this._onSetupTexture) { + this._onSetupTexture(); + this._onSetupTexture = null; } }, @@ -509,16 +546,16 @@ TiltVisualizer.Presenter.prototype = { * Create the combined mesh representing the document visualization by * traversing the document & adding a stack for each node that is drawable. * - * @param {Object} aData + * @param {Object} aMeshData * object containing the necessary mesh verts, texcoord etc. */ - setupMesh: function TVP_setupMesh(aData) + _setupMesh: function TVP__setupMesh(aMeshData) { - let renderer = this.renderer; + let renderer = this._renderer; // destroy any previously created mesh - TiltUtils.destroyObject(this.meshStacks); - TiltUtils.destroyObject(this.meshWireframe); + TiltUtils.destroyObject(this._meshStacks); this._meshStacks = []; + TiltUtils.destroyObject(this._meshWireframe); this._meshWireframe = []; // if the renderer was destroyed, don't continue setup if (!renderer || !renderer.context) { @@ -526,59 +563,66 @@ TiltVisualizer.Presenter.prototype = { } // save the mesh data for future use - this.meshData = aData; + this._meshData = aMeshData; - // create the visualization mesh using the vertices, texture coordinates - // and indices computed when traversing the document object model - this.meshStacks = { - vertices: new renderer.VertexBuffer(aData.vertices, 3), - texCoord: new renderer.VertexBuffer(aData.texCoord, 2), - color: new renderer.VertexBuffer(aData.color, 3), - indices: new renderer.IndexBuffer(aData.stacksIndices) - }; + // create a sub-mesh for each group in the mesh data + for (let i = 0, len = aMeshData.groups.length; i < len; i++) { + let group = aMeshData.groups[i]; - // additionally, create a wireframe representation to make the - // visualization a bit more pretty - this.meshWireframe = { - vertices: this.meshStacks.vertices, - indices: new renderer.IndexBuffer(aData.wireframeIndices) - }; + // create the visualization mesh using the vertices, texture coordinates + // and indices computed when traversing the document object model + this._meshStacks.push({ + vertices: new renderer.VertexBuffer(group.vertices, 3), + texCoord: new renderer.VertexBuffer(group.texCoord, 2), + color: new renderer.VertexBuffer(group.color, 3), + indices: new renderer.IndexBuffer(group.stacksIndices) + }); + + // additionally, create a wireframe representation to make the + // visualization a bit more pretty + this._meshWireframe.push({ + vertices: this._meshStacks[i].vertices, + indices: new renderer.IndexBuffer(group.wireframeIndices) + }); + } // if there's no initial selection made, highlight the required node if (!this._initialSelection) { this._initialSelection = true; this.highlightNode(this.chromeWindow.InspectorUI.selection); + + if (this._currentSelection === 0) { // if the "html" node is selected + this._highlight.disabled = true; + } } + // configure the required mesh transformations and background only once if (!this._initialMeshConfiguration) { this._initialMeshConfiguration = true; - let width = renderer.width; - let height = renderer.height; - // set the necessary mesh offsets - this.transforms.offset[0] = -width * 0.5; - this.transforms.offset[1] = -height * 0.5; + this.transforms.offset[0] = -renderer.width * 0.5; + this.transforms.offset[1] = -renderer.height * 0.5; // make sure the canvas is opaque now that the initialization is finished this.canvas.style.background = TiltVisualizerStyle.canvas.background; - this.drawVisualization(); - this.redraw = true; + this._drawVisualization(); + this._redraw = true; } - if ("function" === typeof this.onSetupMesh) { - this.onSetupMesh(); - this.onSetupMesh = null; + if ("function" === typeof this._onSetupMesh) { + this._onSetupMesh(); + this._onSetupMesh = null; } }, /** - * Computes the mesh vertices, texture coordinates etc. + * Computes the mesh vertices, texture coordinates etc. by groups of nodes. */ - setupMeshData: function TVP_setupMeshData() + _setupMeshData: function TVP__setupMeshData() { - let renderer = this.renderer; + let renderer = this._renderer; // if the renderer was destroyed, don't continue setup if (!renderer || !renderer.context) { @@ -586,55 +630,53 @@ TiltVisualizer.Presenter.prototype = { } // traverse the document and get the depths, coordinates and local names - this.traverseData = TiltUtils.DOM.traverse(this.contentWindow, { + this._traverseData = TiltUtils.DOM.traverse(this.contentWindow, { invisibleElements: INVISIBLE_ELEMENTS, minSize: ELEMENT_MIN_SIZE, - maxX: this.texture.width, - maxY: this.texture.height + maxX: this._texture.width, + maxY: this._texture.height }); let worker = new ChromeWorker(TILT_CRAFTER); worker.addEventListener("message", function TVP_onMessage(event) { - this.setupMesh(event.data); + this._setupMesh(event.data); }.bind(this), false); // calculate necessary information regarding vertices, texture coordinates // etc. in a separate thread, as this process may take a while worker.postMessage({ + maxGroupNodes: MAX_GROUP_NODES, thickness: STACK_THICKNESS, style: TiltVisualizerStyle.nodes, - texWidth: this.texture.width, - texHeight: this.texture.height, - nodesInfo: this.traverseData.info + texWidth: this._texture.width, + texHeight: this._texture.height, + nodesInfo: this._traverseData.info }); }, /** * Sets up event listeners necessary for the presenter. */ - setupEventListeners: function TVP_setupEventListeners() + _setupEventListeners: function TVP__setupEventListeners() { - // bind the owner object to the necessary functions - TiltUtils.bindObjectFunc(this, "^on"); - - this.contentWindow.addEventListener("resize", this.onResize, false); + this.contentWindow.addEventListener("resize", this._onResize, false); }, /** * Called when the content window of the current browser is resized. */ - onResize: function TVP_onResize(e) + _onResize: function TVP_onResize(e) { let zoom = this.chromeWindow.InspectorUI.highlighter.zoom; let width = e.target.innerWidth * zoom; let height = e.target.innerHeight * zoom; // handle aspect ratio changes to update the projection matrix - this.renderer.width = width; - this.renderer.height = height; + this._renderer.width = width; + this._renderer.height = height; - this.redraw = true; + this._redraw = true; }, /** @@ -647,7 +689,7 @@ TiltVisualizer.Presenter.prototype = { */ highlightNode: function TVP_highlightNode(aNode, aFlags) { - this.highlightNodeFor(this.traverseData.nodes.indexOf(aNode), aFlags); + this.highlightNodeFor(this._traverseData.nodes.indexOf(aNode), aFlags); }, /** @@ -705,13 +747,13 @@ TiltVisualizer.Presenter.prototype = { * information supplied. * * @param {Number} aNodeIndex - * the index of the node in the this.traverseData array + * the index of the node in the this._traverseData array * @param {String} aFlags * flags specifying highlighting options */ highlightNodeFor: function TVP_highlightNodeFor(aNodeIndex, aFlags) { - this.redraw = true; + this._redraw = true; // if the node was already selected, don't do anything if (this._currentSelection === aNodeIndex) { @@ -721,15 +763,15 @@ TiltVisualizer.Presenter.prototype = { // if an invalid or nonexisted node is specified, disable the highlight if (aNodeIndex < 0) { this._currentSelection = -1; - this.highlight.disabled = true; + this._highlight.disabled = true; Services.obs.notifyObservers(null, this.NOTIFICATIONS.UNHIGHLIGHTING, null); return; } - let highlight = this.highlight; - let info = this.traverseData.info[aNodeIndex]; - let node = this.traverseData.nodes[aNodeIndex]; + let highlight = this._highlight; + let info = this._traverseData.info[aNodeIndex]; + let node = this._traverseData.nodes[aNodeIndex]; let style = TiltVisualizerStyle.nodes; highlight.disabled = false; @@ -761,8 +803,8 @@ TiltVisualizer.Presenter.prototype = { if (aFlags && aFlags.indexOf("moveIntoView") !== -1) { this.controller.arcball.moveIntoView(vec3.lerp( - vec3.scale(this.highlight.v0, this.transforms.zoom, []), - vec3.scale(this.highlight.v1, this.transforms.zoom, []), 0.5)); + vec3.scale(this._highlight.v0, this.transforms.zoom, []), + vec3.scale(this._highlight.v1, this.transforms.zoom, []), 0.5)); } Services.obs.notifyObservers(null, this.NOTIFICATIONS.HIGHLIGHTING, null); @@ -772,7 +814,7 @@ TiltVisualizer.Presenter.prototype = { * Deletes a node from the visualization mesh. * * @param {Number} aNodeIndex - * the index of the node in the this.traverseData array; + * the index of the node in the this._traverseData array; * if not specified, it will default to the current selection */ deleteNode: function TVP_deleteNode(aNodeIndex) @@ -782,19 +824,22 @@ TiltVisualizer.Presenter.prototype = { return; } - let renderer = this.renderer; - let meshData = this.meshData; + let renderer = this._renderer; - for (let i = 0, k = 36 * aNodeIndex; i < 36; i++) { - meshData.vertices[i + k] = 0; + let groupIndex = parseInt(aNodeIndex / MAX_GROUP_NODES); + let nodeIndex = parseInt((aNodeIndex + (groupIndex ? 1 : 0)) % MAX_GROUP_NODES); + let group = this._meshStacks[groupIndex]; + let vertices = group.vertices.components; + + for (let i = 0, k = 36 * nodeIndex; i < 36; i++) { + vertices[i + k] = 0; } - this.meshStacks.vertices = new renderer.VertexBuffer(meshData.vertices, 3); - this.highlight.disabled = true; - this.redraw = true; + group.vertices = new renderer.VertexBuffer(vertices, 3); + this._highlight.disabled = true; + this._redraw = true; - Services.obs.notifyObservers(null, - this.NOTIFICATIONS.NODE_REMOVED, null); + Services.obs.notifyObservers(null, this.NOTIFICATIONS.NODE_REMOVED, null); }, /** @@ -816,7 +861,7 @@ TiltVisualizer.Presenter.prototype = { aProperties = aProperties || {}; // if the mesh wasn't created yet, don't continue picking - if (!this.meshStacks || !this.meshWireframe) { + if (!this._meshStacks || !this._meshWireframe) { return; } @@ -835,9 +880,8 @@ TiltVisualizer.Presenter.prototype = { }, false); let zoom = this.chromeWindow.InspectorUI.highlighter.zoom; - let width = this.renderer.width * zoom; - let height = this.renderer.height * zoom; - let mesh = this.meshStacks; + let width = this._renderer.width * zoom; + let height = this._renderer.height * zoom; x *= zoom; y *= zoom; @@ -846,12 +890,12 @@ TiltVisualizer.Presenter.prototype = { // and do all the heavy lifting in a separate thread worker.postMessage({ thickness: STACK_THICKNESS, - vertices: mesh.vertices.components, + vertices: this._meshData.allVertices, // create the ray destined for 3D picking ray: vec3.createRay([x, y, 0], [x, y, 1], [0, 0, width, height], - mesh.mvMatrix, - mesh.projMatrix) + this._meshStacks.mvMatrix, + this._meshStacks.projMatrix) }); }, @@ -874,7 +918,7 @@ TiltVisualizer.Presenter.prototype = { transforms.translation[2] !== z) { vec3.set(aTranslation, transforms.translation); - this.redraw = true; + this._redraw = true; } }, @@ -899,39 +943,43 @@ TiltVisualizer.Presenter.prototype = { transforms.rotation[3] !== w) { quat4.set(aQuaternion, transforms.rotation); - this.redraw = true; + this._redraw = true; } }, /** * Handles notifications at specific frame counts. */ - handleKeyframeNotifications: function TV_handleKeyframeNotifications() + _handleKeyframeNotifications: function TV__handleKeyframeNotifications() { - if (!TiltVisualizer.Prefs.introTransition && !this.isExecutingDestruction) { - this.frames = INTRO_TRANSITION_DURATION; + if (!TiltVisualizer.Prefs.introTransition && !this._isExecutingDestruction) { + this._time = INTRO_TRANSITION_DURATION; } - if (!TiltVisualizer.Prefs.outroTransition && this.isExecutingDestruction) { - this.frames = OUTRO_TRANSITION_DURATION; + if (!TiltVisualizer.Prefs.outroTransition && this._isExecutingDestruction) { + this._time = OUTRO_TRANSITION_DURATION; } - if (this.frames === INTRO_TRANSITION_DURATION && - !this.isExecutingDestruction) { + if (this._time >= INTRO_TRANSITION_DURATION && + !this._isInitializationFinished && + !this._isExecutingDestruction) { + this._isInitializationFinished = true; Services.obs.notifyObservers(null, this.NOTIFICATIONS.INITIALIZED, null); - if ("function" === typeof this.onInitializationFinished) { - this.onInitializationFinished(); + if ("function" === typeof this._onInitializationFinished) { + this._onInitializationFinished(); } } - if (this.frames === OUTRO_TRANSITION_DURATION && - this.isExecutingDestruction) { + if (this._time >= OUTRO_TRANSITION_DURATION && + !this._isDestructionFinished && + this._isExecutingDestruction) { + this._isDestructionFinished = true; Services.obs.notifyObservers(null, this.NOTIFICATIONS.BEFORE_DESTROYED, null); - if ("function" === typeof this.onDestructionFinished) { - this.onDestructionFinished(); + if ("function" === typeof this._onDestructionFinished) { + this._onDestructionFinished(); } } }, @@ -945,17 +993,17 @@ TiltVisualizer.Presenter.prototype = { */ executeDestruction: function TV_executeDestruction(aCallback) { - if (!this.isExecutingDestruction) { - this.isExecutingDestruction = true; - this.onDestructionFinished = aCallback; + if (!this._isExecutingDestruction) { + this._isExecutingDestruction = true; + this._onDestructionFinished = aCallback; // if we execute the destruction after the initialization finishes, // proceed normally; otherwise, skip everything and immediately issue // the callback - if (this.frames > OUTRO_TRANSITION_DURATION) { - this.frames = 0; - this.redraw = true; + if (this._time > OUTRO_TRANSITION_DURATION) { + this._time = 0; + this._redraw = true; } else { aCallback(); } @@ -969,33 +1017,34 @@ TiltVisualizer.Presenter.prototype = { */ isInitialized: function TVP_isInitialized() { - return this.renderer && this.renderer.context; + return this._renderer && this._renderer.context; }, /** * Function called when this object is destroyed. */ - finalize: function TVP_finalize() + _finalize: function TVP__finalize() { - TiltUtils.destroyObject(this.visualizationProgram); - TiltUtils.destroyObject(this.texture); + TiltUtils.destroyObject(this._visualizationProgram); + TiltUtils.destroyObject(this._texture); - if (this.meshStacks) { - TiltUtils.destroyObject(this.meshStacks.vertices); - TiltUtils.destroyObject(this.meshStacks.texCoord); - TiltUtils.destroyObject(this.meshStacks.color); - TiltUtils.destroyObject(this.meshStacks.indices); + if (this._meshStacks) { + this._meshStacks.forEach(function(group) { + TiltUtils.destroyObject(group.vertices); + TiltUtils.destroyObject(group.texCoord); + TiltUtils.destroyObject(group.color); + TiltUtils.destroyObject(group.indices); + }); + } + if (this._meshWireframe) { + this._meshWireframe.forEach(function(group) { + TiltUtils.destroyObject(group.indices); + }); } - if (this.meshWireframe) { - TiltUtils.destroyObject(this.meshWireframe.indices); - } + TiltUtils.destroyObject(this._renderer); - TiltUtils.destroyObject(this.highlight); - TiltUtils.destroyObject(this.transforms); - TiltUtils.destroyObject(this.renderer); - - this.contentWindow.removeEventListener("resize", this.onResize, false); + this.contentWindow.removeEventListener("resize", this._onResize, false); } }; @@ -1023,36 +1072,36 @@ TiltVisualizer.Controller = function TV_Controller(aCanvas, aPresenter) /** * The initial controller dimensions and offset, in pixels. */ - this.zoom = aPresenter.transforms.zoom; - this.left = (aPresenter.contentWindow.pageXOffset || 0) * this.zoom; - this.top = (aPresenter.contentWindow.pageYOffset || 0) * this.zoom; - this.width = aCanvas.width; - this.height = aCanvas.height; + this._zoom = aPresenter.transforms.zoom; + this._left = (aPresenter.contentWindow.pageXOffset || 0) * this._zoom; + this._top = (aPresenter.contentWindow.pageYOffset || 0) * this._zoom; + this._width = aCanvas.width; + this._height = aCanvas.height; /** * Arcball used to control the visualization using the mouse. */ this.arcball = new TiltVisualizer.Arcball( - this.presenter.chromeWindow, this.width, this.height, 0, + this.presenter.chromeWindow, this._width, this._height, 0, [ - this.width + this.left < aPresenter.maxTextureSize ? -this.left : 0, - this.height + this.top < aPresenter.maxTextureSize ? -this.top : 0 + this._width + this._left < aPresenter._maxTextureSize ? -this._left : 0, + this._height + this._top < aPresenter._maxTextureSize ? -this._top : 0 ]); /** * Object containing the rotation quaternion and the translation amount. */ - this.coordinates = null; + this._coordinates = null; // bind the owner object to the necessary functions - TiltUtils.bindObjectFunc(this, "update"); - TiltUtils.bindObjectFunc(this, "^on"); + TiltUtils.bindObjectFunc(this, "_update"); + TiltUtils.bindObjectFunc(this, "^_on"); // add the necessary event listeners this.addEventListeners(); // attach this controller's update function to the presenter ondraw event - aPresenter.ondraw = this.update; + this.presenter._controllerUpdate = this._update; }; TiltVisualizer.Controller.prototype = { @@ -1066,19 +1115,19 @@ TiltVisualizer.Controller.prototype = { let presenter = this.presenter; // bind commonly used mouse and keyboard events with the controller - canvas.addEventListener("mousedown", this.onMouseDown, false); - canvas.addEventListener("mouseup", this.onMouseUp, false); - canvas.addEventListener("mousemove", this.onMouseMove, false); - canvas.addEventListener("mouseover", this.onMouseOver, false); - canvas.addEventListener("mouseout", this.onMouseOut, false); - canvas.addEventListener("MozMousePixelScroll", this.onMozScroll, false); - canvas.addEventListener("keydown", this.onKeyDown, false); - canvas.addEventListener("keyup", this.onKeyUp, false); - canvas.addEventListener("keypress", this.onKeyPress, true); - canvas.addEventListener("blur", this.onBlur, false); + canvas.addEventListener("mousedown", this._onMouseDown, false); + canvas.addEventListener("mouseup", this._onMouseUp, false); + canvas.addEventListener("mousemove", this._onMouseMove, false); + canvas.addEventListener("mouseover", this._onMouseOver, false); + canvas.addEventListener("mouseout", this._onMouseOut, false); + canvas.addEventListener("MozMousePixelScroll", this._onMozScroll, false); + canvas.addEventListener("keydown", this._onKeyDown, false); + canvas.addEventListener("keyup", this._onKeyUp, false); + canvas.addEventListener("keypress", this._onKeyPress, true); + canvas.addEventListener("blur", this._onBlur, false); // handle resize events to change the arcball dimensions - presenter.contentWindow.addEventListener("resize", this.onResize, false); + presenter.contentWindow.addEventListener("resize", this._onResize, false); }, /** @@ -1089,45 +1138,47 @@ TiltVisualizer.Controller.prototype = { let canvas = this.canvas; let presenter = this.presenter; - canvas.removeEventListener("mousedown", this.onMouseDown, false); - canvas.removeEventListener("mouseup", this.onMouseUp, false); - canvas.removeEventListener("mousemove", this.onMouseMove, false); - canvas.removeEventListener("mouseover", this.onMouseOver, false); - canvas.removeEventListener("mouseout", this.onMouseOut, false); - canvas.removeEventListener("MozMousePixelScroll", this.onMozScroll, false); - canvas.removeEventListener("keydown", this.onKeyDown, false); - canvas.removeEventListener("keyup", this.onKeyUp, false); - canvas.removeEventListener("keypress", this.onKeyPress, true); - canvas.removeEventListener("blur", this.onBlur, false); + canvas.removeEventListener("mousedown", this._onMouseDown, false); + canvas.removeEventListener("mouseup", this._onMouseUp, false); + canvas.removeEventListener("mousemove", this._onMouseMove, false); + canvas.removeEventListener("mouseover", this._onMouseOver, false); + canvas.removeEventListener("mouseout", this._onMouseOut, false); + canvas.removeEventListener("MozMousePixelScroll", this._onMozScroll, false); + canvas.removeEventListener("keydown", this._onKeyDown, false); + canvas.removeEventListener("keyup", this._onKeyUp, false); + canvas.removeEventListener("keypress", this._onKeyPress, true); + canvas.removeEventListener("blur", this._onBlur, false); - presenter.contentWindow.removeEventListener("resize", this.onResize,false); + presenter.contentWindow.removeEventListener("resize", this._onResize, false); }, /** * Function called each frame, updating the visualization camera transforms. * - * @param {Number} aFrames - * the current animation frame count + * @param {Number} aTime + * total time passed since rendering started + * @param {Number} aDelta + * the current animation frame delta */ - update: function TVC_update(aFrames) + _update: function TVC__update(aTime, aDelta) { - this.frames = aFrames; - this.coordinates = this.arcball.update(); + this._time = aTime; + this._coordinates = this.arcball.update(aDelta); - this.presenter.setRotation(this.coordinates.rotation); - this.presenter.setTranslation(this.coordinates.translation); + this.presenter.setRotation(this._coordinates.rotation); + this.presenter.setTranslation(this._coordinates.translation); }, /** * Called once after every time a mouse button is pressed. */ - onMouseDown: function TVC_onMouseDown(e) + _onMouseDown: function TVC__onMouseDown(e) { e.target.focus(); e.preventDefault(); e.stopPropagation(); - if (this.frames < MOUSE_INTRO_DELAY) { + if (this._time < MOUSE_INTRO_DELAY) { return; } @@ -1142,12 +1193,12 @@ TiltVisualizer.Controller.prototype = { /** * Called every time a mouse button is released. */ - onMouseUp: function TVC_onMouseUp(e) + _onMouseUp: function TVC__onMouseUp(e) { e.preventDefault(); e.stopPropagation(); - if (this.frames < MOUSE_INTRO_DELAY) { + if (this._time < MOUSE_INTRO_DELAY) { return; } @@ -1170,12 +1221,12 @@ TiltVisualizer.Controller.prototype = { /** * Called every time the mouse moves. */ - onMouseMove: function TVC_onMouseMove(e) + _onMouseMove: function TVC__onMouseMove(e) { e.preventDefault(); e.stopPropagation(); - if (this.frames < MOUSE_INTRO_DELAY) { + if (this._time < MOUSE_INTRO_DELAY) { return; } @@ -1189,7 +1240,7 @@ TiltVisualizer.Controller.prototype = { /** * Called when the mouse leaves the visualization bounds. */ - onMouseOver: function TVC_onMouseOver(e) + _onMouseOver: function TVC__onMouseOver(e) { e.preventDefault(); e.stopPropagation(); @@ -1200,7 +1251,7 @@ TiltVisualizer.Controller.prototype = { /** * Called when the mouse leaves the visualization bounds. */ - onMouseOut: function TVC_onMouseOut(e) + _onMouseOut: function TVC__onMouseOut(e) { e.preventDefault(); e.stopPropagation(); @@ -1211,7 +1262,7 @@ TiltVisualizer.Controller.prototype = { /** * Called when the mouse wheel is used. */ - onMozScroll: function TVC_onMozScroll(e) + _onMozScroll: function TVC__onMozScroll(e) { e.preventDefault(); e.stopPropagation(); @@ -1222,7 +1273,7 @@ TiltVisualizer.Controller.prototype = { /** * Called when a key is pressed. */ - onKeyDown: function TVC_onKeyDown(e) + _onKeyDown: function TVC__onKeyDown(e) { let code = e.keyCode || e.which; @@ -1238,13 +1289,21 @@ TiltVisualizer.Controller.prototype = { /** * Called when a key is released. */ - onKeyUp: function TVC_onKeyUp(e) + _onKeyUp: function TVC__onKeyUp(e) { let code = e.keyCode || e.which; if (code === e.DOM_VK_X) { this.presenter.deleteNode(); } + if (code === e.DOM_VK_F) { + let highlight = this.presenter._highlight; + let zoom = this.presenter.transforms.zoom; + + this.arcball.moveIntoView(vec3.lerp( + vec3.scale(highlight.v0, zoom, []), + vec3.scale(highlight.v1, zoom, []), 0.5)); + } if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) { e.preventDefault(); e.stopPropagation(); @@ -1255,7 +1314,7 @@ TiltVisualizer.Controller.prototype = { /** * Called when a key is pressed. */ - onKeyPress: function TVC_onKeyPress(e) + _onKeyPress: function TVC__onKeyPress(e) { let tilt = this.presenter.chromeWindow.Tilt; @@ -1269,14 +1328,14 @@ TiltVisualizer.Controller.prototype = { /** * Called when the canvas looses focus. */ - onBlur: function TVC_onBlur(e) { + _onBlur: function TVC__onBlur(e) { this.arcball.cancelKeyEvents(); }, /** * Called when the content window of the current browser is resized. */ - onResize: function TVC_onResize(e) + _onResize: function TVC__onResize(e) { let zoom = this.presenter.chromeWindow.InspectorUI.highlighter.zoom; let width = e.target.innerWidth * zoom; @@ -1298,13 +1357,14 @@ TiltVisualizer.Controller.prototype = { /** * Function called when this object is destroyed. */ - finalize: function TVC_finalize() + _finalize: function TVC__finalize() { TiltUtils.destroyObject(this.arcball); - TiltUtils.destroyObject(this.coordinates); + TiltUtils.destroyObject(this._coordinates); this.removeEventListeners(); - this.presenter.ondraw = null; + this.presenter.controller = null; + this.presenter._controllerUpdate = null; } }; @@ -1394,9 +1454,12 @@ TiltVisualizer.Arcball.prototype = { * and the zoom amount. These values will be returned as "rotation" and * "translation" properties inside an object. * + * @param {Number} aDelta + * the current animation frame delta + * * @return {Object} the rotation quaternion and the translation amount */ - update: function TVA_update() + update: function TVA_update(aDelta) { let mousePress = this._mousePress; let mouseRelease = this._mouseRelease; @@ -1434,7 +1497,7 @@ TiltVisualizer.Arcball.prototype = { this._rotating = true; // find the sphere coordinates of the mouse positions - this.pointToSphere(x, y, this.width, this.height, this.radius, endVec); + this._pointToSphere(x, y, this.width, this.height, this.radius, endVec); // compute the vector perpendicular to the start & end vectors vec3.cross(startVec, endVec, pVec); @@ -1554,11 +1617,19 @@ TiltVisualizer.Arcball.prototype = { (additionalTrans[1] - deltaAdditionalTrans[1]) * ARCBALL_SENSITIVITY; // create an additional rotation based on the key events - quat4.fromEuler(deltaAdditionalRot[0], deltaAdditionalRot[1], 0, deltaRot); + quat4.fromEuler( + deltaAdditionalRot[0], + deltaAdditionalRot[1], + deltaAdditionalRot[2], deltaRot); // create an additional translation based on the key events vec3.set([deltaAdditionalTrans[0], deltaAdditionalTrans[1], 0], deltaTrans); + // handle the reset animation steps if necessary + if (this._resetInProgress) { + this._nextResetStep(aDelta || 1); + } + // return the current rotation and translation return { rotation: quat4.multiply(deltaRot, currentRot), @@ -1583,11 +1654,11 @@ TiltVisualizer.Arcball.prototype = { this._mousePress[0] = x; this._mousePress[1] = y; this._mouseButton = aButton; - this._cancelResetInterval(); + this._cancelReset(); this._save(); // find the sphere coordinates of the mouse positions - this.pointToSphere( + this._pointToSphere( x, y, this.width, this.height, this.radius, this._startVec); quat4.set(this._currentRot, this._lastRot); @@ -1659,7 +1730,7 @@ TiltVisualizer.Arcball.prototype = { */ zoom: function TVA_zoom(aZoom) { - this._cancelResetInterval(); + this._cancelReset(); this._zoomAmount = TiltMath.clamp(this._zoomAmount - aZoom, ARCBALL_ZOOM_MIN, ARCBALL_ZOOM_MAX); }, @@ -1673,7 +1744,7 @@ TiltVisualizer.Arcball.prototype = { */ keyDown: function TVA_keyDown(aCode) { - this._cancelResetInterval(); + this._cancelReset(); this._keyCode[aCode] = true; }, @@ -1705,7 +1776,7 @@ TiltVisualizer.Arcball.prototype = { * @param {Array} aSphereVec * a 3d vector to store the sphere coordinates */ - pointToSphere: function TVA_pointToSphere( + _pointToSphere: function TVA__pointToSphere( x, y, aWidth, aHeight, aRadius, aSphereVec) { // adjust point coords and scale down to range of [-1..1] @@ -1750,6 +1821,32 @@ TiltVisualizer.Arcball.prototype = { this._mouseButton = -1; }, + /** + * Incremental translation method. + * + * @param {Array} aTranslation + * the translation ammount on the [x, y] axis + */ + translate: function TVP_translate(aTranslation) + { + this._additionalTrans[0] += aTranslation[0]; + this._additionalTrans[1] += aTranslation[1]; + }, + + /** + * Incremental rotation method. + * + * @param {Array} aRotation + * the rotation ammount along the [x, y, z] axis + */ + rotate: function TVP_rotate(aRotation) + { + // explicitly rotate along y, x, z values because they're eulerian angles + this._additionalRot[0] += TiltMath.radians(aRotation[1]); + this._additionalRot[1] += TiltMath.radians(aRotation[0]); + this._additionalRot[2] += TiltMath.radians(aRotation[2]); + }, + /** * Moves a target point into view only if it's outside the currently visible * area bounds (in which case it also resets any additional transforms). @@ -1803,48 +1900,53 @@ TiltVisualizer.Arcball.prototype = { */ reset: function TVA_reset(aFinalTranslation, aFinalRotation) { - if ("function" === typeof this.onResetStart) { - this.onResetStart(); - this.onResetStart = null; + if ("function" === typeof this._onResetStart) { + this._onResetStart(); + this._onResetStart = null; } - let func = this._nextResetIntervalStep.bind(this); - this.cancelMouseEvents(); this.cancelKeyEvents(); - this._cancelResetInterval(); + this._cancelReset(); this._save(); this._resetFinalTranslation = vec3.create(aFinalTranslation); this._resetFinalRotation = quat4.create(aFinalRotation); - this._resetInterval = - this.chromeWindow.setInterval(func, ARCBALL_RESET_INTERVAL); + this._resetInProgress = true; }, /** * Cancels the current arcball reset animation if there is one. */ - _cancelResetInterval: function TVA__cancelResetInterval() + _cancelReset: function TVA__cancelReset() { - if (this._resetInterval) { - this.chromeWindow.clearInterval(this._resetInterval); - - this._resetInterval = null; + if (this._resetInProgress) { + this._resetInProgress = false; this._save(); - if ("function" === typeof this.onResetFinish) { - this.onResetFinish(); - this.onResetFinish = null; + if ("function" === typeof this._onResetFinish) { + this._onResetFinish(); + this._onResetFinish = null; + this._onResetStep = null; } } }, /** * Executes the next step in the arcball reset animation. + * + * @param {Number} aDelta + * the current animation frame delta */ - _nextResetIntervalStep: function TVA__nextResetIntervalStep() + _nextResetStep: function TVA__nextResetStep(aDelta) { - let fDelta = EPSILON * EPSILON; + // a very large animation frame delta (in case of seriously low framerate) + // would cause all the interpolations to become highly unstable + aDelta = TiltMath.clamp(aDelta, 1, 100); + + let fNearZero = EPSILON * EPSILON; + let fInterpLin = ARCBALL_RESET_LINEAR_FACTOR * aDelta; + let fInterpSph = ARCBALL_RESET_SPHERICAL_FACTOR; let fTran = this._resetFinalTranslation; let fRot = this._resetFinalRotation; @@ -1852,25 +1954,29 @@ TiltVisualizer.Arcball.prototype = { let r = quat4.multiply(quat4.inverse(quat4.create(this._currentRot)), fRot); // reset the rotation quaternion and translation vector - vec3.lerp(this._currentTrans, t, ARCBALL_RESET_FACTOR / 4); - quat4.slerp(this._currentRot, r, 1 - ARCBALL_RESET_FACTOR); + vec3.lerp(this._currentTrans, t, fInterpLin); + quat4.slerp(this._currentRot, r, fInterpSph); // also reset any additional transforms by the keyboard or mouse - vec3.scale(this._additionalTrans, ARCBALL_RESET_FACTOR); - vec3.scale(this._additionalRot, ARCBALL_RESET_FACTOR); - this._zoomAmount *= ARCBALL_RESET_FACTOR; + vec3.scale(this._additionalTrans, fInterpLin); + vec3.scale(this._additionalRot, fInterpLin); + this._zoomAmount *= fInterpLin; // clear the loop if the all values are very close to zero - if (vec3.length(vec3.subtract(this._lastRot, fRot, [])) < fDelta && - vec3.length(vec3.subtract(this._deltaRot, fRot, [])) < fDelta && - vec3.length(vec3.subtract(this._currentRot, fRot, [])) < fDelta && - vec3.length(vec3.subtract(this._lastTrans, fTran, [])) < fDelta && - vec3.length(vec3.subtract(this._deltaTrans, fTran, [])) < fDelta && - vec3.length(vec3.subtract(this._currentTrans, fTran, [])) < fDelta && - vec3.length(this._additionalRot) < fDelta && - vec3.length(this._additionalTrans) < fDelta) { + if (vec3.length(vec3.subtract(this._lastRot, fRot, [])) < fNearZero && + vec3.length(vec3.subtract(this._deltaRot, fRot, [])) < fNearZero && + vec3.length(vec3.subtract(this._currentRot, fRot, [])) < fNearZero && + vec3.length(vec3.subtract(this._lastTrans, fTran, [])) < fNearZero && + vec3.length(vec3.subtract(this._deltaTrans, fTran, [])) < fNearZero && + vec3.length(vec3.subtract(this._currentTrans, fTran, [])) < fNearZero && + vec3.length(this._additionalRot) < fNearZero && + vec3.length(this._additionalTrans) < fNearZero) { - this._cancelResetInterval(); + this._cancelReset(); + } + + if ("function" === typeof this._onResetStep) { + this._onResetStep(); } }, @@ -1927,9 +2033,9 @@ TiltVisualizer.Arcball.prototype = { /** * Function called when this object is destroyed. */ - finalize: function TVA_finalize() + _finalize: function TVA__finalize() { - this._cancelResetInterval(); + this._cancelReset(); } }; diff --git a/browser/devtools/tilt/TiltWorkerCrafter.js b/browser/devtools/tilt/TiltWorkerCrafter.js index 4f89a6f7181..342eda2ae78 100644 --- a/browser/devtools/tilt/TiltWorkerCrafter.js +++ b/browser/devtools/tilt/TiltWorkerCrafter.js @@ -38,9 +38,6 @@ ***** END LICENSE BLOCK *****/ "use strict"; -const SIXTEEN_OVER_255 = 16 / 255; -const ONE_OVER_255 = 1 / 255; - /** * Given the initialization data (thickness, sizes and information about * each DOM node) this worker sends back the arrays representing @@ -52,27 +49,44 @@ const ONE_OVER_255 = 1 / 255; self.onmessage = function TWC_onMessage(event) { let data = event.data; + let maxGroupNodes = parseInt(data.maxGroupNodes); let thickness = data.thickness; let style = data.style; let texWidth = data.texWidth; let texHeight = data.texHeight; let nodesInfo = data.nodesInfo; - // create the arrays used to construct the 3D mesh data - let vertices = []; - let texCoord = []; - let color = []; - let stacksIndices = []; - let wireframeIndices = []; - let meshWidth = 0; - let meshHeight = 0; + let mesh = { + allVertices: [], + groups: [], + width: 0, + height: 0 + }; + + let vertices; + let texCoord; + let color; + let stacksIndices; + let wireframeIndices; + let index; // seed the random function to get the same values each time // we're doing this to avoid ugly z-fighting with overlapping nodes self.random.seed(0); // go through all the dom nodes and compute the verts, texcoord etc. - for (let n = 0, i = 0, len = nodesInfo.length; n < len; n++) { + for (let n = 0, len = nodesInfo.length; n < len; n++) { + + // check if we need to start creating a new group + if (n % maxGroupNodes === 0) { + vertices = []; // recreate the arrays used to construct the 3D mesh data + texCoord = []; + color = []; + stacksIndices = []; + wireframeIndices = []; + index = 0; + } + let info = nodesInfo[n]; let depth = info.depth; let coord = info.coord; @@ -155,6 +169,7 @@ self.onmessage = function TWC_onMessage(event) g20, g21, g22, g20, g21, g22); + let i = index; // number of vertex points, used to create the indices array let ip1 = i + 1; let ip2 = ip1 + 1; let ip3 = ip2 + 1; @@ -182,23 +197,28 @@ self.onmessage = function TWC_onMessage(event) ip11, ip3, ip10, ip2); } - // number of vertex points, used for creating the indices array - i += 12; // a vertex has 3 coords: x, y and z + // there are 12 vertices in a stack representing a node + index += 12; // set the maximum mesh width and height to calculate the center offset - meshWidth = Math.max(w, meshWidth); - meshHeight = Math.max(h, meshHeight); + mesh.width = Math.max(w, mesh.width); + mesh.height = Math.max(h, mesh.height); + + // check if we need to save the currently active group; this happens after + // we filled all the "slots" in a group or there aren't any remaining nodes + if (((n + 1) % maxGroupNodes === 0) || (n === len - 1)) { + mesh.groups.push({ + vertices: vertices, + texCoord: texCoord, + color: color, + stacksIndices: stacksIndices, + wireframeIndices: wireframeIndices + }); + mesh.allVertices = mesh.allVertices.concat(vertices); + } } - self.postMessage({ - vertices: vertices, - texCoord: texCoord, - color: color, - stacksIndices: stacksIndices, - wireframeIndices: wireframeIndices, - meshWidth: meshWidth, - meshHeight: meshHeight - }); + self.postMessage(mesh); close(); }; diff --git a/browser/devtools/tilt/test/Makefile.in b/browser/devtools/tilt/test/Makefile.in index d832b38e49f..c3e06857d26 100644 --- a/browser/devtools/tilt/test/Makefile.in +++ b/browser/devtools/tilt/test/Makefile.in @@ -80,6 +80,7 @@ _BROWSER_TEST_FILES = \ browser_tilt_picking_highlight01.js \ browser_tilt_picking_highlight02.js \ browser_tilt_picking_highlight03.js \ + browser_tilt_picking_miv.js \ browser_tilt_utils01.js \ browser_tilt_utils02.js \ browser_tilt_utils03.js \ diff --git a/browser/devtools/tilt/test/browser_tilt_arcball-reset-typeahead.js b/browser/devtools/tilt/test/browser_tilt_arcball-reset-typeahead.js index d81672a819a..2cb99eb0624 100644 --- a/browser/devtools/tilt/test/browser_tilt_arcball-reset-typeahead.js +++ b/browser/devtools/tilt/test/browser_tilt_arcball-reset-typeahead.js @@ -62,11 +62,23 @@ function performTest(canvas, arcball, callback) { window.setTimeout(function() { info("Synthesizing arcball reset key press."); - arcball.onResetStart = function() { + arcball._onResetStart = function() { info("Starting arcball reset animation."); }; - arcball.onResetFinish = function() { + arcball._onResetStep = function() { + info("\nlastRot: " + quat4.str(arcball._lastRot) + + "\ndeltaRot: " + quat4.str(arcball._deltaRot) + + "\ncurrentRot: " + quat4.str(arcball._currentRot) + + "\nlastTrans: " + vec3.str(arcball._lastTrans) + + "\ndeltaTrans: " + vec3.str(arcball._deltaTrans) + + "\ncurrentTrans: " + vec3.str(arcball._currentTrans) + + "\nadditionalRot: " + vec3.str(arcball._additionalRot) + + "\nadditionalTrans: " + vec3.str(arcball._additionalTrans) + + "\nzoomAmount: " + arcball._zoomAmount); + }; + + arcball._onResetFinish = function() { ok(isApproxVec(arcball._lastRot, [0, 0, 0, 1]), "The arcball _lastRot field wasn't reset correctly."); ok(isApproxVec(arcball._deltaRot, [0, 0, 0, 1]), @@ -89,8 +101,10 @@ function performTest(canvas, arcball, callback) { ok(isApproxVec([arcball._zoomAmount], [0]), "The arcball _zoomAmount field wasn't reset correctly."); - info("Finishing arcball reset test."); - callback(); + executeSoon(function() { + info("Finishing arcball reset test."); + callback(); + }); }; EventUtils.synthesizeKey("VK_R", { type: "keydown" }); diff --git a/browser/devtools/tilt/test/browser_tilt_arcball-reset.js b/browser/devtools/tilt/test/browser_tilt_arcball-reset.js index 899345863b3..42a0ac2de75 100644 --- a/browser/devtools/tilt/test/browser_tilt_arcball-reset.js +++ b/browser/devtools/tilt/test/browser_tilt_arcball-reset.js @@ -60,11 +60,23 @@ function performTest(canvas, arcball, callback) { window.setTimeout(function() { info("Synthesizing arcball reset key press."); - arcball.onResetStart = function() { + arcball._onResetStart = function() { info("Starting arcball reset animation."); }; - arcball.onResetFinish = function() { + arcball._onResetStep = function() { + info("\nlastRot: " + quat4.str(arcball._lastRot) + + "\ndeltaRot: " + quat4.str(arcball._deltaRot) + + "\ncurrentRot: " + quat4.str(arcball._currentRot) + + "\nlastTrans: " + vec3.str(arcball._lastTrans) + + "\ndeltaTrans: " + vec3.str(arcball._deltaTrans) + + "\ncurrentTrans: " + vec3.str(arcball._currentTrans) + + "\nadditionalRot: " + vec3.str(arcball._additionalRot) + + "\nadditionalTrans: " + vec3.str(arcball._additionalTrans) + + "\nzoomAmount: " + arcball._zoomAmount); + }; + + arcball._onResetFinish = function() { ok(isApproxVec(arcball._lastRot, [0, 0, 0, 1]), "The arcball _lastRot field wasn't reset correctly."); ok(isApproxVec(arcball._deltaRot, [0, 0, 0, 1]), @@ -87,8 +99,10 @@ function performTest(canvas, arcball, callback) { ok(isApproxVec([arcball._zoomAmount], [0]), "The arcball _zoomAmount field wasn't reset correctly."); - info("Finishing arcball reset test."); - callback(); + executeSoon(function() { + info("Finishing arcball reset test."); + callback(); + }); }; EventUtils.synthesizeKey("VK_R", { type: "keydown" }); diff --git a/browser/devtools/tilt/test/browser_tilt_arcball.js b/browser/devtools/tilt/test/browser_tilt_arcball.js index bcbe0a47e66..3d1078e1bc3 100644 --- a/browser/devtools/tilt/test/browser_tilt_arcball.js +++ b/browser/devtools/tilt/test/browser_tilt_arcball.js @@ -48,10 +48,10 @@ function test() { let arcball3 = new TiltVisualizer.Arcball(window, 512, 512); let sphereVec = vec3.create(); - arcball3.pointToSphere(123, 456, 256, 512, 512, sphereVec); + arcball3._pointToSphere(123, 456, 256, 512, 512, sphereVec); ok(isApproxVec(sphereVec, [-0.009765625, 0.390625, 0.9204980731010437]), - "The pointToSphere() function didn't map the coordinates correctly."); + "The _pointToSphere() function didn't map the coordinates correctly."); let stack1 = []; let expect1 = [ diff --git a/browser/devtools/tilt/test/browser_tilt_controller.js b/browser/devtools/tilt/test/browser_tilt_controller.js index be03ba48709..45bf84db057 100644 --- a/browser/devtools/tilt/test/browser_tilt_controller.js +++ b/browser/devtools/tilt/test/browser_tilt_controller.js @@ -47,7 +47,7 @@ function test() { EventUtils.synthesizeKey("VK_A", { type: "keydown" }); EventUtils.synthesizeKey("VK_LEFT", { type: "keydown" }); - instance.controller.update(); + instance.controller._update(); ok(!isEqualVec(tran(), prev_tran), "After a translation key is pressed, the vector should change."); @@ -58,7 +58,7 @@ function test() { cancellingEvent(); - instance.controller.update(); + instance.controller._update(); ok(!isEqualVec(tran(), prev_tran), "Even if the canvas lost focus, the vector has some inertia."); @@ -70,7 +70,7 @@ function test() { while (!isEqualVec(tran(), prev_tran) || !isEqualVec(rot(), prev_rot)) { - instance.controller.update(); + instance.controller._update(); save(); } diff --git a/browser/devtools/tilt/test/browser_tilt_picking.js b/browser/devtools/tilt/test/browser_tilt_picking.js index 653e37f8121..327e1a922fb 100644 --- a/browser/devtools/tilt/test/browser_tilt_picking.js +++ b/browser/devtools/tilt/test/browser_tilt_picking.js @@ -21,15 +21,13 @@ function test() { let presenter = instance.presenter; let canvas = presenter.canvas; - presenter.onSetupMesh = function() { + presenter._onSetupMesh = function() { presenter.pickNode(canvas.width / 2, 10, { onpick: function(data) { ok(data.index > 0, "Simply picking a node didn't work properly."); - ok(!presenter.highlight.disabled, - "After only picking a node, it shouldn't be highlighted."); Services.obs.addObserver(cleanup, DESTROYED, false); InspectorUI.closeInspectorUI(); diff --git a/browser/devtools/tilt/test/browser_tilt_picking_delete.js b/browser/devtools/tilt/test/browser_tilt_picking_delete.js index 98c83608572..82418ef722f 100644 --- a/browser/devtools/tilt/test/browser_tilt_picking_delete.js +++ b/browser/devtools/tilt/test/browser_tilt_picking_delete.js @@ -23,13 +23,13 @@ function test() { presenter = instance.presenter; Services.obs.addObserver(whenNodeRemoved, NODE_REMOVED, false); - presenter.onSetupMesh = function() { + presenter._onSetupMesh = function() { presenter.highlightNodeAt(presenter.canvas.width / 2, 10, { onpick: function() { ok(presenter._currentSelection > 0, "Highlighting a node didn't work properly."); - ok(!presenter.highlight.disabled, + ok(!presenter._highlight.disabled, "After highlighting a node, it should be highlighted. D'oh."); presenter.deleteNode(); @@ -44,14 +44,14 @@ function test() { function whenNodeRemoved() { ok(presenter._currentSelection > 0, "Deleting a node shouldn't change the current selection."); - ok(presenter.highlight.disabled, + ok(presenter._highlight.disabled, "After deleting a node, it shouldn't be highlighted."); let nodeIndex = presenter._currentSelection; - let meshData = presenter.meshData; + let vertices = presenter._meshStacks[0].vertices.components; for (let i = 0, k = 36 * nodeIndex; i < 36; i++) { - is(meshData.vertices[i + k], 0, + is(vertices[i + k], 0, "The stack vertices weren't degenerated properly."); } diff --git a/browser/devtools/tilt/test/browser_tilt_picking_highlight01-offs.js b/browser/devtools/tilt/test/browser_tilt_picking_highlight01-offs.js index 48485154998..0c7b58b4fed 100644 --- a/browser/devtools/tilt/test/browser_tilt_picking_highlight01-offs.js +++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight01-offs.js @@ -24,7 +24,7 @@ function test() { presenter = instance.presenter; Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false); - presenter.onInitializationFinished = function() { + presenter._onInitializationFinished = function() { let contentDocument = presenter.contentWindow.document; let div = contentDocument.getElementById("far-far-away"); @@ -38,9 +38,9 @@ function test() { function whenHighlighting() { ok(presenter._currentSelection > 0, "Highlighting a node didn't work properly."); - ok(!presenter.highlight.disabled, + ok(!presenter._highlight.disabled, "After highlighting a node, it should be highlighted. D'oh."); - ok(presenter.controller.arcball._resetInterval, + ok(presenter.controller.arcball._resetInProgress, "Highlighting a node that's not already visible should trigger a reset!"); executeSoon(function() { @@ -52,7 +52,7 @@ function whenHighlighting() { function whenUnhighlighting() { ok(presenter._currentSelection < 0, "Unhighlighting a should remove the current selection."); - ok(presenter.highlight.disabled, + ok(presenter._highlight.disabled, "After unhighlighting a node, it shouldn't be highlighted anymore. D'oh."); executeSoon(function() { diff --git a/browser/devtools/tilt/test/browser_tilt_picking_highlight01.js b/browser/devtools/tilt/test/browser_tilt_picking_highlight01.js index 46ae839e71b..95910e86bca 100644 --- a/browser/devtools/tilt/test/browser_tilt_picking_highlight01.js +++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight01.js @@ -23,7 +23,7 @@ function test() { presenter = instance.presenter; Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false); - presenter.onSetupMesh = function() { + presenter._onSetupMesh = function() { let contentDocument = presenter.contentWindow.document; let div = contentDocument.getElementById("first-law"); @@ -37,9 +37,9 @@ function test() { function whenHighlighting() { ok(presenter._currentSelection > 0, "Highlighting a node didn't work properly."); - ok(!presenter.highlight.disabled, + ok(!presenter._highlight.disabled, "After highlighting a node, it should be highlighted. D'oh."); - ok(!presenter.controller.arcball._resetInterval, + ok(!presenter.controller.arcball._resetInProgress, "Highlighting a node that's already visible shouldn't trigger a reset."); executeSoon(function() { @@ -51,7 +51,7 @@ function whenHighlighting() { function whenUnhighlighting() { ok(presenter._currentSelection < 0, "Unhighlighting a should remove the current selection."); - ok(presenter.highlight.disabled, + ok(presenter._highlight.disabled, "After unhighlighting a node, it shouldn't be highlighted anymore. D'oh."); executeSoon(function() { diff --git a/browser/devtools/tilt/test/browser_tilt_picking_highlight02.js b/browser/devtools/tilt/test/browser_tilt_picking_highlight02.js index a378495ff1c..85656bb192c 100644 --- a/browser/devtools/tilt/test/browser_tilt_picking_highlight02.js +++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight02.js @@ -23,7 +23,7 @@ function test() { presenter = instance.presenter; Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false); - presenter.onSetupMesh = function() { + presenter._onSetupMesh = function() { presenter.highlightNodeAt(presenter.canvas.width / 2, 10); }; } @@ -34,7 +34,7 @@ function test() { function whenHighlighting() { ok(presenter._currentSelection > 0, "Highlighting a node didn't work properly."); - ok(!presenter.highlight.disabled, + ok(!presenter._highlight.disabled, "After highlighting a node, it should be highlighted. D'oh."); executeSoon(function() { @@ -46,7 +46,7 @@ function whenHighlighting() { function whenUnhighlighting() { ok(presenter._currentSelection < 0, "Unhighlighting a should remove the current selection."); - ok(presenter.highlight.disabled, + ok(presenter._highlight.disabled, "After unhighlighting a node, it shouldn't be highlighted anymore. D'oh."); executeSoon(function() { diff --git a/browser/devtools/tilt/test/browser_tilt_picking_highlight03.js b/browser/devtools/tilt/test/browser_tilt_picking_highlight03.js index 0ad0633471a..66756081159 100644 --- a/browser/devtools/tilt/test/browser_tilt_picking_highlight03.js +++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight03.js @@ -23,7 +23,7 @@ function test() { presenter = instance.presenter; Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false); - presenter.onSetupMesh = function() { + presenter._onSetupMesh = function() { presenter.highlightNodeFor(5); // 1 = html, 2 = body, 3 = first div }; } @@ -34,7 +34,7 @@ function test() { function whenHighlighting() { ok(presenter._currentSelection > 0, "Highlighting a node didn't work properly."); - ok(!presenter.highlight.disabled, + ok(!presenter._highlight.disabled, "After highlighting a node, it should be highlighted. D'oh."); executeSoon(function() { @@ -46,7 +46,7 @@ function whenHighlighting() { function whenUnhighlighting() { ok(presenter._currentSelection < 0, "Unhighlighting a should remove the current selection."); - ok(presenter.highlight.disabled, + ok(presenter._highlight.disabled, "After unhighlighting a node, it shouldn't be highlighted anymore. D'oh."); executeSoon(function() { diff --git a/browser/devtools/tilt/test/browser_tilt_picking_miv.js b/browser/devtools/tilt/test/browser_tilt_picking_miv.js new file mode 100644 index 00000000000..d9735892098 --- /dev/null +++ b/browser/devtools/tilt/test/browser_tilt_picking_miv.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +let presenter; + +function test() { + if (!isTiltEnabled()) { + info("Skipping highlight test because Tilt isn't enabled."); + return; + } + if (!isWebGLSupported()) { + info("Skipping highlight test because WebGL isn't supported."); + return; + } + + requestLongerTimeout(10); + waitForExplicitFinish(); + + createTab(function() { + createTilt({ + onTiltOpen: function(instance) + { + presenter = instance.presenter; + Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false); + + presenter._onInitializationFinished = function() { + let contentDocument = presenter.contentWindow.document; + let div = contentDocument.getElementById("far-far-away"); + + presenter.highlightNode(div); + }; + } + }); + }); +} + +function whenHighlighting() { + ok(presenter._currentSelection > 0, + "Highlighting a node didn't work properly."); + ok(!presenter._highlight.disabled, + "After highlighting a node, it should be highlighted. D'oh."); + ok(!presenter.controller.arcball._resetInProgress, + "Highlighting a node that's not already visible shouldn't trigger a reset " + + "without this being explicitly requested!"); + + EventUtils.sendKey("F"); + executeSoon(whenBringingIntoView); +} + +function whenBringingIntoView() { + ok(presenter._currentSelection > 0, + "The node should still be selected."); + ok(!presenter._highlight.disabled, + "The node should still be highlighted"); + ok(presenter.controller.arcball._resetInProgress, + "Highlighting a node that's not already visible should trigger a reset " + + "when this is being explicitly requested!"); + + executeSoon(function() { + Services.obs.addObserver(cleanup, DESTROYED, false); + InspectorUI.closeInspectorUI(); + }); +} + +function cleanup() { + Services.obs.removeObserver(whenHighlighting, HIGHLIGHTING); + Services.obs.removeObserver(cleanup, DESTROYED); + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/devtools/tilt/test/browser_tilt_utils05.js b/browser/devtools/tilt/test/browser_tilt_utils05.js index 5c93eecd1dd..ef00d2a16ff 100644 --- a/browser/devtools/tilt/test/browser_tilt_utils05.js +++ b/browser/devtools/tilt/test/browser_tilt_utils05.js @@ -2,10 +2,6 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -let tmp = {}; -Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tmp); -let LayoutHelpers = tmp.LayoutHelpers; - function init(callback) { let iframe = gBrowser.ownerDocument.createElement("iframe"); diff --git a/browser/devtools/tilt/test/browser_tilt_utils06.js b/browser/devtools/tilt/test/browser_tilt_utils06.js index e1ab6f2d362..eee915261b0 100644 --- a/browser/devtools/tilt/test/browser_tilt_utils06.js +++ b/browser/devtools/tilt/test/browser_tilt_utils06.js @@ -11,7 +11,7 @@ let someObject = { }; let anotherObject = { - finalize: function() + _finalize: function() { someObject.c = 3; } diff --git a/browser/devtools/tilt/test/browser_tilt_visualizer.js b/browser/devtools/tilt/test/browser_tilt_visualizer.js index b1c87b6773e..fbbcb737317 100644 --- a/browser/devtools/tilt/test/browser_tilt_visualizer.js +++ b/browser/devtools/tilt/test/browser_tilt_visualizer.js @@ -19,7 +19,6 @@ function test() { chromeWindow: window, contentWindow: gBrowser.selectedBrowser.contentWindow, parentNode: gBrowser.selectedBrowser.parentNode, - requestAnimationFrame: window.mozRequestAnimationFrame, inspectorUI: window.InspectorUI, onError: function onWebGLError() @@ -71,33 +70,33 @@ function test() { } function testPresenter(presenter) { - ok(presenter.renderer, + ok(presenter._renderer, "The presenter renderer wasn't initialized properly."); - ok(presenter.visualizationProgram, + ok(presenter._visualizationProgram, "The presenter visualizationProgram wasn't initialized properly."); - ok(presenter.texture, + ok(presenter._texture, "The presenter texture wasn't initialized properly."); - ok(!presenter.meshStacks, + ok(!presenter._meshStacks, "The presenter meshStacks shouldn't be initialized yet."); - ok(!presenter.meshWireframe, + ok(!presenter._meshWireframe, "The presenter meshWireframe shouldn't be initialized yet."); - ok(presenter.traverseData, + ok(presenter._traverseData, "The presenter nodesInformation wasn't initialized properly."); - ok(presenter.highlight, + ok(presenter._highlight, "The presenter highlight wasn't initialized properly."); - ok(presenter.highlight.disabled, - "The presenter highlight should be initially disabled"); - ok(isApproxVec(presenter.highlight.v0, [0, 0, 0]), + ok(presenter._highlight.disabled, + "The presenter highlight should be initially disabled."); + ok(isApproxVec(presenter._highlight.v0, [0, 0, 0]), "The presenter highlight first vertex should be initially zeroed."); - ok(isApproxVec(presenter.highlight.v1, [0, 0, 0]), + ok(isApproxVec(presenter._highlight.v1, [0, 0, 0]), "The presenter highlight second vertex should be initially zeroed."); - ok(isApproxVec(presenter.highlight.v2, [0, 0, 0]), + ok(isApproxVec(presenter._highlight.v2, [0, 0, 0]), "The presenter highlight third vertex should be initially zeroed."); - ok(isApproxVec(presenter.highlight.v3, [0, 0, 0]), + ok(isApproxVec(presenter._highlight.v3, [0, 0, 0]), "The presenter highlight fourth vertex should be initially zeroed."); ok(presenter.transforms, "The presenter transforms wasn't initialized properly."); - ok(isApproxVec(presenter.transforms.zoom, 1), + is(presenter.transforms.zoom, 1, "The presenter transforms zoom should be initially 1."); ok(isApproxVec(presenter.transforms.offset, [0, 0, 0]), "The presenter transforms offset should be initially zeroed."); @@ -113,7 +112,7 @@ function testPresenter(presenter) { "The presenter transforms translation wasn't modified as it should"); ok(isApproxVec(presenter.transforms.rotation, [5, 6, 7, 8]), "The presenter transforms rotation wasn't modified as it should"); - ok(presenter.redraw, + ok(presenter._redraw, "The new transforms should have issued a redraw request."); } diff --git a/browser/devtools/tilt/test/browser_tilt_zoom.js b/browser/devtools/tilt/test/browser_tilt_zoom.js index a49ab9bdd2b..fe280dfba3b 100644 --- a/browser/devtools/tilt/test/browser_tilt_zoom.js +++ b/browser/devtools/tilt/test/browser_tilt_zoom.js @@ -35,7 +35,7 @@ function test() { let initialWidth = contentWindow.innerWidth; let initialHeight = contentWindow.innerHeight; - let renderer = instance.presenter.renderer; + let renderer = instance.presenter._renderer; let arcball = instance.controller.arcball; ok(isApprox(contentWindow.innerWidth * ZOOM, renderer.width, 1), diff --git a/browser/devtools/tilt/test/head.js b/browser/devtools/tilt/test/head.js index 137fb0f86c0..0ecce48f358 100644 --- a/browser/devtools/tilt/test/head.js +++ b/browser/devtools/tilt/test/head.js @@ -7,6 +7,7 @@ Components.utils.import("resource:///modules/devtools/TiltGL.jsm", tempScope); Components.utils.import("resource:///modules/devtools/TiltMath.jsm", tempScope); Components.utils.import("resource:///modules/devtools/TiltUtils.jsm", tempScope); Components.utils.import("resource:///modules/devtools/TiltVisualizer.jsm", tempScope); +Components.utils.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope); let TiltGL = tempScope.TiltGL; let EPSILON = tempScope.EPSILON; let TiltMath = tempScope.TiltMath; @@ -16,6 +17,7 @@ let mat4 = tempScope.mat4; let quat4 = tempScope.quat4; let TiltUtils = tempScope.TiltUtils; let TiltVisualizer = tempScope.TiltVisualizer; +let LayoutHelpers = tempScope.LayoutHelpers; const DEFAULT_HTML = "data:text/html," + diff --git a/browser/devtools/webconsole/GcliTiltCommands.jsm b/browser/devtools/webconsole/GcliTiltCommands.jsm new file mode 100644 index 00000000000..248508cf830 --- /dev/null +++ b/browser/devtools/webconsole/GcliTiltCommands.jsm @@ -0,0 +1,224 @@ +/* ***** 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 GCLI Commands. + * + * 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): + * Victor Porof (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +let EXPORTED_SYMBOLS = [ "GcliCommands" ]; + +Components.utils.import("resource:///modules/gcli.jsm"); +Components.utils.import("resource:///modules/HUDService.jsm"); + + +/** + * 'tilt' command + */ +gcli.addCommand({ + name: 'tilt', + description: gcli.lookup("tiltDesc"), + manual: gcli.lookup("tiltManual") +}); + + +/** + * 'tilt open' command + */ +gcli.addCommand({ + name: 'tilt open', + description: gcli.lookup("tiltOpenDesc"), + manual: gcli.lookup("tiltOpenManual"), + params: [ + { + name: "node", + type: "node", + defaultValue: null, + description: gcli.lookup("inspectNodeDesc"), + manual: gcli.lookup("inspectNodeManual") + } + ], + exec: function(args, context) { + let chromeWindow = context.environment.chromeDocument.defaultView; + let InspectorUI = chromeWindow.InspectorUI; + let Tilt = chromeWindow.Tilt; + + if (Tilt.currentInstance) { + Tilt.update(args.node); + } else { + let hudId = chromeWindow.HUDConsoleUI.getOpenHUD(); + let hud = HUDService.getHudReferenceById(hudId); + + if (hud && !hud.consolePanel) { + HUDService.deactivateHUDForContext(chromeWindow.gBrowser.selectedTab); + } + InspectorUI.openInspectorUI(args.node); + Tilt.initialize(); + } + } +}); + + +/** + * 'tilt translate' command + */ +gcli.addCommand({ + name: 'tilt translate', + description: gcli.lookup("tiltTranslateDesc"), + manual: gcli.lookup("tiltTranslateManual"), + params: [ + { + name: "x", + type: "number", + defaultValue: 0, + description: gcli.lookup("tiltTranslateXDesc"), + manual: gcli.lookup("tiltTranslateXManual") + }, + { + name: "y", + type: "number", + defaultValue: 0, + description: gcli.lookup("tiltTranslateYDesc"), + manual: gcli.lookup("tiltTranslateYManual") + } + ], + exec: function(args, context) { + let chromeWindow = context.environment.chromeDocument.defaultView; + let Tilt = chromeWindow.Tilt; + + if (Tilt.currentInstance) { + Tilt.currentInstance.controller.arcball.translate([args.x, args.y]); + } + } +}); + + +/** + * 'tilt rotate' command + */ +gcli.addCommand({ + name: 'tilt rotate', + description: gcli.lookup("tiltRotateDesc"), + manual: gcli.lookup("tiltRotateManual"), + params: [ + { + name: "x", + type: { name: 'number', min: -360, max: 360, step: 10 }, + defaultValue: 0, + description: gcli.lookup("tiltRotateXDesc"), + manual: gcli.lookup("tiltRotateXManual") + }, + { + name: "y", + type: { name: 'number', min: -360, max: 360, step: 10 }, + defaultValue: 0, + description: gcli.lookup("tiltRotateYDesc"), + manual: gcli.lookup("tiltRotateYManual") + }, + { + name: "z", + type: { name: 'number', min: -360, max: 360, step: 10 }, + defaultValue: 0, + description: gcli.lookup("tiltRotateZDesc"), + manual: gcli.lookup("tiltRotateZManual") + } + ], + exec: function(args, context) { + let chromeWindow = context.environment.chromeDocument.defaultView; + let Tilt = chromeWindow.Tilt; + + if (Tilt.currentInstance) { + Tilt.currentInstance.controller.arcball.rotate([args.x, args.y, args.z]); + } + } +}); + + +/** + * 'tilt zoom' command + */ +gcli.addCommand({ + name: 'tilt zoom', + description: gcli.lookup("tiltZoomDesc"), + manual: gcli.lookup("tiltZoomManual"), + params: [ + { + name: "zoom", + type: { name: 'number' }, + description: gcli.lookup("tiltZoomAmountDesc"), + manual: gcli.lookup("tiltZoomAmountManual") + } + ], + exec: function(args, context) { + let chromeWindow = context.environment.chromeDocument.defaultView; + let Tilt = chromeWindow.Tilt; + + if (Tilt.currentInstance) { + Tilt.currentInstance.controller.arcball.zoom(-args.zoom); + } + } +}); + + +/** + * 'tilt reset' command + */ +gcli.addCommand({ + name: 'tilt reset', + description: gcli.lookup("tiltResetDesc"), + manual: gcli.lookup("tiltResetManual"), + exec: function(args, context) { + let chromeWindow = context.environment.chromeDocument.defaultView; + let Tilt = chromeWindow.Tilt; + + if (Tilt.currentInstance) { + Tilt.currentInstance.controller.arcball.reset(); + } + } +}); + + +/** + * 'tilt close' command + */ +gcli.addCommand({ + name: 'tilt close', + description: gcli.lookup("tiltCloseDesc"), + manual: gcli.lookup("tiltCloseManual"), + exec: function(args, context) { + let chromeWindow = context.environment.chromeDocument.defaultView; + let Tilt = chromeWindow.Tilt; + + Tilt.destroy(Tilt.currentWindowId); + } +}); diff --git a/browser/devtools/webconsole/HUDService.jsm b/browser/devtools/webconsole/HUDService.jsm index 7053809bbc6..bd3604188cb 100644 --- a/browser/devtools/webconsole/HUDService.jsm +++ b/browser/devtools/webconsole/HUDService.jsm @@ -164,6 +164,7 @@ function loadCommands() { let commandExports = {}; Cu.import("resource:///modules/GcliCommands.jsm", commandExports); + Cu.import("resource:///modules/GcliTiltCommands.jsm", commandExports); return commandExports; } diff --git a/browser/devtools/webconsole/Makefile.in b/browser/devtools/webconsole/Makefile.in index d5165a56b36..71f99a34f64 100644 --- a/browser/devtools/webconsole/Makefile.in +++ b/browser/devtools/webconsole/Makefile.in @@ -50,6 +50,7 @@ EXTRA_JS_MODULES = \ AutocompletePopup.jsm \ gcli.jsm \ GcliCommands.jsm \ + GcliTiltCommands.jsm \ $(NULL) EXTRA_PP_JS_MODULES = \ diff --git a/browser/devtools/webconsole/test/browser_gcli_web.js b/browser/devtools/webconsole/test/browser_gcli_web.js index db12eecfcf9..6145cf97fef 100644 --- a/browser/devtools/webconsole/test/browser_gcli_web.js +++ b/browser/devtools/webconsole/test/browser_gcli_web.js @@ -1103,7 +1103,6 @@ exports.testIncompleteMultiMatch = function() { test.is(Status.ERROR, status); test.is(-1, assignC.paramIndex); test.ok(assignC.getPredictions().length > 0); - test.ok(assignC.getPredictions().length < 20); // could break ... verifyPredictionsContains('tsv', assignC.getPredictions()); verifyPredictionsContains('tsr', assignC.getPredictions()); test.is(null, requ.commandAssignment.getValue()); diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index a185b173f13..9a761155fb2 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -213,11 +213,19 @@ can reach it easily. --> - + + + + + + + + + @@ -599,3 +607,15 @@ just addresses the organization to follow, e.g. "This site is run by " --> + + + + + + + + + diff --git a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties index f4a1ce01820..07f794df92b 100644 --- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties +++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties @@ -56,6 +56,136 @@ inspectNodeDesc=CSS selector # on what it does. inspectNodeManual=A CSS selector for use with Document.querySelector which identifies a single element +# LOCALIZATION NOTE (tiltDesc) A very short description of the 'tilt' +# command. See tiltManual for a fuller description of what it does. This +# string is designed to be shown in a menu alongside the command name, which +# is why it should be as short as possible. +tiltDesc=Visualize the webpage in 3D + +# LOCALIZATION NOTE (tiltManual) A fuller description of the 'tilt' +# command, displayed when the user asks for help on what it does. +tiltManual=Investigate the relationship between various parts of a webpage and their ancestors in a 3D environment + +# LOCALIZATION NOTE (tiltOpenDesc) A very short description of the 'tilt inspect' +# command. See tiltOpenManual for a fuller description of what it does. This +# string is designed to be shown in a menu alongside the command name, which +# is why it should be as short as possible. +tiltOpenDesc=Open the Inspector 3D view + +# LOCALIZATION NOTE (tiltOpenManual) A fuller description of the 'tilt translate' +# command, displayed when the user asks for help on what it does. +tiltOpenManual=Initialize the 3D page inspector and optionally highlight a node using a CSS selector + +# LOCALIZATION NOTE (tiltTranslateDesc) A very short description of the 'tilt translate' +# command. See tiltTranslateManual for a fuller description of what it does. This +# string is designed to be shown in a menu alongside the command name, which +# is why it should be as short as possible. +tiltTranslateDesc=Move the webpage mesh + +# LOCALIZATION NOTE (tiltTranslateManual) A fuller description of the 'tilt translate' +# command, displayed when the user asks for help on what it does. +tiltTranslateManual=Incrementally translate the webpage mesh in a certain direction + +# LOCALIZATION NOTE (tiltTranslateXDesc) A very short string to describe the +# 'x' parameter to the 'tilt translate' command, which is displayed in a dialog +# when the user is using this command. +tiltTranslateXDesc=X (pixels) + +# LOCALIZATION NOTE (tiltTranslateXManual) A fuller description of the 'x' +# parameter to the 'translate' command, displayed when the user asks for help +# on what it does. +tiltTranslateXManual=The ammount in pixels to translate the webpage mesh on the X axis + +# LOCALIZATION NOTE (tiltTranslateYDesc) A very short string to describe the +# 'y' parameter to the 'tilt translate' command, which is displayed in a dialog +# when the user is using this command. +tiltTranslateYDesc=Y (pixels) + +# LOCALIZATION NOTE (tiltTranslateYManual) A fuller description of the 'y' +# parameter to the 'translate' command, displayed when the user asks for help +# on what it does. +tiltTranslateYManual=The ammount in pixels to translate the webpage mesh on the Y axis + +# LOCALIZATION NOTE (tiltRotateDesc) A very short description of the 'tilt rotate' +# command. See tiltRotateManual for a fuller description of what it does. This +# string is designed to be shown in a menu alongside the command name, which +# is why it should be as short as possible. +tiltRotateDesc=Spin the webpage mesh + +# LOCALIZATION NOTE (tiltRotateManual) A fuller description of the 'tilt rotate' +# command, displayed when the user asks for help on what it does. +tiltRotateManual=Incrementally rotate the webpage mesh in a certain direction + +# LOCALIZATION NOTE (tiltRotateXDesc) A very short string to describe the +# 'x' parameter to the 'tilt rotate' command, which is displayed in a dialog +# when the user is using this command. +tiltRotateXDesc=X (degrees) + +# LOCALIZATION NOTE (tiltRotateXManual) A fuller description of the 'x' +# parameter to the 'rotate' command, displayed when the user asks for help +# on what it does. +tiltRotateXManual=The ammount in degrees to rotate the webpage mesh along the X axis + +# LOCALIZATION NOTE (tiltRotateYDesc) A very short string to describe the +# 'y' parameter to the 'tilt rotate' command, which is displayed in a dialog +# when the user is using this command. +tiltRotateYDesc=Y (degrees) + +# LOCALIZATION NOTE (tiltRotateYManual) A fuller description of the 'y' +# parameter to the 'rotate' command, displayed when the user asks for help +# on what it does. +tiltRotateYManual=The ammount in degrees to rotate the webpage mesh along the Y axis + +# LOCALIZATION NOTE (tiltRotateZDesc) A very short string to describe the +# 'z' parameter to the 'tilt rotate' command, which is displayed in a dialog +# when the user is using this command. +tiltRotateZDesc=Z (degrees) + +# LOCALIZATION NOTE (tiltRotateZManual) A fuller description of the 'z' +# parameter to the 'rotate' command, displayed when the user asks for help +# on what it does. +tiltRotateZManual=The ammount in degrees to rotate the webpage mesh along the Z axis + +# LOCALIZATION NOTE (tiltZoomDesc) A very short description of the 'tilt zoom' +# command. See tiltZoomManual for a fuller description of what it does. This +# string is designed to be shown in a menu alongside the command name, which +# is why it should be as short as possible. +tiltZoomDesc=Move away or towards the webpage mesh + +# LOCALIZATION NOTE (tiltZoomManual) A fuller description of the 'tilt zoom' +# command, displayed when the user asks for help on what it does. +tiltZoomManual=Incrementally move the webpage mesh in a certain direction along the Z axis + +# LOCALIZATION NOTE (tiltZoomAmountDesc) A very short string to describe the +# 'zoom' parameter to the 'tilt zoom' command, which is displayed in a dialog +# when the user is using this command. +tiltZoomAmountDesc=Zoom (pixels) + +# LOCALIZATION NOTE (tiltZoomAmmuntManual) A fuller description of the 'zoom' +# parameter to the 'zoom' command, displayed when the user asks for help +# on what it does. +tiltZoomAmountManual=The amount in pixels to translate the webpage mesh along the Z axis + +# LOCALIZATION NOTE (tiltResetDesc) A very short description of the 'tilt reset' +# command. See tiltResetManual for a fuller description of what it does. This +# string is designed to be shown in a menu alongside the command name, which +# is why it should be as short as possible. +tiltResetDesc=Reset the translation, rotation and zoom + +# LOCALIZATION NOTE (tiltResetManual) A fuller description of the 'tilt reset' +# command, displayed when the user asks for help on what it does. +tiltResetManual=Resets any transformations applied to the webpage mesh modelview matrix + +# LOCALIZATION NOTE (tiltCloseDesc) A very short description of the 'tilt close' +# command. See tiltCloseManual for a fuller description of what it does. This +# string is designed to be shown in a menu alongside the command name, which +# is why it should be as short as possible. +tiltCloseDesc=Close the visualization if open + +# LOCALIZATION NOTE (tiltCloseManual) A fuller description of the 'tilt close' +# command, displayed when the user asks for help on what it does. +tiltCloseManual=Close the visualization and switch back to the Inspector default highlighter + # LOCALIZATION NOTE (breakDesc) A very short string used to describe the # function of the break command. breakDesc=Manage breakpoints diff --git a/browser/locales/en-US/chrome/browser/devtools/inspector.properties b/browser/locales/en-US/chrome/browser/devtools/inspector.properties index caa56cfb346..b815d2552d5 100644 --- a/browser/locales/en-US/chrome/browser/devtools/inspector.properties +++ b/browser/locales/en-US/chrome/browser/devtools/inspector.properties @@ -20,20 +20,8 @@ breadcrumbs.siblings=Siblings # LOCALIZATION NOTE (htmlPanel): Used in the Inspector tool's openInspectorUI # method when registering the HTML panel. -# LOCALIZATION NOTE (htmlPanel.label): This is a lable for a button that -# activates the Web Developer->Inspect UI's HTML Tree Panel. -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 - # LOCALIZATION NOTE (ruleView.*): Button label, accesskey and tooltip text # associated with the Highlighter's CSS Rule View in the Style Sidebar. ruleView.label=Rules ruleView.accesskey=R -ruleView.tooltiptext=View and Edit CSS \ No newline at end of file +ruleView.tooltiptext=View and Edit CSS diff --git a/browser/themes/gnomestripe/browser.css b/browser/themes/gnomestripe/browser.css index 4c23cadb2ed..5b91352180a 100644 --- a/browser/themes/gnomestripe/browser.css +++ b/browser/themes/gnomestripe/browser.css @@ -1992,10 +1992,6 @@ panel[dimmed="true"] { border-top: 1px solid hsla(210, 8%, 5%, .65); } -#inspector-toolbar[treepanel-open] { - padding-top: 0; -} - #devtools-side-splitter { -moz-appearance: none; border: 0; @@ -2011,15 +2007,6 @@ panel[dimmed="true"] { background-color: -moz-Field; } -/* Highlighter - toolbar resizer */ - -#inspector-top-resizer { - -moz-appearance: none; - cursor: n-resize; - background: none; - height: 4px; -} - /* Highlighter - Node Infobar */ /* Highlighter - Node Infobar - text */ @@ -2032,6 +2019,10 @@ html|*#highlighter-nodeinfobar-id { color: hsl(90, 79%, 52%); } +html|*#highlighter-nodeinfobar-pseudo-classes { + color: hsl(20, 100%, 70%); +} + /* Highlighter - Node Infobar - box & arrow */ #highlighter-nodeinfobar { @@ -2136,11 +2127,19 @@ html|*#highlighter-nodeinfobar-id { color: hsl(205,100%,70%); } +.inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-pseudo-classes { + color: hsl(20, 100%, 70%); +} + .inspector-breadcrumbs-id, .inspector-breadcrumbs-classes { color: #8d99a6; } +.inspector-breadcrumbs-pseudo-classes { + color: hsl(20, 100%, 85%); +} + /* Highlighter toolbar - breadcrumbs - LTR */ .inspector-breadcrumbs-button:-moz-locale-dir(ltr):first-of-type { @@ -2260,3 +2259,15 @@ html|*#highlighter-nodeinfobar-id { .inspector-breadcrumbs-button:last-of-type[checked]:hover:active:-moz-locale-dir(rtl) { -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected-pressed.png") 1 13 2 13 fill stretch; } + +/* Highlighter toolbar - HTML Tree */ + +#inspector-tree-splitter { + -moz-appearance: none; + border-top: 1px solid black; + border-bottom-width: 0; + min-height: 3px; + height: 3px; + margin-bottom: -3px; + position: relative; +} diff --git a/browser/themes/gnomestripe/devtools/debugger.css b/browser/themes/gnomestripe/devtools/debugger.css index cacc63dce54..96e009c0aad 100644 --- a/browser/themes/gnomestripe/devtools/debugger.css +++ b/browser/themes/gnomestripe/devtools/debugger.css @@ -247,6 +247,29 @@ a { -moz-appearance: treetwistyopen; } +/** + * Animations + */ + +.details[open] { + -moz-animation-duration: 0.25s; + -moz-animation-name: showblock; +} + +@-moz-keyframes showblock { + from { + opacity: 0; + -moz-transform-origin: top; + -moz-transform: scaleY(0); + } + + to { + opacity: 1; + -moz-transform-origin: top; + -moz-transform: scaleY(1); + } +} + /** * Display helpers */ diff --git a/browser/themes/gnomestripe/inspector.css b/browser/themes/gnomestripe/inspector.css index 0ba70f99290..399b70772c6 100644 --- a/browser/themes/gnomestripe/inspector.css +++ b/browser/themes/gnomestripe/inspector.css @@ -59,7 +59,7 @@ body { overflow: auto; font-family: Lucida Grande, sans-serif; font-size: 11px; - border-top: 1px solid #BBB9BA; + padding-top: 5px; } h1 { diff --git a/browser/themes/pinstripe/browser.css b/browser/themes/pinstripe/browser.css index a8c668dea8c..43b51b2786d 100644 --- a/browser/themes/pinstripe/browser.css +++ b/browser/themes/pinstripe/browser.css @@ -2739,12 +2739,6 @@ panel[dimmed="true"] { padding-right: 18px; /* use -moz-padding-end when/if bug 631729 gets fixed */ } -#inspector-toolbar[treepanel-open] { - padding-top: 0; - padding-right: 0; - -moz-padding-end: 4px; -} - #devtools-side-splitter { background-image: none !important; border: 0; @@ -2760,15 +2754,6 @@ panel[dimmed="true"] { background-color: -moz-Field; } -/* Highlighter - toolbar resizer */ - -#inspector-top-resizer { - -moz-appearance: none; - cursor: n-resize; - background: none; - height: 4px; -} - /* Highlighter - Node Infobar */ /* Highlighter - Node Infobar - text */ @@ -2781,6 +2766,10 @@ html|*#highlighter-nodeinfobar-id { color: hsl(90, 79%, 52%); } +html|*#highlighter-nodeinfobar-pseudo-classes { + color: hsl(20, 100%, 70%); +} + /* Highlighter - Node Infobar - box & arrow */ #highlighter-nodeinfobar { @@ -2879,11 +2868,19 @@ html|*#highlighter-nodeinfobar-id { color: hsl(205,100%,70%); } +.inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-pseudo-classes { + color: hsl(20, 100%, 70%); +} + .inspector-breadcrumbs-id, .inspector-breadcrumbs-classes { color: #8d99a6; } +.inspector-breadcrumbs-pseudo-classes { + color: hsl(20, 100%, 85%); +} + /* Highlighter toolbar - breadcrumbs - LTR */ .inspector-breadcrumbs-button:-moz-locale-dir(ltr):first-of-type { @@ -3003,3 +3000,15 @@ html|*#highlighter-nodeinfobar-id { .inspector-breadcrumbs-button:last-of-type[checked]:hover:active:-moz-locale-dir(rtl) { -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected-pressed.png") 1 13 2 13 fill stretch; } + +/* Highlighter toolbar - HTML Tree */ + +#inspector-tree-splitter { + -moz-appearance: none; + border-top: 1px solid black; + border-bottom-width: 0; + min-height: 3px; + height: 3px; + margin-bottom: -3px; + position: relative; +} diff --git a/browser/themes/pinstripe/devtools/debugger.css b/browser/themes/pinstripe/devtools/debugger.css index d64c1b307d9..422efbf8bb8 100644 --- a/browser/themes/pinstripe/devtools/debugger.css +++ b/browser/themes/pinstripe/devtools/debugger.css @@ -245,6 +245,29 @@ a { -moz-appearance: treetwistyopen; } +/** + * Animations + */ + +.details[open] { + -moz-animation-duration: 0.25s; + -moz-animation-name: showblock; +} + +@-moz-keyframes showblock { + from { + opacity: 0; + -moz-transform-origin: top; + -moz-transform: scaleY(0); + } + + to { + opacity: 1; + -moz-transform-origin: top; + -moz-transform: scaleY(1); + } +} + /** * Display helpers */ diff --git a/browser/themes/pinstripe/inspector.css b/browser/themes/pinstripe/inspector.css index 98eb0e45b58..61aa39cd867 100644 --- a/browser/themes/pinstripe/inspector.css +++ b/browser/themes/pinstripe/inspector.css @@ -59,7 +59,7 @@ body { overflow: auto; font-family: Lucida Grande, sans-serif; font-size: 11px; - border-top: 1px solid #BBB9BA; + padding-top: 5px; } h1 { diff --git a/browser/themes/winstripe/browser.css b/browser/themes/winstripe/browser.css index 17620b2984d..1e733b00ec1 100644 --- a/browser/themes/winstripe/browser.css +++ b/browser/themes/winstripe/browser.css @@ -2687,10 +2687,6 @@ panel[dimmed="true"] { border-top: 1px solid hsla(211,68%,6%,.65) !important; } -#inspector-toolbar[treepanel-open] { - padding-top: 0; -} - #devtools-side-splitter { border: 0; -moz-border-start: 1px solid #242b33; @@ -2705,15 +2701,6 @@ panel[dimmed="true"] { background-color: -moz-Field; } -/* Highlighter - toolbar resizer */ - -#inspector-top-resizer { - -moz-appearance: none; - cursor: n-resize; - background: none; - height: 4px; -} - /* Highlighter - Node Infobar */ /* Highlighter - Node Infobar - text */ @@ -2726,6 +2713,10 @@ html|*#highlighter-nodeinfobar-id { color: hsl(90, 79%, 52%); } +html|*#highlighter-nodeinfobar-pseudo-classes { + color: hsl(20, 100%, 70%); +} + /* Highlighter - Node Infobar - box & arrow */ #highlighter-nodeinfobar { @@ -2830,11 +2821,19 @@ html|*#highlighter-nodeinfobar-id { color: hsl(200,100%,70%); } +.inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-pseudo-classes { + color: hsl(20, 100%, 70%); +} + .inspector-breadcrumbs-id, .inspector-breadcrumbs-classes { color: #8d99a6; } +.inspector-breadcrumbs-pseudo-classes { + color: hsl(20, 100%, 85%); +} + /* Highlighter toolbar - breadcrumbs - LTR */ .inspector-breadcrumbs-button:-moz-locale-dir(ltr):first-of-type { @@ -2954,3 +2953,15 @@ html|*#highlighter-nodeinfobar-id { .inspector-breadcrumbs-button:last-of-type[checked]:hover:active:-moz-locale-dir(rtl) { -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected-pressed.png") 2 13 2 13 fill stretch; } + +/* Highlighter toolbar - HTML Tree */ + +#inspector-tree-splitter { + -moz-appearance: none; + border-top: 1px solid black; + border-bottom-width: 0; + min-height: 3px; + height: 3px; + margin-bottom: -3px; + position: relative; +} diff --git a/browser/themes/winstripe/devtools/debugger.css b/browser/themes/winstripe/devtools/debugger.css index 5c0819b0473..dabd55c492c 100644 --- a/browser/themes/winstripe/devtools/debugger.css +++ b/browser/themes/winstripe/devtools/debugger.css @@ -249,6 +249,29 @@ a { background-image: url("chrome://global/skin/tree/twisty-open.png"); } +/** + * Animations + */ + +.details[open] { + -moz-animation-duration: 0.25s; + -moz-animation-name: showblock; +} + +@-moz-keyframes showblock { + from { + opacity: 0; + -moz-transform-origin: top; + -moz-transform: scaleY(0); + } + + to { + opacity: 1; + -moz-transform-origin: top; + -moz-transform: scaleY(1); + } +} + /** * Display helpers */ diff --git a/browser/themes/winstripe/inspector.css b/browser/themes/winstripe/inspector.css index b4376787adf..44927488055 100644 --- a/browser/themes/winstripe/inspector.css +++ b/browser/themes/winstripe/inspector.css @@ -59,6 +59,7 @@ body { overflow: auto; font-family: Lucida Grande, sans-serif; font-size: 11px; + padding-top: 5px; } h1 { diff --git a/build/automation.py.in b/build/automation.py.in index 4b476a05ea4..ca9c8ce4c33 100644 --- a/build/automation.py.in +++ b/build/automation.py.in @@ -754,7 +754,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t self.log.info("Can't trigger Breakpad, just killing process") proc.kill() - def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath): + def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath, logger): """ Look for timeout or crashes and return the status after the process terminates """ stackFixerProcess = None stackFixerFunction = None @@ -788,6 +788,8 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t (line, didTimeout) = self.readWithTimeout(logsource, timeout) while line != "" and not didTimeout: + if logger: + logger.log(line) if "TEST-START" in line and "|" in line: self.lastTestSeen = line.split("|")[1].strip() if stackFixerFunction: @@ -877,7 +879,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t def runApp(self, testURL, env, app, profileDir, extraArgs, runSSLTunnel = False, utilityPath = None, - xrePath = None, certPath = None, + xrePath = None, certPath = None, logger = None, debuggerInfo = None, symbolsPath = None, timeout = -1, maxTime = None): """ @@ -936,7 +938,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t stderr = subprocess.STDOUT) self.log.info("INFO | automation.py | Application pid: %d", proc.pid) - status = self.waitForFinish(proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath) + status = self.waitForFinish(proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath, logger) self.log.info("INFO | automation.py | Application ran for: %s", str(datetime.now() - startTime)) # Do a final check for zombie child processes. diff --git a/build/automationutils.py b/build/automationutils.py index d55e0f28be8..4a937960858 100644 --- a/build/automationutils.py +++ b/build/automationutils.py @@ -40,6 +40,7 @@ from __future__ import with_statement import glob, logging, os, platform, shutil, subprocess, sys, tempfile, urllib2, zipfile import re from urlparse import urlparse +from operator import itemgetter __all__ = [ "ZipFileReader", @@ -52,6 +53,7 @@ __all__ = [ "DEBUGGER_INFO", "replaceBackSlashes", "wrapCommand", + "ShutdownLeakLogger" ] # Map of debugging programs to information about them, like default arguments @@ -450,3 +452,106 @@ def wrapCommand(cmd): return ["arch", "-arch", "i386"] + cmd # otherwise just execute the command normally return cmd + +class ShutdownLeakLogger(object): + """ + Parses the mochitest run log when running a debug build, assigns all leaked + DOM windows (that are still around after test suite shutdown, despite running + the GC) to the tests that created them and prints leak statistics. + """ + MAX_LEAK_COUNT = 120 + + def __init__(self, logger): + self.logger = logger + self.tests = [] + self.leakedWindows = {} + self.leakedDocShells = set() + self.currentTest = None + self.seenShutdown = False + + def log(self, line): + if line[2:11] == "DOMWINDOW": + self._logWindow(line) + elif line[2:10] == "DOCSHELL": + self._logDocShell(line) + elif line.startswith("TEST-START"): + fileName = line.split(" ")[-1].strip().replace("chrome://mochitests/content/browser/", "") + self.currentTest = {"fileName": fileName, "windows": set(), "docShells": set()} + elif line.startswith("INFO TEST-END"): + # don't track a test if no windows or docShells leaked + if self.currentTest["windows"] and self.currentTest["docShells"]: + self.tests.append(self.currentTest) + self.currentTest = None + elif line.startswith("INFO TEST-START | Shutdown"): + self.seenShutdown = True + + def parse(self): + leakingTests = self._parseLeakingTests() + + if leakingTests: + totalWindows = sum(len(test["leakedWindows"]) for test in leakingTests) + totalDocShells = sum(len(test["leakedDocShells"]) for test in leakingTests) + msgType = "INFO" if totalWindows + totalDocShells < self.MAX_LEAK_COUNT else "UNEXPECTED-FAIL" + self.logger.info("TEST-%s | ShutdownLeaks | leaked %d DOMWindow(s) and %d DocShell(s) until shutdown", msgType, totalWindows, totalDocShells) + + for test in leakingTests: + self.logger.info("\n[%s]", test["fileName"]) + + for url, count in self._zipLeakedWindows(test["leakedWindows"]): + self.logger.info(" %d window(s) [url = %s]", count, url) + + if test["leakedDocShells"]: + self.logger.info(" %d docShell(s)", len(test["leakedDocShells"])) + + def _logWindow(self, line): + created = line[:2] == "++" + id = self._parseValue(line, "serial") + + if self.currentTest: + windows = self.currentTest["windows"] + if created: + windows.add(id) + else: + windows.discard(id) + elif self.seenShutdown and not created: + self.leakedWindows[id] = self._parseValue(line, "url") + + def _logDocShell(self, line): + created = line[:2] == "++" + id = self._parseValue(line, "id") + + if self.currentTest: + docShells = self.currentTest["docShells"] + if created: + docShells.add(id) + else: + docShells.discard(id) + elif self.seenShutdown and not created: + self.leakedDocShells.add(id) + + def _parseValue(self, line, name): + return re.search("\[%s = (.+?)\]" % name, line).group(1) + + def _parseLeakingTests(self): + leakingTests = [] + + for test in self.tests: + test["leakedWindows"] = [self.leakedWindows[id] for id in test["windows"] if id in self.leakedWindows] + test["leakedDocShells"] = [id for id in test["docShells"] if id in self.leakedDocShells] + test["leakCount"] = len(test["leakedWindows"]) + len(test["leakedDocShells"]) + + if test["leakCount"]: + leakingTests.append(test) + + return sorted(leakingTests, key=itemgetter("leakCount"), reverse=True) + + def _zipLeakedWindows(self, leakedWindows): + counts = [] + counted = set() + + for url in leakedWindows: + if not url in counted: + counts.append((url, leakedWindows.count(url))) + counted.add(url) + + return sorted(counts, key=itemgetter(1), reverse=True) diff --git a/build/mobile/remoteautomation.py b/build/mobile/remoteautomation.py index 1fa26b5ee02..1cf419090c7 100644 --- a/build/mobile/remoteautomation.py +++ b/build/mobile/remoteautomation.py @@ -96,7 +96,7 @@ class RemoteAutomation(Automation): return env - def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsDir): + def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsDir, logger): # maxTime is used to override the default timeout, we should honor that status = proc.wait(timeout = maxTime) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index d4e40f78b05..a7d1b96667f 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -785,7 +785,8 @@ nsDocShell::nsDocShell(): // We're counting the number of |nsDocShells| to help find leaks ++gNumberOfDocShells; if (!PR_GetEnv("MOZ_QUIET")) { - printf("++DOCSHELL %p == %ld\n", (void*) this, gNumberOfDocShells); + printf("++DOCSHELL %p == %ld [id = %ld]\n", (void*) this, + gNumberOfDocShells, mHistoryID); } #endif } @@ -813,7 +814,8 @@ nsDocShell::~nsDocShell() // We're counting the number of |nsDocShells| to help find leaks --gNumberOfDocShells; if (!PR_GetEnv("MOZ_QUIET")) { - printf("--DOCSHELL %p == %ld\n", (void*) this, gNumberOfDocShells); + printf("--DOCSHELL %p == %ld [id = %ld]\n", (void*) this, + gNumberOfDocShells, mHistoryID); } #endif } diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py index 9c8930a0c05..2b324bf77f9 100644 --- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -675,6 +675,12 @@ class Mochitest(object): else: timeout = 330.0 # default JS harness timeout is 300 seconds + # it's a debug build, we can parse leaked DOMWindows and docShells + if Automation.IS_DEBUG_BUILD: + logger = ShutdownLeakLogger(self.automation.log) + else: + logger = None + if options.vmwareRecording: self.startVMwareRecording(options); @@ -688,6 +694,7 @@ class Mochitest(object): certPath=options.certPath, debuggerInfo=debuggerInfo, symbolsPath=options.symbolsPath, + logger = logger, timeout = timeout) except KeyboardInterrupt: self.automation.log.info("INFO | runtests.py | Received keyboard interrupt.\n"); @@ -702,6 +709,10 @@ class Mochitest(object): self.stopWebServer(options) self.stopWebSocketServer(options) processLeakLog(self.leak_report_file, options.leakThreshold) + + if logger: + logger.parse() + self.automation.log.info("\nINFO | runtests.py | Running tests: end.") if manifest is not None: diff --git a/toolkit/content/widgets/videocontrols.xml b/toolkit/content/widgets/videocontrols.xml index 105353011e2..18abc941aed 100644 --- a/toolkit/content/widgets/videocontrols.xml +++ b/toolkit/content/widgets/videocontrols.xml @@ -634,10 +634,15 @@ } for each (let event in this.videoEvents) this.video.removeEventListener(event, this, false); + + for each(let element in this.controlListeners) + element.item.removeEventListener(element.event, element.func, false); + + delete this.controlListeners; + this.video.removeEventListener("media-showStatistics", this._handleCustomEventsBound, false); delete this._handleCustomEventsBound; - this.video.ownerDocument.removeEventListener("mozfullscreenchange", this._setFullscreenButtonStateBound, false); - delete this._setFullscreenButtonStateBound; + this.log("--- videocontrols terminated ---"); }, @@ -931,6 +936,7 @@ togglePause : function () { if (this.video.paused || this.video.ended) { this._triggeredByControls = true; + this.hideClickToPlay(); this.video.play(); } else { this.video.pause(); @@ -975,7 +981,19 @@ this.fullscreenButton.removeAttribute("fullscreened"); }, - handleClickToPlay : function () { + clickToPlayClickHandler : function(e) { + if (e.button != 0 || this.hasError()) + return; + // Read defaultPrevented asynchronously, since Web content + // may want to consume the "click" event but will only + // receive it after us. + let self = this; + setTimeout(function clickToPlayCallback() { + if (!e.defaultPrevented) + self.togglePause(); + }, 0); + }, + hideClickToPlay : function () { let videoHeight = this.video.clientHeight; let videoWidth = this.video.clientWidth; @@ -990,7 +1008,6 @@ this.clickToPlay.removeAttribute("immediate"); } this.clickToPlay.setAttribute("fadeout", "true"); - this.togglePause(); }, setPlayButtonState : function(aPaused) { @@ -1330,47 +1347,36 @@ this.video.addEventListener(event, this, (event == "error") ? true : false); var self = this; - this.muteButton.addEventListener("command", function() { self.toggleMute(); }, false); - this.playButton.addEventListener("command", function() { self.togglePause(); }, false); - this.fullscreenButton.addEventListener("command", function() { self.toggleFullscreen(); }, false ); - this.clickToPlay.addEventListener("click", function clickToPlayClickHandler(e) { - if (e.button != 0 || self.hasError()) - return; - // Read defaultPrevented asynchronously, since Web content - // may want to consume the "click" event but will only - // receive it after us. - setTimeout(function clickToPlayCallback() { - if (!e.defaultPrevented) - self.handleClickToPlay(); - }, 0); - }, false); - this.controlsSpacer.addEventListener("click", function spacerClickHandler(e) { - if (e.button != 0 || self.hasError()) - return; - // Read defaultPrevented asynchronously, since Web content - // may want to consume the "click" event but will only - // receive it after us (bug 693014). - setTimeout(function togglePauseCallback() { - if (!e.defaultPrevented) - self.togglePause(); - }, 0); - }, false); + this.controlListeners = []; - if (!this.isAudioOnly) { - this.muteButton.addEventListener("mouseover", function(e) { self.onVolumeMouseInOut(e); }, false); - this.muteButton.addEventListener("mouseout", function(e) { self.onVolumeMouseInOut(e); }, false); - this.volumeStack.addEventListener("mouseover", function(e) { self.onVolumeMouseInOut(e); }, false); - this.volumeStack.addEventListener("mouseout", function(e) { self.onVolumeMouseInOut(e); }, false); + // Helper function to add an event listener to the given element + function addListener(elem, eventName, func) { + let boundFunc = func.bind(self); + self.controlListeners.push({ item: elem, event: eventName, func: boundFunc }); + elem.addEventListener(eventName, boundFunc, false); } - this.videocontrols.addEventListener("transitionend", function(e) { self.onTransitionEnd(e); }, false); - this._setFullscreenButtonStateBound = this.setFullscreenButtonState.bind(this); - this.video.ownerDocument.addEventListener("mozfullscreenchange", this._setFullscreenButtonStateBound, false); + addListener(this.muteButton, "command", this.toggleMute); + addListener(this.playButton, "command", this.togglePause); + addListener(this.fullscreenButton, "command", this.toggleFullscreen); + addListener(this.clickToPlay, "click", this.clickToPlayClickHandler); + addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler); + + if (!this.isAudioOnly) { + addListener(this.muteButton, "mouseover", this.onVolumeMouseInOut); + addListener(this.muteButton, "mouseout", this.onVolumeMouseInOut); + addListener(this.volumeStack, "mouseover", this.onVolumeMouseInOut); + addListener(this.volumeStack, "mouseout", this.onVolumeMouseInOut); + } + + addListener(this.videocontrols, "transitionend", this.onTransitionEnd); + addListener(this.video.ownerDocument, "mozfullscreenchange", this.setFullscreenButtonState); // Make the