browser/devtools/inspector/inspector-panel.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 const {Cc, Ci, Cu, Cr} = require("chrome");
     9 Cu.import("resource://gre/modules/Services.jsm");
    11 let promise = require("devtools/toolkit/deprecated-sync-thenables");
    12 let EventEmitter = require("devtools/toolkit/event-emitter");
    13 let {CssLogic} = require("devtools/styleinspector/css-logic");
    15 loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView);
    16 loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs);
    17 loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar);
    18 loader.lazyGetter(this, "SelectorSearch", () => require("devtools/inspector/selector-search").SelectorSearch);
    20 const LAYOUT_CHANGE_TIMER = 250;
    22 /**
    23  * Represents an open instance of the Inspector for a tab.
    24  * The inspector controls the breadcrumbs, the markup view, and the sidebar
    25  * (computed view, rule view, font view and layout view).
    26  *
    27  * Events:
    28  * - ready
    29  *      Fired when the inspector panel is opened for the first time and ready to
    30  *      use
    31  * - new-root
    32  *      Fired after a new root (navigation to a new page) event was fired by
    33  *      the walker, and taken into account by the inspector (after the markup
    34  *      view has been reloaded)
    35  * - markuploaded
    36  *      Fired when the markup-view frame has loaded
    37  * - layout-change
    38  *      Fired when the layout of the inspector changes
    39  * - breadcrumbs-updated
    40  *      Fired when the breadcrumb widget updates to a new node
    41  * - layoutview-updated
    42  *      Fired when the layoutview (box model) updates to a new node
    43  * - markupmutation
    44  *      Fired after markup mutations have been processed by the markup-view
    45  * - computed-view-refreshed
    46  *      Fired when the computed rules view updates to a new node
    47  * - computed-view-property-expanded
    48  *      Fired when a property is expanded in the computed rules view
    49  * - computed-view-property-collapsed
    50  *      Fired when a property is collapsed in the computed rules view
    51  * - rule-view-refreshed
    52  *      Fired when the rule view updates to a new node
    53  */
    54 function InspectorPanel(iframeWindow, toolbox) {
    55   this._toolbox = toolbox;
    56   this._target = toolbox._target;
    57   this.panelDoc = iframeWindow.document;
    58   this.panelWin = iframeWindow;
    59   this.panelWin.inspector = this;
    60   this._inspector = null;
    62   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
    63   this._target.on("will-navigate", this._onBeforeNavigate);
    65   EventEmitter.decorate(this);
    66 }
    68 exports.InspectorPanel = InspectorPanel;
    70 InspectorPanel.prototype = {
    71   /**
    72    * open is effectively an asynchronous constructor
    73    */
    74   open: function InspectorPanel_open() {
    75     return this.target.makeRemote().then(() => {
    76       return this._getPageStyle();
    77     }).then(() => {
    78       return this._getDefaultNodeForSelection();
    79     }).then(defaultSelection => {
    80       return this._deferredOpen(defaultSelection);
    81     }).then(null, console.error);
    82   },
    84   get toolbox() {
    85     return this._toolbox;
    86   },
    88   get inspector() {
    89     return this._toolbox.inspector;
    90   },
    92   get walker() {
    93     return this._toolbox.walker;
    94   },
    96   get selection() {
    97     return this._toolbox.selection;
    98   },
   100   get isOuterHTMLEditable() {
   101     return this._target.client.traits.editOuterHTML;
   102   },
   104   get hasUrlToImageDataResolver() {
   105     return this._target.client.traits.urlToImageDataResolver;
   106   },
   108   _deferredOpen: function(defaultSelection) {
   109     let deferred = promise.defer();
   111     this.onNewRoot = this.onNewRoot.bind(this);
   112     this.walker.on("new-root", this.onNewRoot);
   114     this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
   115     this.lastNodemenuItem = this.nodemenu.lastChild;
   116     this._setupNodeMenu = this._setupNodeMenu.bind(this);
   117     this._resetNodeMenu = this._resetNodeMenu.bind(this);
   118     this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
   119     this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
   121     this.onNewSelection = this.onNewSelection.bind(this);
   122     this.selection.on("new-node-front", this.onNewSelection);
   123     this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
   124     this.selection.on("before-new-node-front", this.onBeforeNewSelection);
   125     this.onDetached = this.onDetached.bind(this);
   126     this.selection.on("detached-front", this.onDetached);
   128     this.breadcrumbs = new HTMLBreadcrumbs(this);
   130     if (this.target.isLocalTab) {
   131       this.browser = this.target.tab.linkedBrowser;
   132       this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
   133       this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
   135       // Show a warning when the debugger is paused.
   136       // We show the warning only when the inspector
   137       // is selected.
   138       this.updateDebuggerPausedWarning = function() {
   139         let notificationBox = this._toolbox.getNotificationBox();
   140         let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
   141         if (!notification && this._toolbox.currentToolId == "inspector" &&
   142             this.target.isThreadPaused) {
   143           let message = this.strings.GetStringFromName("debuggerPausedWarning.message");
   144           notificationBox.appendNotification(message,
   145             "inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
   146         }
   148         if (notification && this._toolbox.currentToolId != "inspector") {
   149           notificationBox.removeNotification(notification);
   150         }
   152         if (notification && !this.target.isThreadPaused) {
   153           notificationBox.removeNotification(notification);
   154         }
   156       }.bind(this);
   157       this.target.on("thread-paused", this.updateDebuggerPausedWarning);
   158       this.target.on("thread-resumed", this.updateDebuggerPausedWarning);
   159       this._toolbox.on("select", this.updateDebuggerPausedWarning);
   160       this.updateDebuggerPausedWarning();
   161     }
   163     this._initMarkup();
   164     this.isReady = false;
   166     this.once("markuploaded", function() {
   167       this.isReady = true;
   169       // All the components are initialized. Let's select a node.
   170       this.selection.setNodeFront(defaultSelection, "inspector-open");
   172       this.markup.expandNode(this.selection.nodeFront);
   174       this.emit("ready");
   175       deferred.resolve(this);
   176     }.bind(this));
   178     this.setupSearchBox();
   179     this.setupSidebar();
   181     return deferred.promise;
   182   },
   184   _onBeforeNavigate: function() {
   185     this._defaultNode = null;
   186     this.selection.setNodeFront(null);
   187     this._destroyMarkup();
   188     this.isDirty = false;
   189   },
   191   _getPageStyle: function() {
   192     return this._toolbox.inspector.getPageStyle().then(pageStyle => {
   193       this.pageStyle = pageStyle;
   194     });
   195   },
   197   /**
   198    * Return a promise that will resolve to the default node for selection.
   199    */
   200   _getDefaultNodeForSelection: function() {
   201     if (this._defaultNode) {
   202       return this._defaultNode;
   203     }
   204     let walker = this.walker;
   205     let rootNode = null;
   207     // If available, set either the previously selected node or the body
   208     // as default selected, else set documentElement
   209     return walker.getRootNode().then(aRootNode => {
   210       rootNode = aRootNode;
   211       return walker.querySelector(rootNode, this.selectionCssSelector);
   212     }).then(front => {
   213       if (front) {
   214         return front;
   215       }
   216       return walker.querySelector(rootNode, "body");
   217     }).then(front => {
   218       if (front) {
   219         return front;
   220       }
   221       return this.walker.documentElement(this.walker.rootNode);
   222     }).then(node => {
   223       if (walker !== this.walker) {
   224         promise.reject(null);
   225       }
   226       this._defaultNode = node;
   227       return node;
   228     });
   229   },
   231   /**
   232    * Target getter.
   233    */
   234   get target() {
   235     return this._target;
   236   },
   238   /**
   239    * Target setter.
   240    */
   241   set target(value) {
   242     this._target = value;
   243   },
   245   /**
   246    * Expose gViewSourceUtils so that other tools can make use of them.
   247    */
   248   get viewSourceUtils() {
   249     return this.panelWin.gViewSourceUtils;
   250   },
   252   /**
   253    * Indicate that a tool has modified the state of the page.  Used to
   254    * decide whether to show the "are you sure you want to navigate"
   255    * notification.
   256    */
   257   markDirty: function InspectorPanel_markDirty() {
   258     this.isDirty = true;
   259   },
   261   /**
   262    * Hooks the searchbar to show result and auto completion suggestions.
   263    */
   264   setupSearchBox: function InspectorPanel_setupSearchBox() {
   265     // Initiate the selectors search object.
   266     if (this.searchSuggestions) {
   267       this.searchSuggestions.destroy();
   268       this.searchSuggestions = null;
   269     }
   270     this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
   271     this.searchSuggestions = new SelectorSearch(this, this.searchBox);
   272   },
   274   /**
   275    * Build the sidebar.
   276    */
   277   setupSidebar: function InspectorPanel_setupSidebar() {
   278     let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
   279     this.sidebar = new ToolSidebar(tabbox, this, "inspector");
   281     let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
   283     this._setDefaultSidebar = function(event, toolId) {
   284       Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
   285     }.bind(this);
   287     this.sidebar.on("select", this._setDefaultSidebar);
   289     this.sidebar.addTab("ruleview",
   290                         "chrome://browser/content/devtools/cssruleview.xhtml",
   291                         "ruleview" == defaultTab);
   293     this.sidebar.addTab("computedview",
   294                         "chrome://browser/content/devtools/computedview.xhtml",
   295                         "computedview" == defaultTab);
   297     if (Services.prefs.getBoolPref("devtools.fontinspector.enabled") && !this.target.isRemote) {
   298       this.sidebar.addTab("fontinspector",
   299                           "chrome://browser/content/devtools/fontinspector/font-inspector.xhtml",
   300                           "fontinspector" == defaultTab);
   301     }
   303     this.sidebar.addTab("layoutview",
   304                         "chrome://browser/content/devtools/layoutview/view.xhtml",
   305                         "layoutview" == defaultTab);
   307     let ruleViewTab = this.sidebar.getTab("ruleview");
   309     this.sidebar.show();
   310   },
   312   /**
   313    * Reset the inspector on new root mutation.
   314    */
   315   onNewRoot: function InspectorPanel_onNewRoot() {
   316     this._defaultNode = null;
   317     this.selection.setNodeFront(null);
   318     this._destroyMarkup();
   319     this.isDirty = false;
   321     let onNodeSelected = defaultNode => {
   322       // Cancel this promise resolution as a new one had
   323       // been queued up.
   324       if (this._pendingSelection != onNodeSelected) {
   325         return;
   326       }
   327       this._pendingSelection = null;
   328       this.selection.setNodeFront(defaultNode, "navigateaway");
   330       this._initMarkup();
   331       this.once("markuploaded", () => {
   332         if (!this.markup) {
   333           return;
   334         }
   335         this.markup.expandNode(this.selection.nodeFront);
   336         this.setupSearchBox();
   337         this.emit("new-root");
   338       });
   339     };
   340     this._pendingSelection = onNodeSelected;
   341     this._getDefaultNodeForSelection().then(onNodeSelected);
   342   },
   344   _selectionCssSelector: null,
   346   /**
   347    * Set the currently selected node unique css selector.
   348    * Will store the current target url along with it to allow pre-selection at
   349    * reload
   350    */
   351   set selectionCssSelector(cssSelector) {
   352     this._selectionCssSelector = {
   353       selector: cssSelector,
   354       url: this._target.url
   355     };
   356   },
   358   /**
   359    * Get the current selection unique css selector if any, that is, if a node
   360    * is actually selected and that node has been selected while on the same url
   361    */
   362   get selectionCssSelector() {
   363     if (this._selectionCssSelector &&
   364         this._selectionCssSelector.url === this._target.url) {
   365       return this._selectionCssSelector.selector;
   366     } else {
   367       return null;
   368     }
   369   },
   371   /**
   372    * When a new node is selected.
   373    */
   374   onNewSelection: function InspectorPanel_onNewSelection(event, value, reason) {
   375     if (reason === "selection-destroy") {
   376       return;
   377     }
   379     this.cancelLayoutChange();
   381     // Wait for all the known tools to finish updating and then let the
   382     // client know.
   383     let selection = this.selection.nodeFront;
   385     // On any new selection made by the user, store the unique css selector
   386     // of the selected node so it can be restored after reload of the same page
   387     if (reason !== "navigateaway" &&
   388         this.selection.node &&
   389         this.selection.isElementNode()) {
   390       this.selectionCssSelector = CssLogic.findCssSelector(this.selection.node);
   391     }
   393     let selfUpdate = this.updating("inspector-panel");
   394     Services.tm.mainThread.dispatch(() => {
   395       try {
   396         selfUpdate(selection);
   397       } catch(ex) {
   398         console.error(ex);
   399       }
   400     }, Ci.nsIThread.DISPATCH_NORMAL);
   401   },
   403   /**
   404    * Delay the "inspector-updated" notification while a tool
   405    * is updating itself.  Returns a function that must be
   406    * invoked when the tool is done updating with the node
   407    * that the tool is viewing.
   408    */
   409   updating: function(name) {
   410     if (this._updateProgress && this._updateProgress.node != this.selection.nodeFront) {
   411       this.cancelUpdate();
   412     }
   414     if (!this._updateProgress) {
   415       // Start an update in progress.
   416       var self = this;
   417       this._updateProgress = {
   418         node: this.selection.nodeFront,
   419         outstanding: new Set(),
   420         checkDone: function() {
   421           if (this !== self._updateProgress) {
   422             return;
   423           }
   424           if (this.node !== self.selection.nodeFront) {
   425             self.cancelUpdate();
   426             return;
   427           }
   428           if (this.outstanding.size !== 0) {
   429             return;
   430           }
   432           self._updateProgress = null;
   433           self.emit("inspector-updated", name);
   434         },
   435       };
   436     }
   438     let progress = this._updateProgress;
   439     let done = function() {
   440       progress.outstanding.delete(done);
   441       progress.checkDone();
   442     };
   443     progress.outstanding.add(done);
   444     return done;
   445   },
   447   /**
   448    * Cancel notification of inspector updates.
   449    */
   450   cancelUpdate: function() {
   451     this._updateProgress = null;
   452   },
   454   /**
   455    * When a new node is selected, before the selection has changed.
   456    */
   457   onBeforeNewSelection: function InspectorPanel_onBeforeNewSelection(event,
   458                                                                      node) {
   459     if (this.breadcrumbs.indexOf(node) == -1) {
   460       // only clear locks if we'd have to update breadcrumbs
   461       this.clearPseudoClasses();
   462     }
   463   },
   465   /**
   466    * When a node is deleted, select its parent node or the defaultNode if no
   467    * parent is found (may happen when deleting an iframe inside which the
   468    * node was selected).
   469    */
   470   onDetached: function InspectorPanel_onDetached(event, parentNode) {
   471     this.cancelLayoutChange();
   472     this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
   473     this.selection.setNodeFront(parentNode ? parentNode : this._defaultNode, "detached");
   474   },
   476   /**
   477    * Destroy the inspector.
   478    */
   479   destroy: function InspectorPanel__destroy() {
   480     if (this._panelDestroyer) {
   481       return this._panelDestroyer;
   482     }
   484     if (this.walker) {
   485       this.walker.off("new-root", this.onNewRoot);
   486       this.pageStyle = null;
   487     }
   489     this.cancelUpdate();
   490     this.cancelLayoutChange();
   492     if (this.browser) {
   493       this.browser.removeEventListener("resize", this.scheduleLayoutChange, true);
   494       this.browser = null;
   495     }
   497     this.target.off("will-navigate", this._onBeforeNavigate);
   499     this.target.off("thread-paused", this.updateDebuggerPausedWarning);
   500     this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
   501     this._toolbox.off("select", this.updateDebuggerPausedWarning);
   503     this.sidebar.off("select", this._setDefaultSidebar);
   504     this.sidebar.destroy();
   505     this.sidebar = null;
   507     this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
   508     this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
   509     this.breadcrumbs.destroy();
   510     this.searchSuggestions.destroy();
   511     this.searchBox = null;
   512     this.selection.off("new-node-front", this.onNewSelection);
   513     this.selection.off("before-new-node", this.onBeforeNewSelection);
   514     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
   515     this.selection.off("detached-front", this.onDetached);
   516     this._panelDestroyer = this._destroyMarkup();
   517     this.panelWin.inspector = null;
   518     this.target = null;
   519     this.panelDoc = null;
   520     this.panelWin = null;
   521     this.breadcrumbs = null;
   522     this.searchSuggestions = null;
   523     this.lastNodemenuItem = null;
   524     this.nodemenu = null;
   525     this._toolbox = null;
   527     return this._panelDestroyer;
   528   },
   530   /**
   531    * Show the node menu.
   532    */
   533   showNodeMenu: function InspectorPanel_showNodeMenu(aButton, aPosition, aExtraItems) {
   534     if (aExtraItems) {
   535       for (let item of aExtraItems) {
   536         this.nodemenu.appendChild(item);
   537       }
   538     }
   539     this.nodemenu.openPopup(aButton, aPosition, 0, 0, true, false);
   540   },
   542   hideNodeMenu: function InspectorPanel_hideNodeMenu() {
   543     this.nodemenu.hidePopup();
   544   },
   546   /**
   547    * Disable the delete item if needed. Update the pseudo classes.
   548    */
   549   _setupNodeMenu: function InspectorPanel_setupNodeMenu() {
   550     let isSelectionElement = this.selection.isElementNode();
   552     // Set the pseudo classes
   553     for (let name of ["hover", "active", "focus"]) {
   554       let menu = this.panelDoc.getElementById("node-menu-pseudo-" + name);
   556       if (isSelectionElement) {
   557         let checked = this.selection.nodeFront.hasPseudoClassLock(":" + name);
   558         menu.setAttribute("checked", checked);
   559         menu.removeAttribute("disabled");
   560       } else {
   561         menu.setAttribute("disabled", "true");
   562       }
   563     }
   565     // Disable delete item if needed
   566     let deleteNode = this.panelDoc.getElementById("node-menu-delete");
   567     if (this.selection.isRoot() || this.selection.isDocumentTypeNode()) {
   568       deleteNode.setAttribute("disabled", "true");
   569     } else {
   570       deleteNode.removeAttribute("disabled");
   571     }
   573     // Disable / enable "Copy Unique Selector", "Copy inner HTML" &
   574     // "Copy outer HTML" as appropriate
   575     let unique = this.panelDoc.getElementById("node-menu-copyuniqueselector");
   576     let copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner");
   577     let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
   578     if (isSelectionElement) {
   579       unique.removeAttribute("disabled");
   580       copyInnerHTML.removeAttribute("disabled");
   581       copyOuterHTML.removeAttribute("disabled");
   582     } else {
   583       unique.setAttribute("disabled", "true");
   584       copyInnerHTML.setAttribute("disabled", "true");
   585       copyOuterHTML.setAttribute("disabled", "true");
   586     }
   588     // Enable the "edit HTML" item if the selection is an element and the root
   589     // actor has the appropriate trait (isOuterHTMLEditable)
   590     let editHTML = this.panelDoc.getElementById("node-menu-edithtml");
   591     if (this.isOuterHTMLEditable && isSelectionElement) {
   592       editHTML.removeAttribute("disabled");
   593     } else {
   594       editHTML.setAttribute("disabled", "true");
   595     }
   597     // Enable the "copy image data-uri" item if the selection is previewable
   598     // which essentially checks if it's an image or canvas tag
   599     let copyImageData = this.panelDoc.getElementById("node-menu-copyimagedatauri");
   600     let markupContainer = this.markup.getContainer(this.selection.nodeFront);
   601     if (markupContainer && markupContainer.isPreviewable()) {
   602       copyImageData.removeAttribute("disabled");
   603     } else {
   604       copyImageData.setAttribute("disabled", "true");
   605     }
   606   },
   608   _resetNodeMenu: function InspectorPanel_resetNodeMenu() {
   609     // Remove any extra items
   610     while (this.lastNodemenuItem.nextSibling) {
   611       let toDelete = this.lastNodemenuItem.nextSibling;
   612       toDelete.parentNode.removeChild(toDelete);
   613     }
   614   },
   616   _initMarkup: function InspectorPanel_initMarkup() {
   617     let doc = this.panelDoc;
   619     this._markupBox = doc.getElementById("markup-box");
   621     // create tool iframe
   622     this._markupFrame = doc.createElement("iframe");
   623     this._markupFrame.setAttribute("flex", "1");
   624     this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
   625     this._markupFrame.setAttribute("context", "inspector-node-popup");
   627     // This is needed to enable tooltips inside the iframe document.
   628     this._boundMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
   629     this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true);
   631     this._markupBox.setAttribute("collapsed", true);
   632     this._markupBox.appendChild(this._markupFrame);
   633     this._markupFrame.setAttribute("src", "chrome://browser/content/devtools/markup-view.xhtml");
   634   },
   636   _onMarkupFrameLoad: function InspectorPanel__onMarkupFrameLoad() {
   637     this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
   638     delete this._boundMarkupFrameLoad;
   640     this._markupFrame.contentWindow.focus();
   642     this._markupBox.removeAttribute("collapsed");
   644     let controllerWindow = this._toolbox.doc.defaultView;
   645     this.markup = new MarkupView(this, this._markupFrame, controllerWindow);
   647     this.emit("markuploaded");
   648   },
   650   _destroyMarkup: function InspectorPanel__destroyMarkup() {
   651     let destroyPromise;
   653     if (this._boundMarkupFrameLoad) {
   654       this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
   655       this._boundMarkupFrameLoad = null;
   656     }
   658     if (this.markup) {
   659       destroyPromise = this.markup.destroy();
   660       this.markup = null;
   661     } else {
   662       destroyPromise = promise.resolve();
   663     }
   665     if (this._markupFrame) {
   666       this._markupFrame.parentNode.removeChild(this._markupFrame);
   667       this._markupFrame = null;
   668     }
   670     this._markupBox = null;
   672     return destroyPromise;
   673   },
   675   /**
   676    * Toggle a pseudo class.
   677    */
   678   togglePseudoClass: function InspectorPanel_togglePseudoClass(aPseudo) {
   679     if (this.selection.isElementNode()) {
   680       let node = this.selection.nodeFront;
   681       if (node.hasPseudoClassLock(aPseudo)) {
   682         return this.walker.removePseudoClassLock(node, aPseudo, {parents: true});
   683       }
   685       let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
   686       return this.walker.addPseudoClassLock(node, aPseudo, {parents: hierarchical});
   687     }
   688   },
   690   /**
   691    * Clear any pseudo-class locks applied to the current hierarchy.
   692    */
   693   clearPseudoClasses: function InspectorPanel_clearPseudoClasses() {
   694     if (!this.walker) {
   695       return;
   696     }
   697     return this.walker.clearPseudoClassLocks().then(null, console.error);
   698   },
   700   /**
   701    * Edit the outerHTML of the selected Node.
   702    */
   703   editHTML: function InspectorPanel_editHTML()
   704   {
   705     if (!this.selection.isNode()) {
   706       return;
   707     }
   708     if (this.markup) {
   709       this.markup.beginEditingOuterHTML(this.selection.nodeFront);
   710     }
   711   },
   713   /**
   714    * Copy the innerHTML of the selected Node to the clipboard.
   715    */
   716   copyInnerHTML: function InspectorPanel_copyInnerHTML()
   717   {
   718     if (!this.selection.isNode()) {
   719       return;
   720     }
   721     this._copyLongStr(this.walker.innerHTML(this.selection.nodeFront));
   722   },
   724   /**
   725    * Copy the outerHTML of the selected Node to the clipboard.
   726    */
   727   copyOuterHTML: function InspectorPanel_copyOuterHTML()
   728   {
   729     if (!this.selection.isNode()) {
   730       return;
   731     }
   733     this._copyLongStr(this.walker.outerHTML(this.selection.nodeFront));
   734   },
   736   /**
   737    * Copy the data-uri for the currently selected image in the clipboard.
   738    */
   739   copyImageDataUri: function InspectorPanel_copyImageDataUri()
   740   {
   741     let container = this.markup.getContainer(this.selection.nodeFront);
   742     if (container && container.isPreviewable()) {
   743       container.copyImageDataUri();
   744     }
   745   },
   747   _copyLongStr: function InspectorPanel_copyLongStr(promise)
   748   {
   749     return promise.then(longstr => {
   750       return longstr.string().then(toCopy => {
   751         longstr.release().then(null, console.error);
   752         clipboardHelper.copyString(toCopy);
   753       });
   754     }).then(null, console.error);
   755   },
   757   /**
   758    * Copy a unique selector of the selected Node to the clipboard.
   759    */
   760   copyUniqueSelector: function InspectorPanel_copyUniqueSelector()
   761   {
   762     if (!this.selection.isNode()) {
   763       return;
   764     }
   766     let toCopy = CssLogic.findCssSelector(this.selection.node);
   767     if (toCopy) {
   768       clipboardHelper.copyString(toCopy);
   769     }
   770   },
   772   /**
   773    * Delete the selected node.
   774    */
   775   deleteNode: function IUI_deleteNode() {
   776     if (!this.selection.isNode() ||
   777          this.selection.isRoot()) {
   778       return;
   779     }
   781     // If the markup panel is active, use the markup panel to delete
   782     // the node, making this an undoable action.
   783     if (this.markup) {
   784       this.markup.deleteNode(this.selection.nodeFront);
   785     } else {
   786       // remove the node from content
   787       this.walker.removeNode(this.selection.nodeFront);
   788     }
   789   },
   791   /**
   792   * Trigger a high-priority layout change for things that need to be
   793   * updated immediately
   794   */
   795   immediateLayoutChange: function Inspector_immediateLayoutChange()
   796   {
   797     this.emit("layout-change");
   798   },
   800   /**
   801    * Schedule a low-priority change event for things like paint
   802    * and resize.
   803    */
   804   scheduleLayoutChange: function Inspector_scheduleLayoutChange(event)
   805   {
   806     // Filter out non browser window resize events (i.e. triggered by iframes)
   807     if (this.browser.contentWindow === event.target) {
   808       if (this._timer) {
   809         return null;
   810       }
   811       this._timer = this.panelWin.setTimeout(function() {
   812         this.emit("layout-change");
   813         this._timer = null;
   814       }.bind(this), LAYOUT_CHANGE_TIMER);
   815     }
   816   },
   818   /**
   819    * Cancel a pending low-priority change event if any is
   820    * scheduled.
   821    */
   822   cancelLayoutChange: function Inspector_cancelLayoutChange()
   823   {
   824     if (this._timer) {
   825       this.panelWin.clearTimeout(this._timer);
   826       delete this._timer;
   827     }
   828   }
   829 };
   831 /////////////////////////////////////////////////////////////////////////
   832 //// Initializers
   834 loader.lazyGetter(InspectorPanel.prototype, "strings",
   835   function () {
   836     return Services.strings.createBundle(
   837             "chrome://browser/locale/devtools/inspector.properties");
   838   });
   840 loader.lazyGetter(this, "clipboardHelper", function() {
   841   return Cc["@mozilla.org/widget/clipboardhelper;1"].
   842     getService(Ci.nsIClipboardHelper);
   843 });
   846 loader.lazyGetter(this, "DOMUtils", function () {
   847   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
   848 });

mercurial