Bug 1036324 - Adds option to walker.parents() to not traverse DocShellTreeItems of different types

This option helps avoiding blank inspector situations when trying to use the right-click ctx
menu in the browser to inspect an element *while* the page is reloading.
This commit is contained in:
Brian Grinstead 2015-04-15 13:49:00 +02:00
Родитель cf897f2347
Коммит 05d8be44f6
7 изменённых файлов: 194 добавлений и 11 удалений

Просмотреть файл

@ -2146,7 +2146,7 @@ MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
this.tooltipData.data = promise.resolve(res);
});
}, () => {
this.tooltipData.data = promise.reject();
this.tooltipData.data = promise.resolve({});
});
}
},
@ -2166,9 +2166,11 @@ MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
}
return this.tooltipData.data.then(({data, size}) => {
tooltip.setImageContent(data, size);
}, () => {
tooltip.setBrokenImageContent();
if (data && size) {
tooltip.setImageContent(data, size);
} else {
tooltip.setBrokenImageContent();
}
});
},

Просмотреть файл

@ -68,6 +68,7 @@ skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
[browser_markupview_events_jquery_2.1.1.js]
skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
[browser_markupview_load_01.js]
[browser_markupview_html_edit_01.js]
[browser_markupview_html_edit_02.js]
[browser_markupview_html_edit_03.js]

Просмотреть файл

@ -0,0 +1,70 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that selecting an element with the 'Inspect Element' context
// menu during a page reload doesn't cause the markup view to become empty.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1036324
const server = createTestHTTPServer();
// Register a slow image handler so we can simulate a long time between
// a reload and the load event firing.
server.registerContentType("gif", "image/gif");
server.registerPathHandler("/slow.gif", function (metadata, response) {
info ("Image has been requested");
response.processAsync();
setTimeout(() => {
info ("Image is responding");
response.finish();
}, 500);
});
// Test page load events.
const TEST_URL = "data:text/html," +
"<!DOCTYPE html>" +
"<head><meta charset='utf-8' /></head>" +
"<body>" +
"<p>Slow script</p>" +
"<img src='http://localhost:" + server.identity.primaryPort + "/slow.gif' /></script>" +
"</body>" +
"</html>";
add_task(function*() {
let tab = yield addTab(TEST_URL);
let {inspector} = yield openInspector();
let domContentLoaded = waitForLinkedBrowserEvent(tab, "DOMContentLoaded");
let pageLoaded = waitForLinkedBrowserEvent(tab, "load");
ok (inspector.markup, "There is a markup view");
// Select an element while the tab is in the middle of a slow reload.
reloadTab();
yield domContentLoaded;
yield chooseWithInspectElementContextMenu("img");
yield pageLoaded;
yield inspector.once("markuploaded");
ok (inspector.markup, "There is a markup view");
is (inspector.markup._elt.children.length, 1, "The markup view is rendering");
});
function* chooseWithInspectElementContextMenu(selector) {
yield executeInContent("Test:SynthesizeMouse", {
center: true,
selector: selector,
options: {type: "contextmenu", button: 2}
});
executeInContent("Test:SynthesizeKey", {key: "Q", options: {}});
}
function waitForLinkedBrowserEvent(tab, event) {
let def = promise.defer();
tab.linkedBrowser.addEventListener(event, function cb() {
tab.linkedBrowser.removeEventListener(event, cb, true);
def.resolve();
}, true);
return def.promise;
}

Просмотреть файл

@ -172,6 +172,13 @@ function executeInContent(name, data={}, objects={}, expectResponse=true) {
}
}
/**
* Reload the current tab location.
*/
function reloadTab() {
return executeInContent("devtools:test:reload", {}, {}, false);
}
/**
* Simple DOM node accesor function that takes either a node or a string css
* selector as argument and returns the corresponding node
@ -647,3 +654,34 @@ function* waitForMultipleChildrenUpdates(inspector) {
return yield waitForMultipleChildrenUpdates(inspector);
}
}
/**
* Create an HTTP server that can be used to simulate custom requests within
* a test. It is automatically cleaned up when the test ends, so no need to
* call `destroy`.
*
* See https://developer.mozilla.org/en-US/docs/Httpd.js/HTTP_server_for_unit_tests
* for more information about how to register handlers.
*
* The server can be accessed like:
*
* const server = createTestHTTPServer();
* let url = "http://localhost: " + server.identity.primaryPort + "/path";
*
* @returns {HttpServer}
*/
function createTestHTTPServer() {
const {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
let server = new HttpServer();
registerCleanupFunction(function* cleanup() {
let destroyed = promise.defer();
server.stop(() => {
destroyed.resolve();
});
yield destroyed.promise;
});
server.start(-1);
return server;
}

Просмотреть файл

@ -3,11 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
devtools.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
devtools.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm", "Task");
const loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
let EventUtils = {};
loader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
addMessageListener("devtools:test:history", function ({ data }) {
content.history[data.direction]();
@ -189,6 +192,55 @@ addMessageListener("devtools:test:setAttribute", function(msg) {
sendAsyncMessage("devtools:test:setAttribute");
});
/**
* Synthesize a mouse event on an element. This handler doesn't send a message
* back. Consumers should listen to specific events on the inspector/highlighter
* to know when the event got synthesized.
* @param {Object} msg The msg.data part expects the following properties:
* - {Number} x
* - {Number} y
* - {Boolean} center If set to true, x/y will be ignored and
* synthesizeMouseAtCenter will be used instead
* - {Object} options Other event options
* - {String} selector An optional selector that will be used to find the node to
* synthesize the event on, if msg.objects doesn't contain the CPOW.
* The msg.objects part should be the element.
* @param {Object} data Event detail properties:
*/
addMessageListener("Test:SynthesizeMouse", function(msg) {
let {x, y, center, options, selector} = msg.data;
let {node} = msg.objects;
if (!node && selector) {
node = superQuerySelector(selector);
}
if (center) {
EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView);
} else {
EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView);
}
// Most consumers won't need to listen to this message, unless they want to
// wait for the mouse event to be synthesized and don't have another event
// to listen to instead.
sendAsyncMessage("Test:SynthesizeMouse");
});
/**
* Synthesize a key event for an element. This handler doesn't send a message
* back. Consumers should listen to specific events on the inspector/highlighter
* to know when the event got synthesized.
* @param {Object} msg The msg.data part expects the following properties:
* - {String} key
* - {Object} options
*/
addMessageListener("Test:SynthesizeKey", function(msg) {
let {key, options} = msg.data;
EventUtils.synthesizeKey(key, options, content);
});
/**
* Like document.querySelector but can go into iframes too.
* ".container iframe || .sub-container div" will first try to find the node

Просмотреть файл

@ -1410,6 +1410,8 @@ var WalkerActor = protocol.ActorClass({
* Named options, including:
* `sameDocument`: If true, parents will be restricted to the same
* document as the node.
* `sameTypeRootTreeItem`: If true, this will not traverse across
* different types of docshells.
*/
parents: method(function(node, options={}) {
if (isNodeDead(node)) {
@ -1420,16 +1422,23 @@ var WalkerActor = protocol.ActorClass({
let parents = [];
let cur;
while((cur = walker.parentNode())) {
if (options.sameDocument && cur.ownerDocument != node.rawNode.ownerDocument) {
if (options.sameDocument && nodeDocument(cur) != nodeDocument(node.rawNode)) {
break;
}
if (options.sameTypeRootTreeItem &&
nodeDocshell(cur).sameTypeRootTreeItem != nodeDocshell(node.rawNode).sameTypeRootTreeItem) {
break;
}
parents.push(this._ref(cur));
}
return parents;
}, {
request: {
node: Arg(0, "domnode"),
sameDocument: Option(1)
sameDocument: Option(1),
sameTypeRootTreeItem: Option(1)
},
response: {
nodes: RetVal("array:domnode")
@ -3226,7 +3235,7 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
let nodeType = types.getType("domnode");
let returnNode = nodeType.read(nodeType.write(nodeActor, walkerActor), this);
let top = returnNode;
let extras = walkerActor.parents(nodeActor);
let extras = walkerActor.parents(nodeActor, {sameTypeRootTreeItem: true});
for (let extraActor of extras) {
top = nodeType.read(nodeType.write(extraActor, walkerActor), this);
}
@ -3519,6 +3528,16 @@ function nodeDocument(node) {
return node.ownerDocument || (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
}
function nodeDocshell(node) {
let doc = node ? nodeDocument(node) : null;
let win = doc ? doc.defaultView : null;
if (win) {
return win.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDocShell);
}
}
function isNodeDead(node) {
return !node || !node.rawNode || Cu.isDeadWrapper(node.rawNode);
}

Просмотреть файл

@ -1020,7 +1020,8 @@ var StyleRuleActor = protocol.ActorClass({
// Elements don't have a parent stylesheet, and therefore
// don't have an associated URI. Provide a URI for
// those.
form.href = this.rawNode.ownerDocument.location.href;
let doc = this.rawNode.ownerDocument;
form.href = doc.location ? doc.location.href : "";
form.cssText = this.rawStyle.cssText || "";
break;
case Ci.nsIDOMCSSRule.CHARSET_RULE:
@ -1231,7 +1232,7 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
return this._form.href;
}
let sheet = this.parentStyleSheet;
return sheet.href;
return sheet ? sheet.href : "";
},
get nodeHref() {