Bug 1246088 - Safely access contentWindow in iframe and embed; r=bgrins
MozReview-Commit-ID: 6GRsMsJPn5x
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -1,15 +1,16 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
doc_inspector_breadcrumbs.html
doc_inspector_delete-selected-node-01.html
doc_inspector_delete-selected-node-02.html
+ doc_inspector_embed.html
doc_inspector_gcli-inspect-command.html
doc_inspector_highlight_after_transition.html
doc_inspector_highlighter-comments.html
doc_inspector_highlighter-geometry_01.html
doc_inspector_highlighter-geometry_02.html
doc_inspector_highlighter_csstransform.html
doc_inspector_highlighter_dom.html
doc_inspector_highlighter_inline.html
@@ -45,16 +46,17 @@ support-files =
[browser_inspector_highlighter-01.js]
[browser_inspector_highlighter-02.js]
[browser_inspector_highlighter-03.js]
[browser_inspector_highlighter-04.js]
[browser_inspector_highlighter-by-type.js]
[browser_inspector_highlighter-comments.js]
[browser_inspector_highlighter-csstransform_01.js]
[browser_inspector_highlighter-csstransform_02.js]
+[browser_inspector_highlighter-embed.js]
[browser_inspector_highlighter-geometry_01.js]
[browser_inspector_highlighter-geometry_02.js]
[browser_inspector_highlighter-geometry_03.js]
[browser_inspector_highlighter-geometry_04.js]
[browser_inspector_highlighter-geometry_05.js]
[browser_inspector_highlighter-hover_01.js]
[browser_inspector_highlighter-hover_02.js]
[browser_inspector_highlighter-hover_03.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-embed.js
@@ -0,0 +1,30 @@
+/* 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";
+
+// Test that the highlighter can go inside <embed> elements
+
+const TEST_URL = URL_ROOT + "doc_inspector_embed.html";
+
+add_task(function*() {
+ let {inspector} = yield openInspectorForURL(TEST_URL);
+
+ info("Get a node inside the <embed> element and select/highlight it");
+ let body = yield getEmbeddedBody(inspector);
+ yield selectAndHighlightNode(body, inspector);
+
+ let selectedNode = inspector.selection.nodeFront;
+ is(selectedNode.tagName.toLowerCase(), "body", "The selected node is <body>");
+ ok(selectedNode.baseURI.endsWith("doc_inspector_menu.html"),
+ "The selected node is the <body> node inside the <embed> element");
+});
+
+function* getEmbeddedBody({walker}) {
+ let embed = yield walker.querySelector(walker.rootNode, "embed");
+ let {nodes} = yield walker.children(embed);
+ let contentDoc = nodes[0];
+ let body = yield walker.querySelector(contentDoc, "body");
+ return body;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/doc_inspector_embed.html
@@ -0,0 +1,6 @@
+<!doctype html><html><head><meta charset="UTF-8"></head><body>
+<object>
+ <embed src="doc_inspector_menu.html" type="application/html"
+ width="422" height="258"></embed>
+</object>
+</body></html>
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -1,15 +1,15 @@
/* 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 { Ci } = require("chrome");
+const { Ci, Cc } = require("chrome");
const { memoize } = require("sdk/lang/functional");
loader.lazyRequireGetter(this, "setIgnoreLayoutChanges",
"devtools/server/actors/layout", true);
exports.setIgnoreLayoutChanges = (...args) =>
this.setIgnoreLayoutChanges(...args);
/**
@@ -150,18 +150,17 @@ function getFrameOffsets(boundaryWindow,
break;
}
// We are in an iframe.
// We take into account the parent iframe position and its
// offset (borders and padding).
let frameRect = frameElement.getBoundingClientRect();
- let [offsetTop, offsetLeft] =
- getIframeContentOffset(frameElement);
+ let [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
xOffset += frameRect.left + offsetLeft;
yOffset += frameRect.top + offsetTop;
frameWin = getParentWindow(frameWin);
}
return [xOffset * scale, yOffset * scale];
@@ -283,18 +282,17 @@ function getRect(boundaryWindow, aNode,
break;
}
// We are in an iframe.
// We take into account the parent iframe position and its
// offset (borders and padding).
let frameRect = frameElement.getBoundingClientRect();
- let [offsetTop, offsetLeft] =
- getIframeContentOffset(frameElement);
+ let [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
rect.top += frameRect.top + offsetTop;
rect.left += frameRect.left + offsetLeft;
frameWin = getParentWindow(frameWin);
}
return rect;
@@ -358,46 +356,73 @@ function getNodeBounds(boundaryWindow, n
p2: {x: xOffset + width, y: yOffset},
p3: {x: xOffset + width, y: yOffset + height},
p4: {x: xOffset, y: yOffset + height}
};
}
exports.getNodeBounds = getNodeBounds;
/**
- * Returns iframe content offset (iframe border + padding).
+ * Same as doing iframe.contentWindow but works with all types of container
+ * elements that act like frames (e.g. <embed>), where 'contentWindow' isn't a
+ * property that can be accessed.
+ * This uses the inIDeepTreeWalker instead.
+ * @param {DOMNode} aFrame
+ * @return {Window}
+ */
+function safelyGetContentWindow(aFrame) {
+ if (aFrame.contentWindow) {
+ return aFrame.contentWindow;
+ }
+
+ let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(Ci.inIDeepTreeWalker);
+ walker.showSubDocuments = true;
+ walker.showDocumentsAsNodes = true;
+ walker.init(aFrame, Ci.nsIDOMNodeFilter.SHOW_ALL);
+ walker.currentNode = aFrame;
+
+ let document = walker.nextNode();
+ if (!document || !document.defaultView) {
+ throw new Error("Couldn't get the content window inside aFrame " + aFrame);
+ }
+
+ return document.defaultView;
+}
+
+/**
+ * Returns a frame's content offset (frame border + padding).
* Note: this function shouldn't need to exist, had the platform provided a
- * suitable API for determining the offset between the iframe's content and
+ * suitable API for determining the offset between the frame's content and
* its bounding client rect. Bug 626359 should provide us with such an API.
*
- * @param {DOMNode} aIframe
- * The iframe.
+ * @param {DOMNode} aFrame
+ * The frame.
* @return {Array} [offsetTop, offsetLeft]
- * offsetTop is the distance from the top of the iframe and the top of
+ * offsetTop is the distance from the top of the frame and the top of
* the content document.
- * offsetLeft is the distance from the left of the iframe and the left
+ * offsetLeft is the distance from the left of the frame and the left
* of the content document.
*/
-function getIframeContentOffset(aIframe) {
- let style = aIframe.contentWindow.getComputedStyle(aIframe, null);
+function getFrameContentOffset(aFrame) {
+ let style = safelyGetContentWindow(aFrame).getComputedStyle(aFrame, null);
// In some cases, the computed style is null
if (!style) {
return [0, 0];
}
let paddingTop = parseInt(style.getPropertyValue("padding-top"));
let paddingLeft = parseInt(style.getPropertyValue("padding-left"));
let borderTop = parseInt(style.getPropertyValue("border-top-width"));
let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
return [borderTop + paddingTop, borderLeft + paddingLeft];
}
-exports.getIframeContentOffset = getIframeContentOffset;
/**
* Find an element from the given coordinates. This method descends through
* frames to find the element the user clicked inside frames.
*
* @param {DOMDocument} aDocument
* The document to look into.
* @param {Number} aX
@@ -407,24 +432,24 @@ exports.getIframeContentOffset = getIfra
* was found
*/
function getElementFromPoint(aDocument, aX, aY) {
let node = aDocument.elementFromPoint(aX, aY);
if (node && node.contentDocument) {
if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
let rect = node.getBoundingClientRect();
- // Gap between the iframe and its content window.
- let [offsetTop, offsetLeft] = getIframeContentOffset(node);
+ // Gap between the frame and its content window.
+ let [offsetTop, offsetLeft] = getFrameContentOffset(node);
aX -= rect.left + offsetLeft;
aY -= rect.top + offsetTop;
if (aX < 0 || aY < 0) {
- // Didn't reach the content document, still over the iframe.
+ // Didn't reach the content document, still over the frame.
return node;
}
}
if (node instanceof Ci.nsIDOMHTMLIFrameElement ||
node instanceof Ci.nsIDOMHTMLFrameElement) {
let subnode = getElementFromPoint(node.contentDocument, aX, aY);
if (subnode) {
node = subnode;