/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set 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/. */ "use strict"; const {Cu} = require("chrome"); const promise = require("promise"); const {Tools} = require("devtools/client/main"); Cu.import("resource://gre/modules/Services.jsm"); const {PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils"); loader.lazyGetter(this, "gDevTools", () => Cu.import("resource://devtools/client/framework/gDevTools.jsm", {}).gDevTools); loader.lazyGetter(this, "RuleView", () => require("devtools/client/styleinspector/rule-view")); loader.lazyGetter(this, "ComputedView", () => require("devtools/client/styleinspector/computed-view")); loader.lazyGetter(this, "_strings", () => Services.strings .createBundle("chrome://devtools-shared/locale/styleinspector.properties")); // This module doesn't currently export any symbols directly, it only // registers inspector tools. function RuleViewTool(inspector, window) { this.inspector = inspector; this.document = window.document; this.view = new RuleView.CssRuleView(this.inspector, this.document); this.onLinkClicked = this.onLinkClicked.bind(this); this.onSelected = this.onSelected.bind(this); this.refresh = this.refresh.bind(this); this.clearUserProperties = this.clearUserProperties.bind(this); this.onPropertyChanged = this.onPropertyChanged.bind(this); this.onViewRefreshed = this.onViewRefreshed.bind(this); this.onPanelSelected = this.onPanelSelected.bind(this); this.onMutations = this.onMutations.bind(this); this.onResized = this.onResized.bind(this); this.view.on("ruleview-changed", this.onPropertyChanged); this.view.on("ruleview-refreshed", this.onViewRefreshed); this.view.on("ruleview-linked-clicked", this.onLinkClicked); this.inspector.selection.on("detached", this.onSelected); this.inspector.selection.on("new-node-front", this.onSelected); this.inspector.selection.on("pseudoclass", this.refresh); this.inspector.target.on("navigate", this.clearUserProperties); this.inspector.sidebar.on("ruleview-selected", this.onPanelSelected); this.inspector.pageStyle.on("stylesheet-updated", this.refresh); this.inspector.walker.on("mutations", this.onMutations); this.inspector.walker.on("resize", this.onResized); this.onSelected(); } RuleViewTool.prototype = { isSidebarActive: function() { if (!this.view) { return false; } return this.inspector.sidebar.getCurrentTabID() == "ruleview"; }, onSelected: function(event) { // Ignore the event if the view has been destroyed, or if it's inactive. // But only if the current selection isn't null. If it's been set to null, // let the update go through as this is needed to empty the view on // navigation. if (!this.view) { return; } let isInactive = !this.isSidebarActive() && this.inspector.selection.nodeFront; if (isInactive) { return; } this.view.setPageStyle(this.inspector.pageStyle); if (!this.inspector.selection.isConnected() || !this.inspector.selection.isElementNode()) { this.view.selectElement(null); return; } if (!event || event == "new-node-front") { let done = this.inspector.updating("rule-view"); this.view.selectElement(this.inspector.selection.nodeFront) .then(done, done); } }, refresh: function() { if (this.isSidebarActive()) { this.view.refreshPanel(); } }, clearUserProperties: function() { if (this.view && this.view.store && this.view.store.userProperties) { this.view.store.userProperties.clear(); } }, onPanelSelected: function() { if (this.inspector.selection.nodeFront === this.view.viewedElement) { this.refresh(); } else { this.onSelected(); } }, onLinkClicked: function(e, rule) { let sheet = rule.parentStyleSheet; // Chrome stylesheets are not listed in the style editor, so show // these sheets in the view source window instead. if (!sheet || sheet.isSystem) { let href = rule.nodeHref || rule.href; let toolbox = gDevTools.getToolbox(this.inspector.target); toolbox.viewSource(href, rule.line); return; } let location = promise.resolve(rule.location); if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) { location = rule.getOriginalLocation(); } location.then(({ source, href, line, column }) => { let target = this.inspector.target; if (Tools.styleEditor.isTargetSupported(target)) { gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) { let sheet = source || href; toolbox.getCurrentPanel().selectStyleSheet(sheet, line, column); }); } return; }); }, onPropertyChanged: function() { this.inspector.markDirty(); }, onViewRefreshed: function() { this.inspector.emit("rule-view-refreshed"); }, /** * When markup mutations occur, if an attribute of the selected node changes, * we need to refresh the view as that might change the node's styles. */ onMutations: function(mutations) { for (let {type, target} of mutations) { if (target === this.inspector.selection.nodeFront && type === "attributes") { this.refresh(); break; } } }, /** * When the window gets resized, this may cause media-queries to match, and * therefore, different styles may apply. */ onResized: function() { this.refresh(); }, destroy: function() { this.inspector.walker.off("mutations", this.onMutations); this.inspector.walker.off("resize", this.onResized); this.inspector.selection.off("detached", this.onSelected); this.inspector.selection.off("pseudoclass", this.refresh); this.inspector.selection.off("new-node-front", this.onSelected); this.inspector.target.off("navigate", this.clearUserProperties); this.inspector.sidebar.off("ruleview-selected", this.onPanelSelected); if (this.inspector.pageStyle) { this.inspector.pageStyle.off("stylesheet-updated", this.refresh); } this.view.off("ruleview-linked-clicked", this.onLinkClicked); this.view.off("ruleview-changed", this.onPropertyChanged); this.view.off("ruleview-refreshed", this.onViewRefreshed); this.view.destroy(); this.view = this.document = this.inspector = null; } }; function ComputedViewTool(inspector, window) { this.inspector = inspector; this.document = window.document; this.view = new ComputedView.CssComputedView(this.inspector, this.document, this.inspector.pageStyle); this.onSelected = this.onSelected.bind(this); this.refresh = this.refresh.bind(this); this.onPanelSelected = this.onPanelSelected.bind(this); this.onMutations = this.onMutations.bind(this); this.onResized = this.onResized.bind(this); this.inspector.selection.on("detached", this.onSelected); this.inspector.selection.on("new-node-front", this.onSelected); this.inspector.selection.on("pseudoclass", this.refresh); this.inspector.sidebar.on("computedview-selected", this.onPanelSelected); this.inspector.pageStyle.on("stylesheet-updated", this.refresh); this.inspector.walker.on("mutations", this.onMutations); this.inspector.walker.on("resize", this.onResized); this.view.selectElement(null); this.onSelected(); } ComputedViewTool.prototype = { isSidebarActive: function() { if (!this.view) { return false; } return this.inspector.sidebar.getCurrentTabID() == "computedview"; }, onSelected: function(event) { // Ignore the event if the view has been destroyed, or if it's inactive. // But only if the current selection isn't null. If it's been set to null, // let the update go through as this is needed to empty the view on // navigation. if (!this.view) { return; } let isInactive = !this.isSidebarActive() && this.inspector.selection.nodeFront; if (isInactive) { return; } this.view.setPageStyle(this.inspector.pageStyle); if (!this.inspector.selection.isConnected() || !this.inspector.selection.isElementNode()) { this.view.selectElement(null); return; } if (!event || event == "new-node-front") { let done = this.inspector.updating("computed-view"); this.view.selectElement(this.inspector.selection.nodeFront).then(() => { done(); }); } }, refresh: function() { if (this.isSidebarActive()) { this.view.refreshPanel(); } }, onPanelSelected: function() { if (this.inspector.selection.nodeFront === this.view.viewedElement) { this.refresh(); } else { this.onSelected(); } }, /** * When markup mutations occur, if an attribute of the selected node changes, * we need to refresh the view as that might change the node's styles. */ onMutations: function(mutations) { for (let {type, target} of mutations) { if (target === this.inspector.selection.nodeFront && type === "attributes") { this.refresh(); break; } } }, /** * When the window gets resized, this may cause media-queries to match, and * therefore, different styles may apply. */ onResized: function() { this.refresh(); }, destroy: function() { this.inspector.walker.off("mutations", this.onMutations); this.inspector.walker.off("resize", this.onResized); this.inspector.sidebar.off("computedview-selected", this.refresh); this.inspector.selection.off("pseudoclass", this.refresh); this.inspector.selection.off("new-node-front", this.onSelected); this.inspector.selection.off("detached", this.onSelected); this.inspector.sidebar.off("computedview-selected", this.onPanelSelected); if (this.inspector.pageStyle) { this.inspector.pageStyle.off("stylesheet-updated", this.refresh); } this.view.destroy(); this.view = this.document = this.inspector = null; } }; exports.RuleViewTool = RuleViewTool; exports.ComputedViewTool = ComputedViewTool;