Wed, 31 Dec 2014 06:09:35 +0100
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 });