Merge fx-team to m-c.

This commit is contained in:
Joe Walker 2013-03-08 16:47:34 +00:00
commit 7bcffdd7d4
54 changed files with 1332 additions and 426 deletions

View File

@ -1146,6 +1146,9 @@ pref("devtools.editor.expandtab", true);
// indenting and bracket recognition.
pref("devtools.editor.component", "orion");
// Enable the Font Inspector
pref("devtools.fontinspector.enabled", true);
// Whether the character encoding menu is under the main Firefox button. This
// preference is a string so that localizers can alter it.
pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");

View File

@ -7022,8 +7022,8 @@ define("text!gcli/ui/intro.html", [], "\n" +
" <p>\n" +
" ${l10n.introTextCommands}\n" +
" <span class=\"gcli-out-shortcut\" onclick=\"${onclick}\"\n" +
" ondblclick=\"${ondblclick}\" data-command=\"help\">help</span>\n" +
" ${l10n.introTextKeys2} <code>${l10n.introTextF1Escape}</code>.\n" +
" ondblclick=\"${ondblclick}\" data-command=\"help\">help</span>${l10n.introTextKeys2}\n" +
" <code>${l10n.introTextF1Escape}</code>.\n" +
" </p>\n" +
"\n" +
" <button onclick=\"${onGotIt}\" if=\"${showHideButton}\">${l10n.introTextGo}</button>\n" +

View File

@ -47,13 +47,21 @@ function testCommands(dbg, cmd) {
is(output.value, "dbg continue", "debugger continued");
DeveloperToolbarTest.exec({
typed: "dbg close",
completed: false,
blankOutput: true
});
let target = TargetFactory.forTab(gBrowser.selectedTab);
ok(!gDevTools.getToolbox(target),
"Debugger was closed.");
finish();
let toolbox = gDevTools.getToolbox(target);
if (!toolbox) {
ok(true, "Debugger was closed.");
finish();
} else {
toolbox.on("destroyed", function () {
ok(true, "Debugger was closed.");
finish();
});
}
});
});
});

View File

@ -10,9 +10,11 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
this.EXPORTED_SYMBOLS = ["DebuggerPanel"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
@ -37,13 +39,6 @@ DebuggerPanel.prototype = {
this._ensureOnlyOneRunningDebugger();
if (!this.target.isRemote) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
}
let onDebuggerLoaded = function () {
this.panelWin.removeEventListener("Debugger:Loaded",
onDebuggerLoaded, true);
@ -62,7 +57,16 @@ DebuggerPanel.prototype = {
this.panelWin.addEventListener("Debugger:Connected",
onDebuggerConnected, true);
return deferred.promise;
// Remote debugging gets the debuggee from a RemoteTarget object.
if (this.target.isRemote) {
this.panelWin._remoteFlag = true;
return deferred.promise;
}
// Local debugging needs to convert the TabTarget to a RemoteTarget.
return this.target.makeRemote().then(function success() {
return deferred.promise;
});
},
// DevToolPanel API
@ -71,6 +75,7 @@ DebuggerPanel.prototype = {
get isReady() this._isReady,
destroy: function() {
this.emit("destroyed");
return Promise.resolve(null);
},

View File

@ -99,7 +99,7 @@ let DebuggerController = {
/**
* Prepares the hostname and port number for a remote debugger connection
* and handles connection retries and timeouts.
*
* XXX: remove all this (bug 823577)
* @return boolean
* True if connection should proceed normally, false otherwise.
*/
@ -165,15 +165,11 @@ let DebuggerController = {
window.dispatchEvent("Debugger:Connected");
}
let client;
// Remote debugging gets the debuggee from a RemoteTarget object.
if (this._target && this._target.isRemote) {
window._isRemoteDebugger = true;
client = this.client = this._target.client;
if (!window._isChromeDebugger) {
let client = this.client = this._target.client;
this._target.on("close", this._onTabDetached);
this._target.on("navigate", this._onTabNavigated);
this._target.on("will-navigate", this._onTabNavigated);
if (this._target.chrome) {
let dbg = this._target.form.chromeDebugger;
@ -184,25 +180,16 @@ let DebuggerController = {
return;
}
// Content or chrome debugging can connect directly to the debuggee.
// TODO: convert this to use a TabTarget.
let transport = window._isChromeDebugger
? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort)
: DebuggerServer.connectPipe();
// Chrome debugging needs to make the connection to the debuggee.
let transport = debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort);
client = this.client = new DebuggerClient(transport);
let client = this.client = new DebuggerClient(transport);
client.addListener("tabNavigated", this._onTabNavigated);
client.addListener("tabDetached", this._onTabDetached);
client.connect(function(aType, aTraits) {
client.listTabs(function(aResponse) {
if (window._isChromeDebugger) {
let dbg = aResponse.chromeDebugger;
this._startChromeDebugging(client, dbg, callback);
} else {
let tab = aResponse.tabs[aResponse.selected];
this._startDebuggingTab(client, tab, callback);
}
this._startChromeDebugging(client, aResponse.chromeDebugger, callback);
}.bind(this));
}.bind(this));
},
@ -218,8 +205,9 @@ let DebuggerController = {
this.client.removeListener("tabNavigated", this._onTabNavigated);
this.client.removeListener("tabDetached", this._onTabDetached);
// When remote debugging, the connection is closed by the RemoteTarget.
if (!window._isRemoteDebugger) {
// When debugging content or a remote instance, the connection is closed by
// the RemoteTarget.
if (window._isChromeDebugger) {
this.client.close();
}

View File

@ -0,0 +1,13 @@
#
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,16 @@
/* 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/. */
.dim > #root,
.font:not(.has-code) .font-css-code,
.font-is-local,
.font-is-remote,
.font.is-local .font-format-url {
display: none;
}
.font.is-remote .font-is-remote,
.font.is-local .font-is-local {
display: inline;
}

View File

@ -0,0 +1,226 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ft=javascript 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
function FontInspector(inspector, window)
{
this.inspector = inspector;
this.chromeDoc = window.document;
this.init();
}
FontInspector.prototype = {
init: function FI_init() {
this.update = this.update.bind(this);
this.onNewNode = this.onNewNode.bind(this);
this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
this.inspector.selection.on("new-node", this.onNewNode);
this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this.onHighlighterLocked);
}
this.update();
},
/**
* Is the fontinspector visible in the sidebar?
*/
isActive: function FI_isActive() {
return this.inspector.sidebar &&
this.inspector.sidebar.getCurrentTabID() == "fontinspector";
},
/**
* Remove listeners.
*/
destroy: function FI_destroy() {
this.chromeDoc = null;
this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
this.inspector.selection.off("new-node", this.onNewNode);
if (this.inspector.highlighter) {
this.inspector.highlighter.off("locked", this.onHighlighterLocked);
}
},
/**
* Selection 'new-node' event handler.
*/
onNewNode: function FI_onNewNode() {
if (this.isActive() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode() &&
this.inspector.selection.reason != "highlighter") {
this.undim();
this.update();
} else {
this.dim();
}
},
/**
* Highlighter 'locked' event handler
*/
onHighlighterLocked: function FI_onHighlighterLocked() {
this.undim();
this.update();
},
/**
* Hide the font list. No node are selected.
*/
dim: function FI_dim() {
this.chromeDoc.body.classList.add("dim");
this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
},
/**
* Show the font list. A node is selected.
*/
undim: function FI_undim() {
this.chromeDoc.body.classList.remove("dim");
},
/**
* Retrieve all the font related info we have for the selected
* node and display them.
*/
update: function FI_update() {
if (!this.isActive() ||
!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode() ||
this.chromeDoc.body.classList.contains("dim")) {
return;
}
let node = this.inspector.selection.node;
let contentDocument = node.ownerDocument;
// We don't get fonts for a node, but for a range
let rng = contentDocument.createRange();
rng.selectNode(node);
let fonts = DOMUtils.getUsedFontFaces(rng);
let fontsArray = [];
for (let i = 0; i < fonts.length; i++) {
fontsArray.push(fonts.item(i));
}
fontsArray = fontsArray.sort(function(a, b) {
return a.srcIndex < b.srcIndex;
});
this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
for (let f of fontsArray) {
this.render(f, contentDocument);
}
},
/**
* Display the information of one font.
*/
render: function FI_render(font, document) {
let s = this.chromeDoc.querySelector("#template > section");
s = s.cloneNode(true);
s.querySelector(".font-name").textContent = font.name;
s.querySelector(".font-css-name").textContent = font.CSSFamilyName;
s.querySelector(".font-format").textContent = font.format;
if (font.srcIndex == -1) {
s.classList.add("is-local");
} else {
s.classList.add("is-remote");
}
s.querySelector(".font-url").value = font.URI;
let iframe = s.querySelector(".font-preview");
if (font.rule) {
// This is the @font-face{…} code.
let cssText = font.rule.style.parentRule.cssText;
s.classList.add("has-code");
s.querySelector(".font-css-code").textContent = cssText;
// We guess the base URL of the stylesheet to make
// sure the font will be accessible in the preview.
// If the font-face is in an inline <style>, we get
// the location of the page.
let origin = font.rule.style.parentRule.parentStyleSheet.href;
if (!origin) { // Inline stylesheet
origin = document.location.href;
}
// We remove the last part of the URL to get a correct base.
let base = origin.replace(/\/[^\/]*$/,"/")
// From all this information, we build a preview.
this.buildPreview(iframe, font.CSSFamilyName, cssText, base);
} else {
this.buildPreview(iframe, font.CSSFamilyName, "", "");
}
this.chromeDoc.querySelector("#all-fonts").appendChild(s);
},
/**
* Show a preview of the font in an iframe.
*/
buildPreview: function FI_buildPreview(iframe, name, cssCode, base) {
/* The HTML code of the preview is:
* <!DOCTYPE HTML>
* <head>
* <base href="{base}"></base>
* </head>
* <style>
* p {font-family: {name};}
* * {font-size: 40px;line-height:60px;padding:0 10px;margin:0};
* </style>
* <p contenteditable>Abc</p>
*/
let extraCSS = "* {padding:0;margin:0}";
extraCSS += "p {font-family: '" + name + "';}";
extraCSS += "p {font-size: 40px;line-height:60px;padding:0 10px;margin:0;}";
cssCode += extraCSS;
let src = "data:text/html;charset=utf-8,<!DOCTYPE HTML><head><base></base></head><style></style><p contenteditable>Abc</p>";
iframe.addEventListener("load", function onload() {
iframe.removeEventListener("load", onload, true);
let doc = iframe.contentWindow.document;
// We could have done that earlier, but we want to avoid any URL-encoding
// nightmare.
doc.querySelector("base").href = base;
doc.querySelector("style").textContent = cssCode;
}, true);
iframe.src = src;
},
/**
* Select the <body> to show all the fonts included in the document.
*/
showAll: function FI_showAll() {
if (!this.isActive() ||
!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode()) {
return;
}
let node = this.inspector.selection.node;
let contentDocument = node.ownerDocument;
let root = contentDocument.documentElement;
if (contentDocument.body) {
root = contentDocument.body;
}
this.inspector.selection.setNode(root, "fontinspector");
},
}
window.setPanel = function(panel) {
window.fontInspector = new FontInspector(panel, window);
}
window.onunload = function() {
window.fontInspector.destroy();
}

View File

@ -0,0 +1,41 @@
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % fontinspectorDTD SYSTEM "chrome://browser/locale/devtools/font-inspector.dtd" >
%fontinspectorDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&title;</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="font-inspector.css" type="text/css"/>
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://browser/skin/devtools/font-inspector.css" type="text/css"/>
</head>
<body class="devtools-monospace" role="application">
<script type="application/javascript;version=1.8" src="font-inspector.js"></script>
<div id="root">
<ul id="all-fonts"></ul>
<button id="showall" onclick="fontInspector.showAll()">&showAllFonts;</button>
</div>
<div id="template" style="display:none">
<section class="font">
<iframe sandbox="allow-same-origin" class="font-preview"></iframe>
<div class="font-info">
<h1 class="font-name"></h1>
<span class="font-is-local">&system;</span>
<span class="font-is-remote">&remote;</span>
<p class="font-format-url">
<input readonly="readonly" class="font-url"></input>
(<span class="font-format"></span>)
</p>
<p class="font-css">&usedAs; "<span class="font-css-name"></span>"</p>
<pre class="font-css-code"></pre>
</div>
</section>
</div>
</body>
</html>

View File

@ -0,0 +1,6 @@
# vim: set filetype=python:
# 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/.
TEST_DIRS += ['test']

View File

@ -0,0 +1,22 @@
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_BROWSER_FILES = \
browser_fontinspector.js \
browser_fontinspector.html \
browser_font.woff \
$(NULL)
libs:: $(_BROWSER_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)

Binary file not shown.

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: bar;
src: url(bad/font/name.ttf), url(browser_font.woff) format("woff");
}
body{
font-family:Arial;
}
div {
font-family:Arial;
font-family:bar;
}
</style>
<body>
BODY
<div>DIV</div>
</body>

View File

@ -0,0 +1,89 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let tempScope = {};
Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
let TargetFactory = tempScope.TargetFactory;
function test() {
waitForExplicitFinish();
let doc;
let node;
let view;
let inspector;
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
doc = content.document;
waitForFocus(setupTest, content);
}, true);
content.location = "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_fontinspector.html";
function setupTest() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
openFontInspector(toolbox.getCurrentPanel());
});
}
function openFontInspector(aInspector) {
inspector = aInspector;
info("Inspector open");
inspector.selection.setNode(doc.body);
inspector.sidebar.select("fontinspector");
inspector.sidebar.once("fontinspector-ready", viewReady);
}
function viewReady() {
info("Font Inspector ready");
view = inspector.sidebar.getWindowForTab("fontinspector");
ok(!!view.fontInspector, "Font inspector document is alive.");
let d = view.document;
let s = d.querySelectorAll("#all-fonts > section");
is(s.length, 2, "Found 2 fonts");
is(s[0].querySelector(".font-name").textContent,
"DeLarge Bold", "font 0: Right font name");
ok(s[0].classList.contains("is-remote"),
"font 0: is remote");
is(s[0].querySelector(".font-url").value,
"http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_font.woff",
"font 0: right url");
is(s[0].querySelector(".font-format").textContent,
"woff", "font 0: right font format");
is(s[0].querySelector(".font-css-name").textContent,
"bar", "font 0: right css name");
let font1Name = s[1].querySelector(".font-name").textContent;
let font1CssName = s[1].querySelector(".font-css-name").textContent;
// On Linux test machines, the Arial font doesn't exist.
// The fallback is "Liberation Sans"
ok((font1Name == "Arial") || (font1Name == "Liberation Sans"),
"font 1: Right font name");
ok(s[1].classList.contains("is-local"), "font 1: is local");
ok((font1CssName == "Arial") || (font1CssName == "Liberation Sans"),
"Arial", "font 1: right css name");
executeSoon(function() {
gDevTools.once("toolbox-destroyed", finishUp);
inspector._toolbox.destroy();
});
}
function finishUp() {
gBrowser.removeCurrentTab();
finish();
}
}

View File

@ -0,0 +1,5 @@
# vim: set filetype=python:
# 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/.

View File

@ -12,6 +12,11 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
const targets = new WeakMap();
@ -21,8 +26,10 @@ const targets = new WeakMap();
this.TargetFactory = {
/**
* Construct a Target
* @param {XULTab} tab
* The tab to use in creating a new target
* @param {XULTab} | {Object} tab
* The tab to use in creating a new target, or an options object in
* case of remote targets.
*
* @return A target object
*/
forTab: function TF_forTab(tab) {
@ -61,27 +68,7 @@ this.TargetFactory = {
},
/**
* Construct a Target for a remote global
* @param {Object} form
* The serialized form of a debugging protocol actor.
* @param {DebuggerClient} client
* The debuger client instance to communicate with the server.
* @param {boolean} chrome
* A flag denoting that the debugging target is the remote process as a
* whole and not a single tab.
* @return A target object
*/
forRemote: function TF_forRemote(form, client, chrome) {
let target = targets.get(form);
if (target == null) {
target = new RemoteTarget(form, client, chrome);
targets.set(form, target);
}
return target;
},
/**
* Get all of the targets known to some browser instance (local if null)
* Get all of the targets known to the local browser instance
* @return An array of target objects
*/
allTargets: function TF_allTargets() {
@ -171,8 +158,16 @@ Object.defineProperty(Target.prototype, "version", {
*/
function TabTarget(tab) {
EventEmitter.decorate(this);
this._tab = tab;
this._setupListeners();
this.destroy = this.destroy.bind(this);
this._handleThreadState = this._handleThreadState.bind(this);
this.on("thread-resumed", this._handleThreadState);
this.on("thread-paused", this._handleThreadState);
// Only real tabs need initialization here. Placeholder objects for remote
// targets will be initialized after a makeRemote method call.
if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
this._tab = tab;
this._setupListeners();
}
}
TabTarget.prototype = {
@ -185,30 +180,99 @@ TabTarget.prototype = {
return this._tab;
},
get form() {
return this._form;
},
get client() {
return this._client;
},
get chrome() {
return this._chrome;
},
get window() {
return this._tab.linkedBrowser.contentWindow;
// Be extra careful here, since this may be called by HS_getHudByWindow
// during shutdown.
if (this._tab && this._tab.linkedBrowser) {
return this._tab.linkedBrowser.contentWindow;
}
},
get name() {
return this._tab.linkedBrowser.contentDocument.title;
return this._tab ? this._tab.linkedBrowser.contentDocument.title :
this._form.title;
},
get url() {
return this._tab.linkedBrowser.contentDocument.location.href;
return this._tab ? this._tab.linkedBrowser.contentDocument.location.href :
this._form.url;
},
get isRemote() {
return false;
return !this.isLocalTab;
},
get isLocalTab() {
return true;
return !!this._tab;
},
get isThreadPaused() {
return !!this._isThreadPaused;
},
/**
* Adds remote protocol capabilities to the target, so that it can be used
* for tools that support the Remote Debugging Protocol even for local
* connections.
*
* @param object aOptions
* An optional object containing remote connection options that is
* supplied when connecting to another instance.
*/
makeRemote: function TabTarget_makeRemote(aOptions) {
if (this._remote) {
return this._remote.promise;
}
this._remote = Promise.defer();
if (aOptions) {
this._form = aOptions.form;
this._client = aOptions.client;
this._chrome = aOptions.chrome;
} else {
// Since a remote protocol connection will be made, let's start the
// DebuggerServer here, once and for all tools.
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
this._client = new DebuggerClient(DebuggerServer.connectPipe());
// A local TabTarget will never perform chrome debugging.
this._chrome = false;
}
this._setupRemoteListeners();
if (aOptions) {
// In the remote debugging case, the protocol connection will have been
// already initialized in the connection screen code.
this._remote.resolve(null);
} else {
this._client.connect(function(aType, aTraits) {
this._client.listTabs(function(aResponse) {
this._form = aResponse.tabs[aResponse.selected];
this._remote.resolve(null);
}.bind(this));
}.bind(this));
}
return this._remote.promise;
},
/**
* Listen to the different events.
*/
@ -218,9 +282,27 @@ TabTarget.prototype = {
this.tab.addEventListener("TabClose", this);
this.tab.parentNode.addEventListener("TabSelect", this);
this.tab.ownerDocument.defaultView.addEventListener("unload", this);
this._handleThreadState = this._handleThreadState.bind(this);
this.on("thread-resumed", this._handleThreadState);
this.on("thread-paused", this._handleThreadState);
},
/**
* Setup listeners for remote debugging, updating existing ones as necessary.
*/
_setupRemoteListeners: function TabTarget__setupRemoteListeners() {
// Reset any conflicting event handlers that were set before makeRemote().
if (this._webProgressListener) {
this._webProgressListener.destroy();
}
this.client.addListener("tabDetached", this.destroy);
this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
if (aPacket.state == "start") {
this.emit("will-navigate", aPacket);
} else {
this.emit("navigate", aPacket);
}
}.bind(this);
this.client.addListener("tabNavigated", this._onTabNavigated);
},
/**
@ -260,28 +342,66 @@ TabTarget.prototype = {
* Target is not alive anymore.
*/
destroy: function() {
if (!this._destroyed) {
this._destroyed = true;
// If several things call destroy then we give them all the same
// destruction promise so we're sure to destroy only once
if (this._destroyer) {
return this._destroyer.promise;
}
this.tab.linkedBrowser.removeProgressListener(this._webProgressListener)
this._webProgressListener.target = null;
this._webProgressListener = null;
this.tab.ownerDocument.defaultView.removeEventListener("unload", this);
this.tab.removeEventListener("TabClose", this);
this.tab.parentNode.removeEventListener("TabSelect", this);
this.off("thread-resumed", this._handleThreadState);
this.off("thread-paused", this._handleThreadState);
this.emit("close");
this._destroyer = Promise.defer();
// Before taking any action, notify listeners that destruction is imminent.
this.emit("close");
// First of all, do cleanup tasks that pertain to both remoted and
// non-remoted targets.
this.off("thread-resumed", this._handleThreadState);
this.off("thread-paused", this._handleThreadState);
if (this._tab) {
this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
this._tab.removeEventListener("TabClose", this);
this._tab.parentNode.removeEventListener("TabSelect", this);
}
// If this target was not remoted, the promise will be resolved before the
// function returns.
// if (!this._remote) {
if (this._tab && !this._client) {
if (this._webProgressListener) {
this._webProgressListener.destroy();
}
targets.delete(this._tab);
this._tab = null;
this._client = null;
this._form = null;
this._remote = null;
this._destroyer.resolve(null);
} else if (this._client) {
// If, on the other hand, this target was remoted, the promise will be
// resolved after the remote connection is closed.
this.client.removeListener("tabNavigated", this._onTabNavigated);
this.client.removeListener("tabDetached", this.destroy);
this._client.close(function onClosed() {
let key = this._tab ? this._tab : this._form;
targets.delete(key);
this._client = null;
this._tab = null;
this._form = null;
this._remote = null;
this._destroyer.resolve(null);
}.bind(this));
}
return Promise.resolve(null);
return this._destroyer.promise;
},
toString: function() {
return 'TabTarget:' + this.tab;
return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
},
};
@ -322,13 +442,24 @@ TabWebProgressListener.prototype = {
onSecurityChange: function() {},
onStatusChange: function() {},
onLocationChange: function TwPL_onLocationChange(webProgress, request, URI, flags) {
onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
if (this.target &&
!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
let window = webProgress.DOMWindow;
this.target.emit("navigate", window);
}
},
/**
* Destroy the progress listener instance.
*/
destroy: function TWPL_destroy() {
if (this.target.tab) {
this.target.tab.linkedBrowser.removeProgressListener(this);
}
this.target._webProgressListener = null;
this.target = null;
}
};
@ -412,101 +543,3 @@ WindowTarget.prototype = {
return 'WindowTarget:' + this.window;
},
};
/**
* A RemoteTarget represents a page living in a remote Firefox instance.
*/
function RemoteTarget(form, client, chrome) {
EventEmitter.decorate(this);
this._client = client;
this._form = form;
this._chrome = chrome;
this._setupListeners();
}
RemoteTarget.prototype = {
supports: supports,
get version() getVersion(),
get isRemote() true,
get chrome() this._chrome,
get name() this._form.title,
get url() this._form.url,
get client() this._client,
get form() this._form,
get isLocalTab() false,
get isThreadPaused() !!this._isThreadPaused,
/**
* Listen to the different events.
*/
_setupListeners: function() {
this.destroy = this.destroy.bind(this);
this.client.addListener("tabDetached", this.destroy);
this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
if (aPacket.state == "start") {
this.emit("will-navigate", aPacket);
} else {
this.emit("navigate", aPacket);
}
}.bind(this);
this.client.addListener("tabNavigated", this._onTabNavigated);
this._handleThreadState = this._handleThreadState.bind(this);
this.on("thread-resumed", this._handleThreadState);
this.on("thread-paused", this._handleThreadState);
},
/**
* Handle script status.
*/
_handleThreadState: function(event) {
switch (event) {
case "thread-resumed":
this._isThreadPaused = false;
break;
case "thread-paused":
this._isThreadPaused = true;
break;
}
},
/**
* Target is not alive anymore.
*/
destroy: function RT_destroy() {
// If several things call destroy then we give them all the same
// destruction promise so we're sure to destroy only once
if (this._destroyer) {
return this._destroyer.promise;
}
this._destroyer = Promise.defer();
this.client.removeListener("tabNavigated", this._onTabNavigated);
this.client.removeListener("tabDetached", this.destroy);
this._client.close(function onClosed() {
this._client = null;
this.off("thread-resumed", this._handleThreadState);
this.off("thread-paused", this._handleThreadState);
this.emit("close");
this._destroyer.resolve(null);
}.bind(this));
return this._destroyer.promise;
},
toString: function() {
return 'RemoteTarget:' + this.form.actor;
},
};

View File

@ -126,7 +126,7 @@ this.Toolbox = function Toolbox(target, selectedTool, hostType) {
this._toolUnregistered = this._toolUnregistered.bind(this);
this.destroy = this.destroy.bind(this);
this._target.once("close", this.destroy);
this._target.on("close", this.destroy);
if (!hostType) {
hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
@ -432,12 +432,13 @@ Toolbox.prototype = {
* The id of the tool to switch to
*/
selectTool: function TBOX_selectTool(id) {
if (this._currentToolId == id) {
return;
}
let deferred = Promise.defer();
if (this._currentToolId == id) {
// Return the existing panel in order to have a consistent return value.
return Promise.resolve(this._toolPanels.get(id));
}
if (!this.isReady) {
throw new Error("Can't select tool, wait for toolbox 'ready' event");
}
@ -686,13 +687,10 @@ Toolbox.prototype = {
this.off("select", this._refreshHostTitle);
this.off("host-changed", this._refreshHostTitle);
let outstanding = [];
gDevTools.off("tool-registered", this._toolRegistered);
gDevTools.off("tool-unregistered", this._toolUnregistered);
// Remote targets need to be notified that the toolbox is being torn down.
if (this._target && this._target.isRemote) {
outstanding.push(this._target.destroy());
}
this._target = null;
let outstanding = [];
for (let [id, panel] of this._toolPanels) {
outstanding.push(panel.destroy());
@ -700,8 +698,13 @@ Toolbox.prototype = {
outstanding.push(this._host.destroy());
gDevTools.off("tool-registered", this._toolRegistered);
gDevTools.off("tool-unregistered", this._toolUnregistered);
// Targets need to be notified that the toolbox is being torn down, so that
// remote protocol connections can be gracefully terminated.
if (this._target) {
this._target.off("close", this.destroy);
outstanding.push(this._target.destroy());
}
this._target = null;
Promise.all(outstanding).then(function() {
this.emit("destroyed");

View File

@ -164,7 +164,14 @@ function handleConnectionTimeout() {
* Opens the toolbox.
*/
function openToolbox(form, chrome=false) {
let target = TargetFactory.forRemote(form, gClient, chrome);
gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
window.close();
let options = {
form: form,
client: gClient,
chrome: chrome
};
let target = TargetFactory.forTab(options);
target.makeRemote(options).then(function() {
gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
window.close();
});
}

View File

@ -51,7 +51,10 @@ function selectAndCheckById(id) {
function testToggle() {
toolbox.once("destroyed", function() {
gDevTools.showToolbox(target, "styleeditor").then(function() {
// Cannot reuse a target after it's destroyed.
target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "styleeditor").then(function(aToolbox) {
toolbox = aToolbox;
is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
finishUp();
});
@ -61,9 +64,10 @@ function testToggle() {
}
function finishUp() {
toolbox.destroy();
toolbox = null;
target = null;
gBrowser.removeCurrentTab();
finish();
toolbox.destroy().then(function() {
toolbox = null;
target = null;
gBrowser.removeCurrentTab();
finish();
});
}

View File

@ -41,7 +41,9 @@ function onVisible() {
function onWillNavigate(event, request) {
ok(true, "will-navigate event received");
target.once("navigate", onNavigate);
// Wait for navigation handling to complete before removing the tab, in order
// to avoid triggering assertions.
target.once("navigate", executeSoon.bind(null, onNavigate));
}
function onNavigate() {

View File

@ -90,12 +90,14 @@ function testToolSelect()
function testDestroy()
{
toolbox.destroy().then(function() {
target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target).then(testRememberHost);
});
}
function testRememberHost()
function testRememberHost(aToolbox)
{
toolbox = aToolbox;
// last host was the window - make sure it's the same when re-opening
is(toolbox.hostType, Toolbox.HostType.WINDOW, "host remembered");
@ -123,8 +125,9 @@ function cleanup()
{
Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
toolbox.destroy();
DevTools = Toolbox = toolbox = target = null;
gBrowser.removeCurrentTab();
finish();
}
toolbox.destroy().then(function() {
DevTools = Toolbox = toolbox = target = null;
gBrowser.removeCurrentTab();
finish();
});
}

View File

@ -66,9 +66,10 @@ function selectCB(event, id) {
}
function tidyUp() {
toolbox.destroy();
gBrowser.removeCurrentTab();
toolbox.destroy().then(function() {
gBrowser.removeCurrentTab();
toolbox = toolIDs = idIndex = Toolbox = null;
finish();
toolbox = toolIDs = idIndex = Toolbox = null;
finish();
});
}

View File

@ -54,23 +54,32 @@ function test() {
// destroy toolbox, create new one hosted in a window (with a
// different tool id), and check title
.then(function () toolbox.destroy())
.then(function () gDevTools.showToolbox(target, null,
Toolbox.HostType.WINDOW))
.then(function (aToolbox) { toolbox = aToolbox; })
.then(function () toolbox.selectTool(TOOL_ID_1))
.then(checkTitle.bind(null, LABEL_1, URL_2,
"toolbox destroyed and recreated"))
// clean up
.then(function () toolbox.destroy())
.then(function () {
toolbox = null;
gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("devtools.toolbox.host");
Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
finish();
// Give the tools a chance to handle the navigation event before
// destroying the toolbox.
executeSoon(function() {
toolbox.destroy()
.then(function () {
// After destroying the toolbox, a fresh target is required.
target = TargetFactory.forTab(gBrowser.selectedTab);
return gDevTools.showToolbox(target, null, Toolbox.HostType.WINDOW);
})
.then(function (aToolbox) { toolbox = aToolbox; })
.then(function () toolbox.selectTool(TOOL_ID_1))
.then(checkTitle.bind(null, LABEL_1, URL_2,
"toolbox destroyed and recreated"))
// clean up
.then(function () toolbox.destroy())
.then(function () {
toolbox = null;
gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("devtools.toolbox.host");
Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
finish();
});
});
});
});
}

View File

@ -219,6 +219,12 @@ InspectorPanel.prototype = {
"chrome://browser/content/devtools/csshtmltree.xul",
"computedview" == defaultTab);
if (Services.prefs.getBoolPref("devtools.fontinspector.enabled")) {
this.sidebar.addTab("fontinspector",
"chrome://browser/content/devtools/fontinspector/font-inspector.xhtml",
"fontinspector" == defaultTab);
}
this.sidebar.addTab("layoutview",
"chrome://browser/content/devtools/layoutview/view.xhtml",
"layoutview" == defaultTab);
@ -412,6 +418,7 @@ InspectorPanel.prototype = {
this.nodemenu = null;
this.searchBox = null;
this.highlighter = null;
this._searchResults = null;
return Promise.resolve(null);
},

View File

@ -20,7 +20,7 @@ function createDocument()
'solely to provide some things to test the inspector initialization.</p>\n' +
'If you are reading this, you should go do something else instead. Maybe ' +
'read a book. Or better yet, write some test-cases for another bit of code. ' +
'<span style="{font-style: italic}">Maybe more inspector test-cases!</span></p>\n' +
'<span style="{font-style: italic}">Inspector\'s!</span></p>\n' +
'<p id="closing">end transmission</p>\n' +
'</div>';
doc.title = "Inspector Initialization Test";

View File

@ -19,6 +19,9 @@ browser.jar:
content/browser/devtools/layoutview/view.js (layoutview/view.js)
content/browser/devtools/layoutview/view.xhtml (layoutview/view.xhtml)
content/browser/devtools/layoutview/view.css (layoutview/view.css)
content/browser/devtools/fontinspector/font-inspector.js (fontinspector/font-inspector.js)
content/browser/devtools/fontinspector/font-inspector.xhtml (fontinspector/font-inspector.xhtml)
content/browser/devtools/fontinspector/font-inspector.css (fontinspector/font-inspector.css)
content/browser/orion.js (sourceeditor/orion/orion.js)
* content/browser/source-editor-overlay.xul (sourceeditor/source-editor-overlay.xul)
content/browser/debugger.xul (debugger/debugger.xul)

View File

@ -104,7 +104,7 @@ LayoutView.prototype = {
this.browser.removeEventListener("MozAfterPaint", this.update, true);
}
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this.onHighlighterLocked);
this.inspector.highlighter.off("locked", this.onHighlighterLocked);
}
this.sizeHeadingLabel = null;
this.sizeLabel = null;

View File

@ -644,7 +644,7 @@ MarkupView.prototype = {
delete this._boundFocus;
this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
this._frame.contentWindow.removeEventListener("resize", this._boundUpdatePreview, true);
this._frame.contentWindow.removeEventListener("resize", this._boundResizePreview, true);
this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true);
this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true);
delete this._boundUpdatePreview;

View File

@ -19,4 +19,5 @@ DIRS += [
'responsivedesign',
'framework',
'profiler',
'fontinspector',
]

View File

@ -23,19 +23,6 @@ XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
* DebuggerServer.
*/
function ProfilerConnection(client) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
this.isRemote = true;
if (!client) {
let transport = DebuggerServer.connectPipe();
client = new DebuggerClient(transport);
this.isRemote = false;
}
this.client = client;
}
@ -49,22 +36,10 @@ ProfilerConnection.prototype = {
* Function to be called once we're connected to the client.
*/
connect: function PCn_connect(aCallback) {
let client = this.client;
let listTabs = function () {
client.listTabs(function (aResponse) {
this.actor = aResponse.profilerActor;
aCallback();
}.bind(this));
}.bind(this);
if (this.isRemote) {
return void listTabs();
}
client.connect(function (aType, aTraits) {
listTabs();
});
this.client.listTabs(function (aResponse) {
this.actor = aResponse.profilerActor;
aCallback();
}.bind(this));
},
/**
@ -129,9 +104,7 @@ ProfilerConnection.prototype = {
* Cleanup.
*/
destroy: function PCn_destroy() {
this.client.close(function () {
this.client = null;
}.bind(this));
this.client = null;
}
};
@ -139,14 +112,13 @@ ProfilerConnection.prototype = {
* Object defining the profiler controller components.
*/
function ProfilerController(target) {
let client;
if (target.isRemote) {
client = target.client;
this.profiler = new ProfilerConnection(target.client);
// Chrome debugging targets have already obtained a reference to the profiler
// actor.
this._connected = !!target.chrome;
if (target.chrome) {
this.profiler.actor = target.form.profilerActor;
}
this.profiler = new ProfilerConnection(client);
this._connected = false;
}
ProfilerController.prototype = {
@ -160,7 +132,7 @@ ProfilerController.prototype = {
*/
connect: function (aCallback) {
if (this._connected) {
aCallback();
return void aCallback();
}
this.profiler.connect(function onConnect() {

View File

@ -9,12 +9,14 @@ const Cu = Components.utils;
Cu.import("resource:///modules/devtools/gDevTools.jsm");
Cu.import("resource:///modules/devtools/ProfilerController.jsm");
Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
@ -205,7 +207,6 @@ function ProfilerPanel(frame, toolbox) {
this.window = frame.window;
this.document = frame.document;
this.target = toolbox.target;
this.controller = new ProfilerController(this.target);
this.profiles = new Map();
this._uid = 0;
@ -256,23 +257,39 @@ ProfilerPanel.prototype = {
* @return Promise
*/
open: function PP_open() {
let deferred = Promise.defer();
let promise;
// Local profiling needs to make the target remote.
if (!this.target.isRemote) {
promise = this.target.makeRemote();
} else {
promise = Promise.resolve(this.target);
}
this.controller.connect(function onConnect() {
let create = this.document.getElementById("profiler-create");
create.addEventListener("click", this.createProfile.bind(this), false);
create.removeAttribute("disabled");
return promise
.then(function(target) {
let deferred = Promise.defer();
this.controller = new ProfilerController(this.target);
let profile = this.createProfile();
this.switchToProfile(profile, function () {
this.isReady = true;
this.emit("ready");
this.controller.connect(function onConnect() {
let create = this.document.getElementById("profiler-create");
create.addEventListener("click", this.createProfile.bind(this), false);
create.removeAttribute("disabled");
deferred.resolve(this);
let profile = this.createProfile();
this.switchToProfile(profile, function () {
this.isReady = true;
this.emit("ready");
deferred.resolve(this);
}.bind(this))
}.bind(this));
return deferred.promise;
}.bind(this))
}.bind(this));
return deferred.promise;
.then(null, function onError(reason) {
Cu.reportError("ProfilerPanel open failed. " +
reason.error + ": " + reason.message);
});
},
/**

View File

@ -53,10 +53,12 @@ gcli.addCommand({
mgr.on("off", aChangeHandler);
},
offChange: function(aTarget, aChangeHandler) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
mgr.off("on", aChangeHandler);
mgr.off("off", aChangeHandler);
if (aTarget.tab) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
mgr.off("on", aChangeHandler);
mgr.off("off", aChangeHandler);
}
},
},
exec: gcli_cmd_resize

View File

@ -57,9 +57,11 @@ gcli.addCommand({
tilt.on("change", aChangeHandler);
},
offChange: function(aTarget, aChangeHandler) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let tilt = TiltManager.getTiltForBrowser(browserWindow);
tilt.off("change", aChangeHandler);
if (aTarget.tab) {
let browserWindow = aTarget.tab.ownerDocument.defaultView;
let tilt = TiltManager.getTiltForBrowser(browserWindow);
tilt.off("change", aChangeHandler);
}
},
},
exec: function(args, context) {

View File

@ -225,7 +225,9 @@ AutocompletePopup.prototype = {
*/
set selectedIndex(aIndex) {
this._list.selectedIndex = aIndex;
this._list.ensureIndexIsVisible(this._list.selectedIndex);
if (this._list.ensureIndexIsVisible) {
this._list.ensureIndexIsVisible(this._list.selectedIndex);
}
},
/**

View File

@ -237,12 +237,11 @@ WebConsole.prototype = {
if ((win = this.iframe.contentWindow) &&
(doc = win.document) &&
doc.readyState == "complete") {
this.iframe.addEventListener("load", onIframeLoad, true);
}
else {
initUI();
}
else {
this.iframe.addEventListener("load", onIframeLoad, true);
}
return deferred.promise;
},
@ -344,6 +343,67 @@ WebConsole.prototype = {
this.viewSource(aSourceURL, aSourceLine);
},
/**
* Tries to open a JavaScript file related to the web page for the web console
* instance in the Script Debugger. If the file is not found, it is opened in
* source view instead.
*
* @param string aSourceURL
* The URL of the file.
* @param integer aSourceLine
* The line number which you want to place the caret.
*/
viewSourceInDebugger:
function WC_viewSourceInDebugger(aSourceURL, aSourceLine)
{
let self = this;
let panelWin = null;
let debuggerWasOpen = true;
let toolbox = gDevTools.getToolbox(this.target);
if (!toolbox.getPanel("jsdebugger")) {
debuggerWasOpen = false;
let toolboxWin = toolbox.doc.defaultView;
toolboxWin.addEventListener("Debugger:AfterSourcesAdded",
function afterSourcesAdded() {
toolboxWin.removeEventListener("Debugger:AfterSourcesAdded",
afterSourcesAdded);
loadScript();
});
}
toolbox.selectTool("jsdebugger").then(function onDebuggerOpen(dbg) {
panelWin = dbg.panelWin;
if (debuggerWasOpen) {
loadScript();
}
});
function loadScript() {
let debuggerView = panelWin.DebuggerView;
if (!debuggerView.Sources.containsValue(aSourceURL)) {
toolbox.selectTool("webconsole");
self.viewSource(aSourceURL, aSourceLine);
return;
}
if (debuggerWasOpen && debuggerView.Sources.selectedValue == aSourceURL) {
debuggerView.editor.setCaretPosition(aSourceLine - 1);
return;
}
panelWin.addEventListener("Debugger:SourceShown", onSource, false);
debuggerView.Sources.preferredSource = aSourceURL;
}
function onSource(aEvent) {
if (aEvent.detail.url != aSourceURL) {
return;
}
panelWin.removeEventListener("Debugger:SourceShown", onSource, false);
panelWin.DebuggerView.editor.setCaretPosition(aSourceLine - 1);
}
},
/**
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.
@ -359,8 +419,6 @@ WebConsole.prototype = {
delete HUDService.hudReferences[this.hudId];
let tabWindow = this.target.isLocalTab ? this.target.window : null;
this._destroyer = Promise.defer();
let popupset = this.mainPopupSet;
@ -371,15 +429,15 @@ WebConsole.prototype = {
let onDestroy = function WC_onDestroyUI() {
try {
let tabWindow = this.target.isLocalTab ? this.target.window : null;
tabWindow && tabWindow.focus();
}
catch (ex) {
// Tab focus can fail if the tab is closed.
// Tab focus can fail if the tab or target is closed.
}
let id = WebConsoleUtils.supportsString(this.hudId);
Services.obs.notifyObservers(id, "web-console-destroyed", null);
this._destroyer.resolve(null);
}.bind(this);

View File

@ -10,6 +10,9 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
"resource:///modules/HUDService.jsm");
@ -38,17 +41,31 @@ WebConsolePanel.prototype = {
{
let parentDoc = this._toolbox.doc;
let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
let promise = HUDService.openWebConsole(this.target, iframe);
let promise;
return promise.then(function onSuccess(aWebConsole) {
this.hud = aWebConsole;
this._isReady = true;
this.emit("ready");
return this;
}.bind(this), function onError(aReason) {
Cu.reportError("WebConsolePanel open failed. " +
aReason.error + ": " + aReason.message);
});
// Local debugging needs to make the target remote.
if (!this.target.isRemote) {
promise = this.target.makeRemote();
} else {
promise = Promise.resolve(this.target);
}
return promise
.then(function(aTarget) {
this._frameWindow._remoteTarget = aTarget;
return HUDService.openWebConsole(this.target, iframe);
}.bind(this))
.then(function onSuccess(aWebConsole) {
this.hud = aWebConsole;
this._isReady = true;
this.emit("ready");
return this;
}.bind(this), function onError(aReason) {
let msg = "WebConsolePanel open failed. " +
aReason.error + ": " + aReason.message;
dump(msg + "\n");
Cu.reportError(msg);
});
},
get target() this._toolbox.target,

View File

@ -104,6 +104,7 @@ MOCHITEST_BROWSER_FILES = \
browser_webconsole_bug_622303_persistent_filters.js \
browser_webconsole_bug_770099_bad_policyuri.js \
browser_webconsole_bug_770099_violation.js \
browser_webconsole_bug_766001_JS_Console_in_Debugger.js \
browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js \
browser_cached_messages.js \
browser_bug664688_sandbox_update_after_navigation.js \
@ -210,6 +211,9 @@ MOCHITEST_BROWSER_FILES += \
test-result-format-as-string.html \
test-bug-737873-mixedcontent.html \
test-repeated-messages.html \
test-bug-766001-console-log.js \
test-bug-766001-js-console-links.html \
test-bug-766001-js-errors.js \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -77,21 +77,21 @@ function tab2Loaded(aEvent) {
try {
let target1 = TargetFactory.forTab(tab1);
gDevTools.closeToolbox(target1);
gDevTools.closeToolbox(target1).then(function() {
try {
let target2 = TargetFactory.forTab(tab2);
gDevTools.closeToolbox(target2);
}
catch (ex) {
ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
noErrors = false;
}
});
}
catch (ex) {
ok(false, "gDevTools.closeToolbox(target1) exception: " + ex);
noErrors = false;
}
try {
let target2 = TargetFactory.forTab(tab2);
gDevTools.closeToolbox(target2);
}
catch (ex) {
ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
noErrors = false;
}
}
function testEnd() {

View File

@ -0,0 +1,113 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
"/test-bug-766001-js-console-links.html";
let nodes, dbg, toolbox, target, index = 0, src, line;
function test()
{
expectUncaughtException();
requestLongerTimeout(2);
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testViewSource);
}, true);
}
function testViewSource(aHud)
{
registerCleanupFunction(function() {
nodes = dbg = toolbox = target = index = src = line = null;
});
let JSSelector = ".webconsole-msg-exception .webconsole-location";
let consoleSelector = ".webconsole-msg-console .webconsole-location";
waitForSuccess({
name: "find the location node",
validatorFn: function()
{
return aHud.outputNode.querySelector(JSSelector) &&
aHud.outputNode.querySelector(consoleSelector);
},
successFn: function()
{
nodes = [aHud.outputNode.querySelector(JSSelector),
aHud.outputNode.querySelector(consoleSelector)];
target = TargetFactory.forTab(gBrowser.selectedTab);
toolbox = gDevTools.getToolbox(target);
toolbox.once("jsdebugger-selected", checkLineAndClickNext);
EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
},
failureFn: finishTest,
});
}
function checkLineAndClickNext(aEvent, aPanel)
{
if (index == 3) {
finishTest();
return;
}
info(aEvent + " event fired for index " + index);
dbg = aPanel;
src = nodes[index%2].getAttribute("title");
ok(src, "source url found for index " + index);
line = nodes[index%2].sourceLine;
ok(line, "found source line for index " + index);
info("Waiting for the correct script to be selected for index " + index);
dbg.panelWin.addEventListener("Debugger:SourceShown", onSource, false);
}
function onSource(aEvent) {
if (aEvent.detail.url != src) {
return;
}
dbg.panelWin.removeEventListener("Debugger:SourceShown", onSource, false);
ok(true, "Correct script is selected for index " + index);
checkCorrectLine(function() {
gDevTools.showToolbox(target, "webconsole").then(function() {
index++;
info("webconsole selected for index " + index);
toolbox.once("jsdebugger-selected", checkLineAndClickNext);
EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
});
});
}
function checkCorrectLine(aCallback)
{
waitForSuccess({
name: "correct source and line test for debugger for index " + index,
validatorFn: function()
{
let debuggerView = dbg.panelWin.DebuggerView;
if (debuggerView.editor &&
debuggerView.editor.getCaretPosition().line == line - 1) {
return true;
}
return false;
},
successFn: function()
{
aCallback && executeSoon(aCallback);
},
failureFn: finishTest,
timeout: 10000,
});
}

View File

@ -0,0 +1,8 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
window.addEventListener("load", function() {
console.log("Blah Blah");
}, false);

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web Console test for bug 766001 : Open JS/Console call Links in Debugger</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript" src="test-bug-766001-js-errors.js"></script>
<script type="text/javascript" src="test-bug-766001-console-log.js"></script>
</head>
<body>
<p>Web Console test for bug 766001 : Open JS/Console call Links in Debugger.</p>
</body>
</html>

View File

@ -0,0 +1,7 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
window.addEventListener("load", function() {
document.bar();
}, false);

View File

@ -15,15 +15,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "debuggerSocketConnect",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
@ -2419,6 +2410,10 @@ WebConsoleFrame.prototype = {
else if (locationNode.parentNode.category == CATEGORY_CSS) {
this.owner.viewSourceInStyleEditor(aSourceURL, aSourceLine);
}
else if (locationNode.parentNode.category == CATEGORY_JS ||
locationNode.parentNode.category == CATEGORY_WEBDEV) {
this.owner.viewSourceInDebugger(aSourceURL, aSourceLine);
}
else {
this.owner.viewSource(aSourceURL, aSourceLine);
}
@ -3988,7 +3983,6 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget)
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
this._onFileActivity = this._onFileActivity.bind(this);
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onListTabs = this._onListTabs.bind(this);
this._onAttachTab = this._onAttachTab.bind(this);
this._onAttachConsole = this._onAttachConsole.bind(this);
this._onCachedMessages = this._onCachedMessages.bind(this);
@ -4072,17 +4066,6 @@ WebConsoleConnectionProxy.prototype = {
*/
_hasNativeConsoleAPI: false,
/**
* Initialize the debugger server.
*/
initServer: function WCCP_initServer()
{
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
},
/**
* Initialize a debugger client and connect it to the debugger server.
*
@ -4111,16 +4094,7 @@ WebConsoleConnectionProxy.prototype = {
this._connectTimer = null;
}.bind(this));
// TODO: convert the non-remote path to use the target API as well.
let transport, client;
if (this.target.isRemote) {
client = this.client = this.target.client;
}
else {
this.initServer();
transport = DebuggerServer.connectPipe();
client = this.client = new DebuggerClient(transport);
}
let client = this.client = this.target.client;
client.addListener("pageError", this._onPageError);
client.addListener("consoleAPICall", this._onConsoleAPICall);
@ -4129,21 +4103,14 @@ WebConsoleConnectionProxy.prototype = {
client.addListener("fileActivity", this._onFileActivity);
client.addListener("tabNavigated", this._onTabNavigated);
if (this.target.isRemote) {
if (!this.target.chrome) {
// target.form is a TabActor grip
this._attachTab(this.target.form);
}
else {
// target.form is a RootActor grip
this._consoleActor = this.target.form.consoleActor;
this._attachConsole();
}
if (!this.target.chrome) {
// target.form is a TabActor grip
this._attachTab(this.target.form);
}
else {
client.connect(function(aType, aTraits) {
client.listTabs(this._onListTabs);
}.bind(this));
// target.form is a RootActor grip
this._consoleActor = this.target.form.consoleActor;
this._attachConsole();
}
return promise;
@ -4163,25 +4130,6 @@ WebConsoleConnectionProxy.prototype = {
this._connectDefer.reject(error);
},
/**
* The "listTabs" response handler.
*
* @private
* @param object aResponse
* The JSON response object received from the server.
*/
_onListTabs: function WCCP__onListTabs(aResponse)
{
if (aResponse.error) {
Cu.reportError("listTabs failed: " + aResponse.error + " " +
aResponse.message);
this._connectDefer.reject(aResponse);
return;
}
this._attachTab(aResponse.tabs[aResponse.selected]);
},
/**
* Attach to the tab actor.
*
@ -4433,21 +4381,6 @@ WebConsoleConnectionProxy.prototype = {
return this._disconnecter.promise;
}
let onDisconnect = function() {
if (timer) {
timer.cancel();
timer = null;
this._disconnecter.resolve(null);
}
}.bind(this);
let timer = null;
let remoteTarget = this.target.isRemote;
if (!remoteTarget) {
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(onDisconnect, 1500, Ci.nsITimer.TYPE_ONE_SHOT);
}
this.client.removeListener("pageError", this._onPageError);
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
this.client.removeListener("networkEvent", this._onNetworkEvent);
@ -4455,28 +4388,13 @@ WebConsoleConnectionProxy.prototype = {
this.client.removeListener("fileActivity", this._onFileActivity);
this.client.removeListener("tabNavigated", this._onTabNavigated);
let client = this.client;
this.client = null;
this.webConsoleClient = null;
this.tabClient = null;
this.target = null;
this.connected = false;
this.owner = null;
if (!remoteTarget) {
try {
client.close(onDisconnect);
}
catch (ex) {
Cu.reportError("Web Console disconnect exception: " + ex);
Cu.reportError(ex.stack);
onDisconnect();
}
}
else {
onDisconnect();
}
this._disconnecter.resolve(null);
return this._disconnecter.promise;
},

View File

@ -0,0 +1,12 @@
<!-- 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/. -->
<!-- LOCALIZATION NOTE : FILE This file contains the Font Inspector strings.
- The Font Inspector is the panel accessible in the Inspector sidebar. -->
<!ENTITY title "Fonts">
<!ENTITY showAllFonts "See all the fonts used in the page">
<!ENTITY usedAs "Used as: ">
<!ENTITY system "system">
<!ENTITY remote "remote">

View File

@ -333,29 +333,29 @@ introDesc=Show the opening message
# command. Displayed when the user asks for help on what it does.
introManual=Redisplay the message that is shown to new users until they click the 'Got it!' button
# LOCALIZATION NOTE (introTextOpening): The 'intro text' opens when the user
# LOCALIZATION NOTE (introTextOpening2): The 'intro text' opens when the user
# first opens the developer toolbar to explain the command line, and is shown
# each time it is opened until the user clicks the 'Got it!' button. This
# string is the opening paragraph of the intro text.
introTextOpening2=This command line is designed for developers. It focuses on speed of input over JavaScript syntax and a rich display over monospace output.
# LOCALIZATION NOTE (introTextCommands): For information about the 'intro
# text' see introTextOpening. The second paragraph is in 2 sections, the first
# section points the user to the 'help' command.
# text' see introTextOpening2. The second paragraph is in 2 sections, the
# first section points the user to the 'help' command.
introTextCommands=For a list of commands type
# LOCALIZATION NOTE (introTextKeys2): For information about the 'intro text'
# see introTextOpening. The second section in the second paragraph points the
# see introTextOpening2. The second section in the second paragraph points the
# user to the F1/Escape keys which show and hide hints.
introTextKeys2=, or to show/hide command hints press
# LOCALIZATION NOTE (introTextF1Escape): For information about the 'intro
# text' see introTextOpening. This string is used with introTextKeys, and
# text' see introTextOpening2. This string is used with introTextKeys2, and
# contains the keys that are pressed to open and close hints.
introTextF1Escape=F1/Escape
# LOCALIZATION NOTE (introTextGo): For information about the 'intro text' see
# introTextOpening. The text on the button that dismisses the intro text.
# introTextOpening2. The text on the button that dismisses the intro text.
introTextGo=Got it!
# LOCALIZATION NOTE (hideIntroDesc): Short description of the 'hideIntro'

View File

@ -1,3 +1,7 @@
# 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/.
toolboxDockButtons.bottom.tooltip=Dock to bottom of browser window
toolboxDockButtons.side.tooltip=Dock to side of browser window
toolboxDockButtons.window.tooltip=Show in separate window

View File

@ -47,6 +47,7 @@
locale/browser/devtools/inspector.dtd (%chrome/browser/devtools/inspector.dtd)
locale/browser/devtools/connection-screen.dtd (%chrome/browser/devtools/connection-screen.dtd)
locale/browser/devtools/connection-screen.properties (%chrome/browser/devtools/connection-screen.properties)
locale/browser/devtools/font-inspector.dtd (%chrome/browser/devtools/font-inspector.dtd)
locale/browser/newTab.dtd (%chrome/browser/newTab.dtd)
locale/browser/newTab.properties (%chrome/browser/newTab.properties)
locale/browser/openLocation.dtd (%chrome/browser/openLocation.dtd)

View File

@ -0,0 +1,79 @@
* {
-moz-box-sizing: border-box;
}
body {
background: #F9F9F9;
margin: 0;
padding-bottom: 20px;
}
#all-fonts {
padding: 0 5px;
margin: 0;
}
#showall {
border-radius: 0;
border: 1px solid black;
margin: 3px;
cursor: pointer;
position: fixed;
bottom: 0;
right: 0;
}
.font {
border-bottom: 1px solid #DDD;
padding: 10px 5px;
font-size: 0;
}
.font:last-of-type {
border-bottom: 0;
}
.font:nth-child(even) {
background: #F4F4F4;
}
.font-preview {
height: 60px;
width: 100%;
border: 0;
display: block;
}
.font-info {
font-size: 1rem;
display: block;
}
.font-name {
display: inline;
}
.font-css-code {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
background: white;
padding: 5px;
border: 1px dotted #CCC;
}
.font-is-local,
.font-is-remote,
.font-format-url,
.font-css {
color: #999
}
.font-url {
border: 1px solid #CCC;
color: #888;
}
.font-url:focus {
color: black;
}

View File

@ -190,6 +190,7 @@ browser.jar:
skin/classic/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
skin/classic/browser/devtools/close.png (devtools/close.png)
skin/classic/browser/devtools/undock.png (devtools/undock.png)
skin/classic/browser/devtools/font-inspector.css (devtools/font-inspector.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-16-throbber.png
skin/classic/browser/sync-16.png

View File

@ -0,0 +1,79 @@
* {
-moz-box-sizing: border-box;
}
body {
background: #F9F9F9;
margin: 0;
padding-bottom: 20px;
}
#all-fonts {
padding: 0 5px;
margin: 0;
}
#showall {
border-radius: 0;
border: 1px solid black;
margin: 3px;
cursor: pointer;
position: fixed;
bottom: 0;
right: 0;
}
.font {
border-bottom: 1px solid #DDD;
padding: 10px 5px;
font-size: 0;
}
.font:last-of-type {
border-bottom: 0;
}
.font:nth-child(even) {
background: #F4F4F4;
}
.font-preview {
height: 60px;
width: 100%;
border: 0;
display: block;
}
.font-info {
font-size: 1rem;
display: block;
}
.font-name {
display: inline;
}
.font-css-code {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
background: white;
padding: 5px;
border: 1px dotted #CCC;
}
.font-is-local,
.font-is-remote,
.font-format-url,
.font-css {
color: #999
}
.font-url {
border: 1px solid #CCC;
color: #888;
}
.font-url:focus {
color: black;
}

View File

@ -271,6 +271,7 @@ browser.jar:
skin/classic/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
skin/classic/browser/devtools/close.png (devtools/close.png)
skin/classic/browser/devtools/undock.png (devtools/undock.png)
skin/classic/browser/devtools/font-inspector.css (devtools/font-inspector.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-throbber.png
skin/classic/browser/sync-16.png

View File

@ -0,0 +1,79 @@
* {
-moz-box-sizing: border-box;
}
body {
background: #F9F9F9;
margin: 0;
padding-bottom: 20px;
}
#all-fonts {
padding: 0 5px;
margin: 0;
}
#showall {
border-radius: 0;
border: 1px solid black;
margin: 3px;
cursor: pointer;
position: fixed;
bottom: 0;
right: 0;
}
.font {
border-bottom: 1px solid #DDD;
padding: 10px 5px;
font-size: 0;
}
.font:last-of-type {
border-bottom: 0;
}
.font:nth-child(even) {
background: #F4F4F4;
}
.font-preview {
height: 60px;
width: 100%;
border: 0;
display: block;
}
.font-info {
font-size: 1rem;
display: block;
}
.font-name {
display: inline;
}
.font-css-code {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
background: white;
padding: 5px;
border: 1px dotted #CCC;
}
.font-is-local,
.font-is-remote,
.font-format-url,
.font-css {
color: #999
}
.font-url {
border: 1px solid #CCC;
color: #888;
}
.font-url:focus {
color: black;
}

View File

@ -217,6 +217,7 @@ browser.jar:
skin/classic/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
skin/classic/browser/devtools/close.png (devtools/close.png)
skin/classic/browser/devtools/undock.png (devtools/undock.png)
skin/classic/browser/devtools/font-inspector.css (devtools/font-inspector.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-throbber.png
skin/classic/browser/sync-16.png
@ -445,6 +446,7 @@ browser.jar:
skin/classic/aero/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
skin/classic/aero/browser/devtools/close.png (devtools/close.png)
skin/classic/aero/browser/devtools/undock.png (devtools/undock.png)
skin/classic/aero/browser/devtools/font-inspector.css (devtools/font-inspector.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/aero/browser/sync-throbber.png
skin/classic/aero/browser/sync-16.png

View File

@ -679,7 +679,6 @@ DebuggerProgressListener.prototype = {
from: this._tabActor.actorID,
type: "tabNavigated",
url: aRequest.URI.spec,
title: "",
nativeConsoleAPI: true,
state: "start",
});