2012-11-30 00:07:59 -08:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
|
|
|
|
|
|
|
this.EXPORTED_SYMBOLS = ["InspectorPanel"];
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2013-02-01 11:43:15 -08:00
|
|
|
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
2012-11-30 00:07:59 -08:00
|
|
|
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
|
2013-02-07 02:41:00 -08:00
|
|
|
Cu.import("resource:///modules/devtools/CssLogic.jsm");
|
2012-11-30 00:07:59 -08:00
|
|
|
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "MarkupView",
|
2012-12-05 03:14:09 -08:00
|
|
|
"resource:///modules/devtools/MarkupView.jsm");
|
2012-11-30 00:07:59 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Selection",
|
|
|
|
"resource:///modules/devtools/Selection.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "HTMLBreadcrumbs",
|
|
|
|
"resource:///modules/devtools/Breadcrumbs.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Highlighter",
|
|
|
|
"resource:///modules/devtools/Highlighter.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "ToolSidebar",
|
|
|
|
"resource:///modules/devtools/Sidebar.jsm");
|
2013-01-28 13:32:35 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "SelectorSearch",
|
|
|
|
"resource:///modules/devtools/SelectorSearch.jsm");
|
2012-11-30 00:07:59 -08:00
|
|
|
|
|
|
|
const LAYOUT_CHANGE_TIMER = 250;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents an open instance of the Inspector for a tab.
|
|
|
|
* The inspector controls the highlighter, the breadcrumbs,
|
|
|
|
* the markup view, and the sidebar (computed view, rule view
|
|
|
|
* and layout view).
|
|
|
|
*/
|
|
|
|
this.InspectorPanel = function InspectorPanel(iframeWindow, toolbox) {
|
|
|
|
this._toolbox = toolbox;
|
|
|
|
this._target = toolbox._target;
|
2012-12-13 05:03:55 -08:00
|
|
|
this.panelDoc = iframeWindow.document;
|
|
|
|
this.panelWin = iframeWindow;
|
|
|
|
this.panelWin.inspector = this;
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 23:05:00 -08:00
|
|
|
EventEmitter.decorate(this);
|
2012-12-13 05:03:55 -08:00
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
InspectorPanel.prototype = {
|
|
|
|
/**
|
|
|
|
* open is effectively an asynchronous constructor
|
|
|
|
*/
|
|
|
|
open: function InspectorPanel_open() {
|
|
|
|
let deferred = Promise.defer();
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
this.onNavigatedAway = this.onNavigatedAway.bind(this);
|
|
|
|
this.target.on("navigate", this.onNavigatedAway);
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
|
|
|
|
this.lastNodemenuItem = this.nodemenu.lastChild;
|
|
|
|
this._setupNodeMenu = this._setupNodeMenu.bind(this);
|
|
|
|
this._resetNodeMenu = this._resetNodeMenu.bind(this);
|
|
|
|
this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
|
|
|
|
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
// Create an empty selection
|
|
|
|
this._selection = new Selection();
|
|
|
|
this.onNewSelection = this.onNewSelection.bind(this);
|
|
|
|
this.selection.on("new-node", this.onNewSelection);
|
2013-01-18 14:03:22 -08:00
|
|
|
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
|
|
|
|
this.selection.on("before-new-node", this.onBeforeNewSelection);
|
2012-12-17 12:58:40 -08:00
|
|
|
this.onDetached = this.onDetached.bind(this);
|
|
|
|
this.selection.on("detached", this.onDetached);
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
this.breadcrumbs = new HTMLBreadcrumbs(this);
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2013-01-09 01:32:35 -08:00
|
|
|
if (this.target.isLocalTab) {
|
2012-12-13 05:03:55 -08:00
|
|
|
this.browser = this.target.tab.linkedBrowser;
|
|
|
|
this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
|
|
|
|
this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
|
|
|
|
|
|
|
|
this.highlighter = new Highlighter(this.target, this, this._toolbox);
|
|
|
|
let button = this.panelDoc.getElementById("inspector-inspect-toolbutton");
|
|
|
|
button.hidden = false;
|
2013-01-21 07:22:47 -08:00
|
|
|
this.onLockStateChanged = function() {
|
2012-12-13 05:03:55 -08:00
|
|
|
if (this.highlighter.locked) {
|
|
|
|
button.removeAttribute("checked");
|
2013-01-21 07:22:47 -08:00
|
|
|
this._toolbox.raise();
|
2012-12-13 05:03:55 -08:00
|
|
|
} else {
|
|
|
|
button.setAttribute("checked", "true");
|
|
|
|
}
|
|
|
|
}.bind(this);
|
2013-01-21 07:22:47 -08:00
|
|
|
this.highlighter.on("locked", this.onLockStateChanged);
|
|
|
|
this.highlighter.on("unlocked", this.onLockStateChanged);
|
2013-01-09 01:32:35 -08:00
|
|
|
|
|
|
|
// Show a warning when the debugger is paused.
|
|
|
|
// We show the warning only when the inspector
|
|
|
|
// is selected.
|
|
|
|
this.updateDebuggerPausedWarning = function() {
|
|
|
|
let notificationBox = this._toolbox.getNotificationBox();
|
|
|
|
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
|
|
|
|
if (!notification && this._toolbox.currentToolId == "inspector" &&
|
|
|
|
this.target.isThreadPaused) {
|
|
|
|
let message = this.strings.GetStringFromName("debuggerPausedWarning.message");
|
|
|
|
notificationBox.appendNotification(message,
|
|
|
|
"inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (notification && this._toolbox.currentToolId != "inspector") {
|
|
|
|
notificationBox.removeNotification(notification);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (notification && !this.target.isThreadPaused) {
|
|
|
|
notificationBox.removeNotification(notification);
|
|
|
|
}
|
|
|
|
|
|
|
|
}.bind(this);
|
|
|
|
this.target.on("thread-paused", this.updateDebuggerPausedWarning);
|
|
|
|
this.target.on("thread-resumed", this.updateDebuggerPausedWarning);
|
|
|
|
this._toolbox.on("select", this.updateDebuggerPausedWarning);
|
|
|
|
this.updateDebuggerPausedWarning();
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
this._initMarkup();
|
|
|
|
this.isReady = false;
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
this.once("markuploaded", function() {
|
|
|
|
this.isReady = true;
|
2012-11-30 00:07:59 -08:00
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
// All the components are initialized. Let's select a node.
|
2013-01-09 01:32:35 -08:00
|
|
|
if (this.target.isLocalTab) {
|
2012-12-13 05:03:55 -08:00
|
|
|
let root = this.browser.contentDocument.documentElement;
|
|
|
|
this._selection.setNode(root);
|
2013-01-09 01:32:35 -08:00
|
|
|
} else if (this.target.window) {
|
2012-12-13 05:03:55 -08:00
|
|
|
let root = this.target.window.document.documentElement;
|
|
|
|
this._selection.setNode(root);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.highlighter) {
|
|
|
|
this.highlighter.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.emit("ready");
|
|
|
|
deferred.resolve(this);
|
|
|
|
}.bind(this));
|
|
|
|
|
2013-01-28 13:32:35 -08:00
|
|
|
this.setupSearchBox();
|
2012-12-13 05:03:55 -08:00
|
|
|
this.setupSidebar();
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
},
|
2012-11-30 00:07:59 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Selection object (read only)
|
|
|
|
*/
|
|
|
|
get selection() {
|
|
|
|
return this._selection;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Target getter.
|
|
|
|
*/
|
|
|
|
get target() {
|
|
|
|
return this._target;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Target setter.
|
|
|
|
*/
|
|
|
|
set target(value) {
|
|
|
|
this._target = value;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expose gViewSourceUtils so that other tools can make use of them.
|
|
|
|
*/
|
|
|
|
get viewSourceUtils() {
|
|
|
|
return this.panelWin.gViewSourceUtils;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicate that a tool has modified the state of the page. Used to
|
|
|
|
* decide whether to show the "are you sure you want to navigate"
|
|
|
|
* notification.
|
|
|
|
*/
|
|
|
|
markDirty: function InspectorPanel_markDirty() {
|
|
|
|
this.isDirty = true;
|
|
|
|
},
|
|
|
|
|
2013-01-28 13:32:35 -08:00
|
|
|
/**
|
|
|
|
* Hooks the searchbar to show result and auto completion suggestions.
|
|
|
|
*/
|
|
|
|
setupSearchBox: function InspectorPanel_setupSearchBox() {
|
|
|
|
// Initiate the selectors search object.
|
|
|
|
let setNodeFunction = function(node) {
|
|
|
|
this.selection.setNode(node, "selectorsearch");
|
|
|
|
}.bind(this);
|
|
|
|
if (this.searchSuggestions) {
|
|
|
|
this.searchSuggestions.destroy();
|
|
|
|
this.searchSuggestions = null;
|
|
|
|
}
|
|
|
|
this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
|
|
|
|
this.searchSuggestions = new SelectorSearch(this.browser.contentDocument,
|
|
|
|
this.searchBox,
|
|
|
|
setNodeFunction);
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Build the sidebar.
|
|
|
|
*/
|
|
|
|
setupSidebar: function InspectorPanel_setupSidebar() {
|
|
|
|
let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
|
|
|
|
this.sidebar = new ToolSidebar(tabbox, this);
|
|
|
|
|
|
|
|
let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
|
|
|
|
|
|
|
|
this._setDefaultSidebar = function(event, toolId) {
|
|
|
|
Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.sidebar.on("select", this._setDefaultSidebar);
|
2012-12-17 11:29:08 -08:00
|
|
|
this.toggleHighlighter = this.toggleHighlighter.bind(this);
|
2012-11-30 00:07:59 -08:00
|
|
|
|
|
|
|
this.sidebar.addTab("ruleview",
|
2013-03-27 15:20:38 -07:00
|
|
|
"chrome://browser/content/devtools/cssruleview.xhtml",
|
2012-11-30 00:07:59 -08:00
|
|
|
"ruleview" == defaultTab);
|
|
|
|
|
|
|
|
this.sidebar.addTab("computedview",
|
2013-03-27 15:20:38 -07:00
|
|
|
"chrome://browser/content/devtools/computedview.xhtml",
|
2012-11-30 00:07:59 -08:00
|
|
|
"computedview" == defaultTab);
|
|
|
|
|
2013-03-06 20:57:00 -08:00
|
|
|
if (Services.prefs.getBoolPref("devtools.fontinspector.enabled")) {
|
|
|
|
this.sidebar.addTab("fontinspector",
|
|
|
|
"chrome://browser/content/devtools/fontinspector/font-inspector.xhtml",
|
|
|
|
"fontinspector" == defaultTab);
|
|
|
|
}
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
this.sidebar.addTab("layoutview",
|
|
|
|
"chrome://browser/content/devtools/layoutview/view.xhtml",
|
|
|
|
"layoutview" == defaultTab);
|
|
|
|
|
2012-12-17 11:29:08 -08:00
|
|
|
let ruleViewTab = this.sidebar.getTab("ruleview");
|
|
|
|
ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false);
|
|
|
|
ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false);
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
this.sidebar.show();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset the inspector on navigate away.
|
|
|
|
*/
|
2013-04-04 01:09:46 -07:00
|
|
|
onNavigatedAway: function InspectorPanel_onNavigatedAway(event, newWindow) {
|
2012-11-30 00:07:59 -08:00
|
|
|
this.selection.setNode(null);
|
|
|
|
this._destroyMarkup();
|
|
|
|
this.isDirty = false;
|
|
|
|
let self = this;
|
2012-12-20 06:38:51 -08:00
|
|
|
|
|
|
|
function onDOMReady() {
|
|
|
|
newWindow.removeEventListener("DOMContentLoaded", onDOMReady, true);
|
|
|
|
|
2013-02-19 02:48:05 -08:00
|
|
|
if (self._destroyed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
if (!self.selection.node) {
|
2013-03-09 12:17:34 -08:00
|
|
|
self.selection.setNode(newWindow.document.documentElement, "navigateaway");
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
self._initMarkup();
|
2013-01-28 13:32:35 -08:00
|
|
|
self.setupSearchBox();
|
2012-12-20 06:38:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (newWindow.document.readyState == "loading") {
|
|
|
|
newWindow.addEventListener("DOMContentLoaded", onDOMReady, true);
|
|
|
|
} else {
|
|
|
|
onDOMReady();
|
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When a new node is selected.
|
|
|
|
*/
|
|
|
|
onNewSelection: function InspectorPanel_onNewSelection() {
|
|
|
|
this.cancelLayoutChange();
|
|
|
|
},
|
|
|
|
|
2013-01-18 14:03:22 -08:00
|
|
|
/**
|
|
|
|
* When a new node is selected, before the selection has changed.
|
|
|
|
*/
|
|
|
|
onBeforeNewSelection: function InspectorPanel_onBeforeNewSelection(event,
|
|
|
|
node) {
|
|
|
|
if (this.breadcrumbs.indexOf(node) == -1) {
|
|
|
|
// only clear locks if we'd have to update breadcrumbs
|
|
|
|
this.clearPseudoClasses();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-12-17 12:58:40 -08:00
|
|
|
/**
|
|
|
|
* When a node is deleted, select its parent node.
|
|
|
|
*/
|
|
|
|
onDetached: function InspectorPanel_onDetached(event, parentNode) {
|
|
|
|
this.cancelLayoutChange();
|
|
|
|
this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
|
|
|
|
this.selection.setNode(parentNode, "detached");
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Destroy the inspector.
|
|
|
|
*/
|
|
|
|
destroy: function InspectorPanel__destroy() {
|
|
|
|
if (this._destroyed) {
|
2012-12-13 05:03:55 -08:00
|
|
|
return Promise.resolve(null);
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
this._destroyed = true;
|
|
|
|
|
2012-12-13 05:03:55 -08:00
|
|
|
this.cancelLayoutChange();
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
if (this.browser) {
|
|
|
|
this.browser.removeEventListener("resize", this.scheduleLayoutChange, true);
|
|
|
|
this.browser = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.target.off("navigate", this.onNavigatedAway);
|
|
|
|
|
|
|
|
if (this.highlighter) {
|
2013-01-21 07:22:47 -08:00
|
|
|
this.highlighter.off("locked", this.onLockStateChanged);
|
|
|
|
this.highlighter.off("unlocked", this.onLockStateChanged);
|
2012-11-30 00:07:59 -08:00
|
|
|
this.highlighter.destroy();
|
|
|
|
}
|
|
|
|
|
2013-01-09 01:32:35 -08:00
|
|
|
this.target.off("thread-paused", this.updateDebuggerPausedWarning);
|
|
|
|
this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
|
|
|
|
this._toolbox.off("select", this.updateDebuggerPausedWarning);
|
|
|
|
|
|
|
|
this._toolbox = null;
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
this.sidebar.off("select", this._setDefaultSidebar);
|
|
|
|
this.sidebar.destroy();
|
|
|
|
this.sidebar = null;
|
|
|
|
|
|
|
|
this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
|
|
|
|
this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
|
|
|
|
this.breadcrumbs.destroy();
|
2013-01-28 13:32:35 -08:00
|
|
|
this.searchSuggestions.destroy();
|
2012-11-30 00:07:59 -08:00
|
|
|
this.selection.off("new-node", this.onNewSelection);
|
2013-01-18 14:03:22 -08:00
|
|
|
this.selection.off("before-new-node", this.onBeforeNewSelection);
|
2012-12-17 12:58:40 -08:00
|
|
|
this.selection.off("detached", this.onDetached);
|
2012-11-30 00:07:59 -08:00
|
|
|
this._destroyMarkup();
|
|
|
|
this._selection.destroy();
|
|
|
|
this._selection = null;
|
|
|
|
this.panelWin.inspector = null;
|
|
|
|
this.target = null;
|
|
|
|
this.panelDoc = null;
|
|
|
|
this.panelWin = null;
|
|
|
|
this.breadcrumbs = null;
|
2013-01-28 13:32:35 -08:00
|
|
|
this.searchSuggestions = null;
|
2012-11-30 00:07:59 -08:00
|
|
|
this.lastNodemenuItem = null;
|
|
|
|
this.nodemenu = null;
|
|
|
|
this.highlighter = null;
|
2012-12-13 05:03:55 -08:00
|
|
|
|
|
|
|
return Promise.resolve(null);
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show the node menu.
|
|
|
|
*/
|
|
|
|
showNodeMenu: function InspectorPanel_showNodeMenu(aButton, aPosition, aExtraItems) {
|
|
|
|
if (aExtraItems) {
|
|
|
|
for (let item of aExtraItems) {
|
|
|
|
this.nodemenu.appendChild(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.nodemenu.openPopup(aButton, aPosition, 0, 0, true, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
hideNodeMenu: function InspectorPanel_hideNodeMenu() {
|
|
|
|
this.nodemenu.hidePopup();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disable the delete item if needed. Update the pseudo classes.
|
|
|
|
*/
|
|
|
|
_setupNodeMenu: function InspectorPanel_setupNodeMenu() {
|
|
|
|
// Set the pseudo classes
|
|
|
|
for (let name of ["hover", "active", "focus"]) {
|
|
|
|
let menu = this.panelDoc.getElementById("node-menu-pseudo-" + name);
|
2013-02-26 08:22:36 -08:00
|
|
|
|
|
|
|
if (this.selection.isElementNode()) {
|
|
|
|
let checked = DOMUtils.hasPseudoClassLock(this.selection.node, ":" + name);
|
|
|
|
menu.setAttribute("checked", checked);
|
|
|
|
menu.removeAttribute("disabled");
|
|
|
|
} else {
|
|
|
|
menu.setAttribute("disabled", "true");
|
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Disable delete item if needed
|
|
|
|
let deleteNode = this.panelDoc.getElementById("node-menu-delete");
|
2013-02-26 08:22:36 -08:00
|
|
|
if (this.selection.isRoot() || this.selection.isDocumentTypeNode()) {
|
2012-11-30 00:07:59 -08:00
|
|
|
deleteNode.setAttribute("disabled", "true");
|
|
|
|
} else {
|
|
|
|
deleteNode.removeAttribute("disabled");
|
|
|
|
}
|
2013-02-26 08:22:36 -08:00
|
|
|
|
|
|
|
// Disable / enable "Copy Unique Selector", "Copy inner HTML" &
|
|
|
|
// "Copy outer HTML" as appropriate
|
|
|
|
let unique = this.panelDoc.getElementById("node-menu-copyuniqueselector");
|
|
|
|
let copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner");
|
|
|
|
let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
|
|
|
|
if (this.selection.isElementNode()) {
|
|
|
|
unique.removeAttribute("disabled");
|
|
|
|
copyInnerHTML.removeAttribute("disabled");
|
|
|
|
copyOuterHTML.removeAttribute("disabled");
|
|
|
|
} else {
|
|
|
|
unique.setAttribute("disabled", "true");
|
|
|
|
copyInnerHTML.setAttribute("disabled", "true");
|
|
|
|
copyOuterHTML.setAttribute("disabled", "true");
|
|
|
|
}
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
_resetNodeMenu: function InspectorPanel_resetNodeMenu() {
|
|
|
|
// Remove any extra items
|
|
|
|
while (this.lastNodemenuItem.nextSibling) {
|
|
|
|
let toDelete = this.lastNodemenuItem.nextSibling;
|
|
|
|
toDelete.parentNode.removeChild(toDelete);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_initMarkup: function InspectorPanel_initMarkup() {
|
|
|
|
let doc = this.panelDoc;
|
|
|
|
|
|
|
|
this._markupBox = doc.getElementById("markup-box");
|
|
|
|
|
|
|
|
// create tool iframe
|
|
|
|
this._markupFrame = doc.createElement("iframe");
|
|
|
|
this._markupFrame.setAttribute("flex", "1");
|
|
|
|
this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
|
|
|
|
this._markupFrame.setAttribute("context", "inspector-node-popup");
|
|
|
|
|
|
|
|
// This is needed to enable tooltips inside the iframe document.
|
|
|
|
this._boundMarkupFrameLoad = function InspectorPanel_initMarkupPanel_onload() {
|
|
|
|
this._markupFrame.contentWindow.focus();
|
|
|
|
this._onMarkupFrameLoad();
|
|
|
|
}.bind(this);
|
|
|
|
this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true);
|
|
|
|
|
|
|
|
this._markupBox.setAttribute("hidden", true);
|
|
|
|
this._markupBox.appendChild(this._markupFrame);
|
|
|
|
this._markupFrame.setAttribute("src", "chrome://browser/content/devtools/markup-view.xhtml");
|
|
|
|
},
|
|
|
|
|
|
|
|
_onMarkupFrameLoad: function InspectorPanel__onMarkupFrameLoad() {
|
|
|
|
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
|
|
|
|
delete this._boundMarkupFrameLoad;
|
|
|
|
|
|
|
|
this._markupBox.removeAttribute("hidden");
|
|
|
|
|
2013-01-09 01:32:36 -08:00
|
|
|
let controllerWindow = this._toolbox.doc.defaultView;
|
2012-11-30 00:07:59 -08:00
|
|
|
this.markup = new MarkupView(this, this._markupFrame, controllerWindow);
|
|
|
|
|
|
|
|
this.emit("markuploaded");
|
|
|
|
},
|
|
|
|
|
|
|
|
_destroyMarkup: function InspectorPanel__destroyMarkup() {
|
|
|
|
if (this._boundMarkupFrameLoad) {
|
|
|
|
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
|
|
|
|
delete this._boundMarkupFrameLoad;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.markup) {
|
|
|
|
this.markup.destroy();
|
|
|
|
delete this.markup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._markupFrame) {
|
|
|
|
this._markupFrame.parentNode.removeChild(this._markupFrame);
|
|
|
|
delete this._markupFrame;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggle a pseudo class.
|
|
|
|
*/
|
|
|
|
togglePseudoClass: function InspectorPanel_togglePseudoClass(aPseudo) {
|
|
|
|
if (this.selection.isElementNode()) {
|
|
|
|
if (DOMUtils.hasPseudoClassLock(this.selection.node, aPseudo)) {
|
|
|
|
this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
|
|
|
|
DOMUtils.removePseudoClassLock(crumb.node, aPseudo);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
|
|
|
|
let node = this.selection.node;
|
|
|
|
do {
|
|
|
|
DOMUtils.addPseudoClassLock(node, aPseudo);
|
|
|
|
node = node.parentNode;
|
|
|
|
} while (hierarchical && node.parentNode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.selection.emit("pseudoclass");
|
2013-02-20 15:33:36 -08:00
|
|
|
this.breadcrumbs.scroll();
|
2012-11-30 00:07:59 -08:00
|
|
|
},
|
|
|
|
|
2013-01-18 14:03:22 -08:00
|
|
|
/**
|
|
|
|
* Clear any pseudo-class locks applied to the current hierarchy.
|
|
|
|
*/
|
|
|
|
clearPseudoClasses: function InspectorPanel_clearPseudoClasses() {
|
|
|
|
this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
|
2013-04-04 01:09:46 -07:00
|
|
|
DOMUtils.clearPseudoClassLocks(crumb.node);
|
2013-01-18 14:03:22 -08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2012-12-17 11:29:08 -08:00
|
|
|
/**
|
|
|
|
* Toggle the highlighter when ruleview is hovered.
|
|
|
|
*/
|
|
|
|
toggleHighlighter: function InspectorPanel_toggleHighlighter(event)
|
|
|
|
{
|
|
|
|
if (event.type == "mouseover") {
|
|
|
|
this.highlighter.hide();
|
|
|
|
}
|
|
|
|
else if (event.type == "mouseout") {
|
|
|
|
this.highlighter.show();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Copy the innerHTML of the selected Node to the clipboard.
|
|
|
|
*/
|
|
|
|
copyInnerHTML: function InspectorPanel_copyInnerHTML()
|
|
|
|
{
|
|
|
|
if (!this.selection.isNode()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let toCopy = this.selection.node.innerHTML;
|
|
|
|
if (toCopy) {
|
|
|
|
clipboardHelper.copyString(toCopy);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy the outerHTML of the selected Node to the clipboard.
|
|
|
|
*/
|
|
|
|
copyOuterHTML: function InspectorPanel_copyOuterHTML()
|
|
|
|
{
|
|
|
|
if (!this.selection.isNode()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let toCopy = this.selection.node.outerHTML;
|
|
|
|
if (toCopy) {
|
|
|
|
clipboardHelper.copyString(toCopy);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-02-07 02:41:00 -08:00
|
|
|
/**
|
|
|
|
* Copy a unique selector of the selected Node to the clipboard.
|
|
|
|
*/
|
|
|
|
copyUniqueSelector: function InspectorPanel_copyUniqueSelector()
|
|
|
|
{
|
|
|
|
if (!this.selection.isNode()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let toCopy = CssLogic.findCssSelector(this.selection.node);
|
|
|
|
if (toCopy) {
|
|
|
|
clipboardHelper.copyString(toCopy);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-30 00:07:59 -08:00
|
|
|
/**
|
|
|
|
* Delete the selected node.
|
|
|
|
*/
|
|
|
|
deleteNode: function IUI_deleteNode() {
|
|
|
|
if (!this.selection.isNode() ||
|
|
|
|
this.selection.isRoot()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let toDelete = this.selection.node;
|
|
|
|
|
|
|
|
let parent = this.selection.node.parentNode;
|
|
|
|
|
|
|
|
// If the markup panel is active, use the markup panel to delete
|
|
|
|
// the node, making this an undoable action.
|
|
|
|
if (this.markup) {
|
|
|
|
this.markup.deleteNode(toDelete);
|
|
|
|
} else {
|
|
|
|
// remove the node from content
|
|
|
|
parent.removeChild(toDelete);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedule a low-priority change event for things like paint
|
|
|
|
* and resize.
|
|
|
|
*/
|
|
|
|
scheduleLayoutChange: function Inspector_scheduleLayoutChange()
|
|
|
|
{
|
|
|
|
if (this._timer) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
this._timer = this.panelWin.setTimeout(function() {
|
|
|
|
this.emit("layout-change");
|
|
|
|
this._timer = null;
|
|
|
|
}.bind(this), LAYOUT_CHANGE_TIMER);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancel a pending low-priority change event if any is
|
|
|
|
* scheduled.
|
|
|
|
*/
|
|
|
|
cancelLayoutChange: function Inspector_cancelLayoutChange()
|
|
|
|
{
|
|
|
|
if (this._timer) {
|
|
|
|
this.panelWin.clearTimeout(this._timer);
|
|
|
|
delete this._timer;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Initializers
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(InspectorPanel.prototype, "strings",
|
|
|
|
function () {
|
|
|
|
return Services.strings.createBundle(
|
|
|
|
"chrome://browser/locale/devtools/inspector.properties");
|
|
|
|
});
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
|
|
|
|
return Cc["@mozilla.org/widget/clipboardhelper;1"].
|
|
|
|
getService(Ci.nsIClipboardHelper);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
|
|
|
|
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
|
|
|
});
|