# HG changeset patch
# User Matt A. Tobin <email@mattatobin.com>
# Date 1723762878 18000
# Node ID aa3be2ac76c951a26740493567c0723492a8ac75
# Parent  efbcad31859404faa270ae8af3703e4996beea99
Bug 1912354 - Allow DOMi to inspect any DOM element by shift + right click

diff --git a/suite/extensions/inspector/base/js/inspector-startup.js b/suite/extensions/inspector/base/js/inspector-startup.js
new file mode 100644
--- /dev/null
+++ b/suite/extensions/inspector/base/js/inspector-startup.js
@@ -0,0 +1,42 @@
+/* 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/. */
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * A startup/shutdown observer, triggers init()/uninit() methods
+ * @constructor
+ */
+function InspectorStartup() {}
+InspectorStartup.prototype =
+{
+  classDescription: "DOMi Startup",
+  contractID: "@mozilla.org/domi/startup;1",
+  classID: Components.ID("{4788f52a-0fbb-494c-8ffd-25e0ff39d80d}"),
+  _xpcom_categories: [{ category: "app-startup", service: true }],
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
+
+  observe: function(subject, topic, data) {
+    switch (topic) {
+      case "app-startup":
+        Services.obs.addObserver(this, "profile-after-change", true);
+        break;
+      case "profile-after-change":
+        if (Services.prefs.getBoolPref("inspector.inspectElementKeyCombo.enabled", false)) {
+          Services.obs.addObserver(this, "quit-application", true);
+          ChromeUtils.import("resource://inspector/InspectElement.jsm");
+          InspectElement.init();
+        }
+        break;
+      case "quit-application":
+        Services.obs.removeObserver(this, "quit-application");
+        InspectElement.uninit();
+        break;
+    }
+  }
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([InspectorStartup]);
diff --git a/suite/extensions/inspector/jar.mn b/suite/extensions/inspector/jar.mn
--- a/suite/extensions/inspector/jar.mn
+++ b/suite/extensions/inspector/jar.mn
@@ -8,16 +8,19 @@ inspector.jar:
 % overlay chrome://inspector/content/inspector.xul chrome://communicator/content/tasksOverlay.xul
 % overlay chrome://communicator/content/tasksOverlay.xul chrome://inspector/content/tasksOverlay.xul
 % overlay chrome://communicator/content/pref/preferences.xul chrome://inspector/content/prefs/prefsOverlay.xul
 % overlay chrome://inspector/content/commandOverlay.xul chrome://inspector/content/viewers/dom/commandOverlay.xul
 % overlay chrome://inspector/content/commandOverlay.xul chrome://inspector/content/viewers/styleRules/commandOverlay.xul
 % overlay chrome://inspector/content/keysetOverlay.xul chrome://inspector/content/viewers/dom/keysetOverlay.xul
 % overlay chrome://inspector/content/popupOverlay.xul chrome://inspector/content/viewers/dom/popupOverlay.xul
 % overlay chrome://inspector/content/popupOverlay.xul chrome://inspector/content/viewers/styleRules/popupOverlay.xul
+% component {4788f52a-0fbb-494c-8ffd-25e0ff39d80d} components/inspector-startup.js
+% contract @mozilla.org/domi/startup;1 {4788f52a-0fbb-494c-8ffd-25e0ff39d80d}
+% category profile-after-change @mozilla.org/domi/startup;1 @mozilla.org/domi/startup;1
 #ifdef XPI_NAME
 %  component {38293526-6b13-4d4f-a075-71939435b408} components/inspector-cmdline.js
 %  contract @mozilla.org/commandlinehandler/general-startup;1?type=inspector {38293526-6b13-4d4f-a075-71939435b408}
 %  category command-line-handler m-inspector @mozilla.org/commandlinehandler/general-startup;1?type=inspector
 #endif
   content/inspector/inspector.xul                                       (resources/content/inspector.xul)
   content/inspector/inspector.js                                        (resources/content/inspector.js)
   content/inspector/inspector.css                                       (resources/content/inspector.css)
@@ -167,8 +170,9 @@ inspector.jar:
   skin/modern/inspector/viewers/accessibleTree/accessibleTree.css       (resources/skin/modern/viewers/accessibleTree/accessibleTree.css)
   skin/modern/inspector/viewers/boxModel/boxModel.css                   (resources/skin/modern/viewers/boxModel/boxModel.css)
   skin/modern/inspector/viewers/dom/columnsDialog.css                   (resources/skin/modern/viewers/dom/columnsDialog.css)
   skin/modern/inspector/viewers/dom/dom.css                             (resources/skin/modern/viewers/dom/dom.css)
   skin/modern/inspector/viewers/dom/findDialog.css                      (resources/skin/modern/viewers/dom/findDialog.css)
   skin/modern/inspector/viewers/domNode/domNode.css                     (resources/skin/modern/viewers/domNode/domNode.css)
   skin/modern/inspector/viewers/styleRules/styleRules.css               (resources/skin/modern/viewers/styleRules/styleRules.css)
   skin/modern/inspector/viewers/xblBindings/xblBindings.css             (resources/skin/modern/viewers/xblBindings/xblBindings.css)
+% resource inspector modules/
diff --git a/suite/extensions/inspector/modules/InspectElement.jsm b/suite/extensions/inspector/modules/InspectElement.jsm
new file mode 100644
--- /dev/null
+++ b/suite/extensions/inspector/modules/InspectElement.jsm
@@ -0,0 +1,106 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["InspectElement"];
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var InspectElement = {
+  handleEvent: function(e) {
+    // Shift + right click
+    if (!e.shiftKey || e.button != 2) {
+      return;
+    }
+
+    try {
+      e.stopPropagation();
+      e.preventDefault();
+    } catch (ex) {}
+
+    if (e.type != "click") {
+      return;
+    }
+
+    let elem = e.originalTarget;
+    let shadowElem = e.target;
+    let win = e.currentTarget;
+    this.inspect(win, elem, shadowElem);
+  },
+  inspect: function(win, elem, shadowElem) {
+    win.openDialog("chrome://inspector/content/", "_blank",
+                   "chrome, all, dialog=no", elem);
+    this.closePopup(elem, win);
+  },
+  closePopup: function (elem, win) {
+    var parent = elem.parentNode;
+    var list = [];
+
+    while (parent != win && parent != null) {
+      if (parent.localName == "menupopup" || parent.localName == "popup") {
+        list.push(parent);
+      }
+      parent = parent.parentNode;
+    }
+
+    var len = list.length;
+
+    if (!len) {
+      return;
+    }
+
+    list[len - 1].hidePopup();
+  },
+  aListener: {
+    onOpenWindow: function (aWindow) {
+      var win = aWindow.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDOMWindow);
+
+      win.addEventListener("load", function _inspect() {
+        this.removeEventListener("load", _inspect, false);
+        win.addEventListener("click", InspectElement, true);
+
+        if (Services.appinfo.OS == "WINNT") {
+          return;
+        }
+
+        win.addEventListener("mouseup", InspectElement, false);
+        win.addEventListener("contextmenu", InspectElement, true);
+      }, false);
+    },
+    onCloseWindow: function (aWindow) {},
+    onWindowTitleChange: function (aWindow, aTitle) {},
+  },
+  init: function() {
+     Services.wm.addListener(this.aListener);
+    var cw =  Services.ww.getWindowEnumerator();
+
+    while (cw.hasMoreElements()) {
+      var win = cw.getNext().QueryInterface(Ci.nsIDOMWindow);
+      win.addEventListener("click", InspectElement, true);
+
+      if (Services.appinfo.OS == "WINNT") {
+        continue;
+      }
+
+      win.addEventListener("mouseup", InspectElement, false);
+      win.addEventListener("contextmenu", InspectElement, true);
+    }
+  },
+  uninit: function() {
+    Services.wm.removeListener(this.aListener);
+    var cw =  Services.ww.getWindowEnumerator();
+
+    while (cw.hasMoreElements()) {
+      var win = cw.getNext().QueryInterface(Ci.nsIDOMWindow);
+      win.removeEventListener("click", InspectElement, true);
+
+      if (Services.appinfo.OS == "WINNT") {
+        continue;
+      }
+
+      win.removeEventListener("mouseup", InspectElement, false);
+      win.removeEventListener("contextmenu", InspectElement, true);
+    }
+  }
+}
\ No newline at end of file
diff --git a/suite/extensions/inspector/moz.build b/suite/extensions/inspector/moz.build
--- a/suite/extensions/inspector/moz.build
+++ b/suite/extensions/inspector/moz.build
@@ -16,13 +16,16 @@ USE_EXTENSION_MANIFEST = True
 JAR_MANIFESTS += ['jar.mn']
 
 JS_PREFERENCE_FILES += [
     'resources/content/prefs/inspector.js',
 ]
 
 EXTRA_COMPONENTS += [
     'base/js/inspector-cmdline.js',
+    'base/js/inspector-startup.js',
 ]
 
+EXTRA_JS_MODULES += ['modules/InspectElement.jsm']
+
 FINAL_TARGET_PP_FILES += [
     'install.rdf',
 ]
diff --git a/suite/extensions/inspector/resources/content/prefs/inspector.js b/suite/extensions/inspector/resources/content/prefs/inspector.js
--- a/suite/extensions/inspector/resources/content/prefs/inspector.js
+++ b/suite/extensions/inspector/resources/content/prefs/inspector.js
@@ -7,8 +7,12 @@ pref("inspector.blink.border-width", 2);
 pref("inspector.blink.duration", 1200);
 pref("inspector.blink.on", true);
 pref("inspector.blink.speed", 100);
 pref("inspector.blink.invert", false);
 pref("inspector.dom.showAnon", true);
 pref("inspector.dom.showWhitespaceNodes", true);
 pref("inspector.dom.showAccessibleNodes", false);
 pref("inspector.dom.showProcessingInstructions", true);
+// Enables inspecting any DOM element by shift + right click
+// This is disabled by default and requires a restart to takeRecords
+// effect.
+pref("inspector.inspectElementKeyCombo.enabled", false);
