browser/devtools/styleinspector/StyleInspector.jsm
author Ed Morley <emorley@mozilla.com>
Tue, 30 Oct 2012 17:02:31 +0000
changeset 111799 ad1d720d82b7f84d3c7e50f4b02b7c3201662ddb
parent 111786 1545e91c658ec9cd25d9750a43bbe7afd8057cf9
child 111803 67cb43bb8865ecbcb79c2ea04a0494fd223cc69d
permissions -rw-r--r--
Backout a145ded68994, e0cf397089ec & 1545e91c658e (bug 798491) for bustage on a CLOSED TREE

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 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/. */

const Cc = Components.classes;
const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/CssRuleView.jsm");
Cu.import("resource:///modules/inspector.jsm");

// This module doesn't currently export any symbols directly, it only
// registers inspector tools.
var EXPORTED_SYMBOLS = [];

/**
 * Lookup l10n string from a string bundle.
 * @param {string} aName The key to lookup.
 * @returns A localized version of the given key.
 */
function l10n(aName)
{
  try {
    return _strings.GetStringFromName(aName);
  } catch (ex) {
    Services.console.logStringMessage("Error reading '" + aName + "'");
    throw new Error("l10n error with " + aName);
  }
}

function RegisterStyleTools()
{
  // Register the rules view
  if (Services.prefs.getBoolPref("devtools.ruleview.enabled")) {
    InspectorUI.registerSidebar({
      id: "ruleview",
      label: l10n("ruleView.label"),
      tooltiptext: l10n("ruleView.tooltiptext"),
      accesskey: l10n("ruleView.accesskey"),
      contentURL: "chrome://browser/content/devtools/cssruleview.xul",
      load: function(aInspector, aFrame) new RuleViewTool(aInspector, aFrame),
      destroy: function(aContext) aContext.destroy()
    });
  }

  // Register the computed styles view
  if (Services.prefs.getBoolPref("devtools.styleinspector.enabled")) {
    InspectorUI.registerSidebar({
      id: "computedview",
      label: this.l10n("style.highlighter.button.label2"),
      tooltiptext: this.l10n("style.highlighter.button.tooltip2"),
      accesskey: this.l10n("style.highlighter.accesskey2"),
      contentURL: "chrome://browser/content/devtools/csshtmltree.xul",
      load: function(aInspector, aFrame) new ComputedViewTool(aInspector, aFrame),
      destroy: function(aContext) aContext.destroy()
    });
  }
}

function RuleViewTool(aInspector, aFrame)
{
  this.inspector = aInspector;
  this.chromeWindow = this.inspector.chromeWindow;
  this.doc = aFrame.contentDocument;

  if (!this.inspector._ruleViewStore) {
   this.inspector._ruleViewStore = {};
  }
  this.view = new CssRuleView(this.doc, this.inspector._ruleViewStore);
  this.doc.documentElement.appendChild(this.view.element);

  this._changeHandler = function() {
    this.inspector.markDirty();
    this.inspector.change("ruleview");
  }.bind(this);

  this.view.element.addEventListener("CssRuleViewChanged", this._changeHandler)

  this._cssLinkHandler = function(aEvent) {
    let rule = aEvent.detail.rule;
    let styleSheet = rule.sheet;
    let doc = this.chromeWindow.content.document;
    let styleSheets = doc.styleSheets;
    let contentSheet = false;
    let line = rule.ruleLine || 0;

    // Array.prototype.indexOf always returns -1 here so we loop through
    // the styleSheets object instead.
    for each (let sheet in styleSheets) {
      if (sheet == styleSheet) {
        contentSheet = true;
        break;
      }
    }

    if (contentSheet)  {
      this.chromeWindow.StyleEditor.openChrome(styleSheet, line);
    } else {
      let href = styleSheet ? styleSheet.href : "";
      if (rule.elementStyle.element) {
        href = rule.elementStyle.element.ownerDocument.location.href;
      }
      let viewSourceUtils = this.chromeWindow.gViewSourceUtils;
      viewSourceUtils.viewSource(href, null, doc, line);
    }
  }.bind(this);

  this.view.element.addEventListener("CssRuleViewCSSLinkClicked",
                                     this._cssLinkHandler);

  this._onSelect = this.onSelect.bind(this);
  this.inspector.on("select", this._onSelect);

  this._onChange = this.onChange.bind(this);
  this.inspector.on("change", this._onChange);
  this.inspector.on("sidebaractivated-ruleview", this._onChange);

  this.onSelect();
}

RuleViewTool.prototype = {
  onSelect: function RVT_onSelect(aEvent, aFrom) {
    let node = this.inspector.selection;
    if (!node) {
      this.view.highlight(null);
      return;
    }

    if (this.inspector.locked) {
      this.view.highlight(node);
    }
  },

  onChange: function RVT_onChange(aEvent, aFrom) {
    if (aFrom == "ruleview" || aFrom == "createpanel") {
      return;
    }

    if (this.inspector.locked && this.inspector.isPanelVisible("ruleview")) {
      this.view.nodeChanged();
    }
  },

  destroy: function RVT_destroy() {
    this.inspector.off("select", this._onSelect);
    this.inspector.off("change", this._onChange);
    this.inspector.off("sidebaractivated-ruleview", this._onChange);
    this.view.element.removeEventListener("CssRuleViewChanged",
                                          this._changeHandler);
    this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
                                          this._cssLinkHandler);
    this.doc.documentElement.removeChild(this.view.element);

    this.view.destroy();

    delete this._changeHandler;
    delete this.view;
    delete this.doc;
    delete this.inspector;
  }
}

function ComputedViewTool(aInspector, aFrame)
{
  this.inspector = aInspector;
  this.iframe = aFrame;
  this.window = aInspector.chromeWindow;
  this.document = this.window.document;
  this.cssLogic = new CssLogic();
  this.view = new CssHtmlTree(this);

  this._onSelect = this.onSelect.bind(this);
  this.inspector.on("select", this._onSelect);
  this._onChange = this.onChange.bind(this);
  this.inspector.on("change", this._onChange);

  // Since refreshes of the computed view are non-destructive,
  // refresh when the tab is changed so we can notice script-driven
  // changes.
  this.inspector.on("sidebaractivated-computedview", this._onChange);

  this.cssLogic.highlight(null);
  this.view.highlight(null);

  this.onSelect();
}

ComputedViewTool.prototype = {
  onSelect: function CVT_onSelect(aEvent)
  {
    if (this.inspector.locked) {
      this.cssLogic.highlight(this.inspector.selection);
      this.view.highlight(this.inspector.selection);
    }
  },

  onChange: function CVT_change(aEvent, aFrom)
  {
    if (aFrom == "computedview" ||
        aFrom == "createpanel" ||
        this.inspector.selection != this.cssLogic.viewedElement) {
      return;
    }

    if (this.inspector.locked && this.inspector.isPanelVisible("computedview")) {
      this.cssLogic.highlight(this.inspector.selection);
      this.view.refreshPanel();
    }
  },

  destroy: function CVT_destroy(aContext)
  {
    this.inspector.off("select", this._onSelect);
    this.inspector.off("change", this._onChange);
    this.inspector.off("sidebaractivated-computedview", this._onChange);
    this.view.destroy();
    delete this.view;

    delete this.cssLogic;
    delete this.cssHtmlTree;
    delete this.iframe;
    delete this.window;
    delete this.document;
  }
}

XPCOMUtils.defineLazyGetter(this, "_strings", function() Services.strings
  .createBundle("chrome://browser/locale/devtools/styleinspector.properties"));

XPCOMUtils.defineLazyGetter(this, "CssLogic", function() {
  let tmp = {};
  Cu.import("resource:///modules/devtools/CssLogic.jsm", tmp);
  return tmp.CssLogic;
});

XPCOMUtils.defineLazyGetter(this, "CssHtmlTree", function() {
  let tmp = {};
  Cu.import("resource:///modules/devtools/CssHtmlTree.jsm", tmp);
  return tmp.CssHtmlTree;
});

RegisterStyleTools();