зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to s-c.
This commit is contained in:
Коммит
6b64ac8884
|
@ -522,7 +522,7 @@
|
|||
<menuitem id="webConsole" observes="devtoolsMenuBroadcaster_WebConsole" accesskey="&webConsoleCmd.accesskey;"/>
|
||||
<menuitem id="menu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect" accesskey="&inspectMenu.accesskey;"/>
|
||||
<menuitem id="menu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI" accesskey="&responsiveDesignTool.accesskey;"/>
|
||||
<menuitem id="menu_debugger" observes="devtoolsMenuBroadcaster_Debugger"/>
|
||||
<menuitem id="menu_debugger" observes="devtoolsMenuBroadcaster_Debugger" accesskey="&debuggerMenu.accesskey;"/>
|
||||
<menuitem id="menu_remoteDebugger" observes="devtoolsMenuBroadcaster_RemoteDebugger"/>
|
||||
<menuitem id="menu_chromeDebugger" observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
|
||||
<menuitem id="menu_scratchpad" observes="devtoolsMenuBroadcaster_Scratchpad" accesskey="&scratchpad.accesskey;"/>
|
||||
|
|
|
@ -18,6 +18,47 @@ gcli.addCommand({
|
|||
manual: gcli.lookup("dbgManual")
|
||||
});
|
||||
|
||||
/**
|
||||
* 'dbg open' command
|
||||
*/
|
||||
gcli.addCommand({
|
||||
name: "dbg open",
|
||||
description: gcli.lookup("dbgOpen"),
|
||||
params: [],
|
||||
exec: function (args, context) {
|
||||
let win = context.environment.chromeDocument.defaultView;
|
||||
let tab = win.gBrowser.selectedTab;
|
||||
let dbg = win.DebuggerUI.findDebugger();
|
||||
|
||||
if (dbg) {
|
||||
if (dbg.ownerTab !== tab) {
|
||||
win.DebuggerUI.toggleDebugger();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
win.DebuggerUI.toggleDebugger();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 'dbg close' command
|
||||
*/
|
||||
gcli.addCommand({
|
||||
name: "dbg close",
|
||||
description: gcli.lookup("dbgClose"),
|
||||
params: [],
|
||||
exec: function (args, context) {
|
||||
let win = context.environment.chromeDocument.defaultView;
|
||||
let tab = win.gBrowser.selectedTab;
|
||||
let dbg = win.DebuggerUI.findDebugger();
|
||||
|
||||
if (dbg) {
|
||||
dbg.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 'dbg interrupt' command
|
||||
|
|
|
@ -8,8 +8,13 @@ function test() {
|
|||
}
|
||||
|
||||
function testDbgCmd() {
|
||||
let pane = DebuggerUI.toggleDebugger();
|
||||
ok(pane, "toggleDebugger() should return a pane.");
|
||||
DeveloperToolbarTest.exec({
|
||||
typed: "dbg open",
|
||||
blankOutput: true
|
||||
});
|
||||
|
||||
let pane = DebuggerUI.findDebugger();
|
||||
ok(pane, "Debugger was opened.");
|
||||
let frame = pane._frame;
|
||||
|
||||
frame.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) {
|
||||
|
@ -41,9 +46,14 @@ function testDbgCmd() {
|
|||
cmd("dbg continue", function() {
|
||||
cmd("dbg continue", function() {
|
||||
is(output.value, "dbg continue", "debugger continued");
|
||||
pane.contentWindow.gClient.close(function() {
|
||||
finish();
|
||||
DeveloperToolbarTest.exec({
|
||||
typed: "dbg close",
|
||||
blankOutput: true
|
||||
});
|
||||
|
||||
let dbg = DebuggerUI.findDebugger();
|
||||
ok(!dbg, "Debugger was closed.");
|
||||
finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ const Cc = Components.classes;
|
|||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const NEW_SCRIPT_DISPLAY_DELAY = 100; // ms
|
||||
const FRAME_STEP_CACHE_DURATION = 100; // ms
|
||||
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
|
||||
const SCRIPTS_URL_MAX_LENGTH = 64; // chars
|
||||
|
@ -929,7 +930,11 @@ SourceScripts.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
|
||||
this._addScript({
|
||||
url: aPacket.url,
|
||||
startLine: aPacket.startLine,
|
||||
source: aPacket.source
|
||||
}, true);
|
||||
|
||||
let preferredScriptUrl = DebuggerView.Scripts.preferredScriptUrl;
|
||||
|
||||
|
@ -937,15 +942,21 @@ SourceScripts.prototype = {
|
|||
if (aPacket.url === DebuggerView.Scripts.preferredScriptUrl) {
|
||||
DebuggerView.Scripts.selectScript(aPacket.url);
|
||||
}
|
||||
// ..or the first entry if there's not one selected yet.
|
||||
else if (!DebuggerView.Scripts.selected) {
|
||||
DebuggerView.Scripts.selectIndex(0);
|
||||
// Selecting a script would make it "preferred", which is a lie here,
|
||||
// because we're only displaying a script to make sure there's always
|
||||
// something available in the SourceEditor and the scripts menulist.
|
||||
// Hence the need revert back to the initial preferred script, just
|
||||
// in case it will be available soon.
|
||||
DebuggerView.Scripts.preferredScriptUrl = preferredScriptUrl;
|
||||
// ..or the first entry if there's none selected yet after a while
|
||||
else {
|
||||
window.setTimeout(function() {
|
||||
// If after a certain delay the preferred script still wasn't received,
|
||||
// just give up on waiting and display the first entry.
|
||||
if (!DebuggerView.Scripts.selected) {
|
||||
DebuggerView.Scripts.selectIndex(0);
|
||||
// Selecting a script would make it "preferred", which is a lie here,
|
||||
// because we're only displaying a script to make sure there's always
|
||||
// something available in the SourceEditor and the scripts menulist.
|
||||
// Hence the need revert back to the initial preferred script, just
|
||||
// in case it will be available soon.
|
||||
DebuggerView.Scripts.preferredScriptUrl = preferredScriptUrl;
|
||||
}
|
||||
}, NEW_SCRIPT_DISPLAY_DELAY);
|
||||
}
|
||||
|
||||
// If there are any stored breakpoints for this script, display them again,
|
||||
|
@ -1206,7 +1217,11 @@ SourceScripts.prototype = {
|
|||
*/
|
||||
showScript: function SS_showScript(aScript, aOptions = {}) {
|
||||
if (aScript.loaded) {
|
||||
this._onShowScript(aScript, aOptions);
|
||||
// Scripts may take a longer time to load than expected, therefore the
|
||||
// required one may change at any time after a previous request was made.
|
||||
if (aScript.url === DebuggerView.Scripts.selected) {
|
||||
this._onShowScript(aScript, aOptions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1217,7 +1232,7 @@ SourceScripts.prototype = {
|
|||
|
||||
// Notify that we need to load a script file.
|
||||
DebuggerController.dispatchEvent("Debugger:LoadSource", {
|
||||
url: aScript.url,
|
||||
script: aScript,
|
||||
options: aOptions
|
||||
});
|
||||
},
|
||||
|
@ -1256,84 +1271,22 @@ SourceScripts.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Handles notifications to load a source script from the cache or from a
|
||||
* local file.
|
||||
*
|
||||
* XXX: It may be better to use nsITraceableChannel to get to the sources
|
||||
* without relying on caching when we can (not for eval, etc.):
|
||||
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
|
||||
* Handles notifications to load a source script.
|
||||
*/
|
||||
_onLoadSource: function SS__onLoadSource(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
let script = aEvent.detail.script;
|
||||
let options = aEvent.detail.options;
|
||||
let self = this;
|
||||
|
||||
switch (Services.io.extractScheme(url)) {
|
||||
case "file":
|
||||
case "chrome":
|
||||
case "resource":
|
||||
try {
|
||||
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
|
||||
if (!Components.isSuccessCode(aStatus)) {
|
||||
return self._logError(url, aStatus);
|
||||
}
|
||||
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
|
||||
source = self._convertToUnicode(source);
|
||||
self._onLoadSourceFinished(url, source, null, options);
|
||||
aStream.close();
|
||||
});
|
||||
} catch (ex) {
|
||||
return self._logError(url, ex.name);
|
||||
}
|
||||
break;
|
||||
let sourceClient = this.activeThread.source(script.source);
|
||||
sourceClient.source(function (aResponse) {
|
||||
if (aResponse.error) {
|
||||
return this._logError(script.url, -1);
|
||||
}
|
||||
|
||||
default:
|
||||
let channel = Services.io.newChannel(url, null, null);
|
||||
let chunks = [];
|
||||
let streamListener = {
|
||||
onStartRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
return self._logError(url, aStatusCode);
|
||||
}
|
||||
},
|
||||
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
|
||||
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
return self._logError(url, aStatusCode);
|
||||
}
|
||||
let source = self._convertToUnicode(chunks.join(""), channel.contentCharset);
|
||||
self._onLoadSourceFinished(url, source, channel.contentType, options);
|
||||
}
|
||||
};
|
||||
|
||||
channel.loadFlags = channel.LOAD_FROM_CACHE;
|
||||
channel.asyncOpen(streamListener, null);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a given string, encoded in a given character set, to unicode.
|
||||
* @param string aString
|
||||
* A string.
|
||||
* @param string aCharset
|
||||
* A character set.
|
||||
* @return string
|
||||
* A unicode string.
|
||||
*/
|
||||
_convertToUnicode: function SS__convertToUnicode(aString, aCharset) {
|
||||
// Decoding primitives.
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
|
||||
try {
|
||||
converter.charset = aCharset || "UTF-8";
|
||||
return converter.ConvertToUnicode(aString);
|
||||
} catch(e) {
|
||||
return aString;
|
||||
}
|
||||
this._onLoadSourceFinished(script.url,
|
||||
aResponse.source,
|
||||
options);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1344,14 +1297,12 @@ SourceScripts.prototype = {
|
|||
* The URL of the source script.
|
||||
* @param string aSourceText
|
||||
* The text of the source script.
|
||||
* @param string aContentType
|
||||
* The content type of the source script.
|
||||
* @param object aOptions [optional]
|
||||
* Additional options for showing the script. Supported options:
|
||||
* - targetLine: place the editor at the given line number.
|
||||
*/
|
||||
_onLoadSourceFinished:
|
||||
function SS__onLoadSourceFinished(aScriptUrl, aSourceText, aContentType, aOptions) {
|
||||
function SS__onLoadSourceFinished(aScriptUrl, aSourceText, aOptions) {
|
||||
let element = DebuggerView.Scripts.getScriptByLocation(aScriptUrl);
|
||||
|
||||
// Tab navigated before we got a chance to finish loading and displaying
|
||||
|
@ -1367,7 +1318,6 @@ SourceScripts.prototype = {
|
|||
|
||||
script.loaded = true;
|
||||
script.text = aSourceText;
|
||||
script.contentType = aContentType;
|
||||
element.setUserData("sourceScript", script, null);
|
||||
|
||||
if (aOptions.silent) {
|
||||
|
|
|
@ -468,7 +468,7 @@ GlobalSearchView.prototype = {
|
|||
continue;
|
||||
}
|
||||
DebuggerController.dispatchEvent("Debugger:LoadSource", {
|
||||
url: url,
|
||||
script: DebuggerView.Scripts.getScriptByLocation(url).getUserData("sourceScript"),
|
||||
options: {
|
||||
silent: true,
|
||||
callback: aFetchCallback
|
||||
|
|
|
@ -59,7 +59,7 @@ function testLocationChange()
|
|||
ok(true, "tabNavigated event was fired.");
|
||||
info("Still attached to the tab.");
|
||||
|
||||
gDebugger.addEventListener("Debugger:AfterNewScript", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
isnot(gDebugger.DebuggerView.Scripts.selected, null,
|
||||
|
|
|
@ -170,18 +170,24 @@ function test()
|
|||
{
|
||||
let scriptsView = gView.Scripts;
|
||||
let scriptLocations = scriptsView.scriptLocations;
|
||||
info("Available scripts: " + scriptLocations);
|
||||
|
||||
if (scriptLocations.length === 2) {
|
||||
// Poll every few milliseconds until the scripts are retrieved.
|
||||
let count = 0;
|
||||
let intervalID = window.setInterval(function() {
|
||||
dump("count: " + count + " ");
|
||||
if (++count > 50) {
|
||||
ok(false, "Timed out while polling for the scripts.");
|
||||
closeDebuggerAndFinish();
|
||||
}
|
||||
if (scriptLocations.length !== 2) {
|
||||
return;
|
||||
}
|
||||
info("Available scripts: " + scriptLocations);
|
||||
|
||||
// We got all the scripts, it's safe to switch.
|
||||
window.clearInterval(intervalID);
|
||||
scriptsView.selectScript(scriptLocations[index]);
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:AfterNewScript", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
switchScript(index);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function reloadPage()
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<link rel="stylesheet" href="chrome://browser/skin/devtools/markup-view.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
|
||||
</head>
|
||||
<body role="application">
|
||||
<body class="devtools-theme-background" role="application">
|
||||
<div id="root"></div>
|
||||
<div id="templates" style="display:none">
|
||||
<ul>
|
||||
|
|
|
@ -9,6 +9,7 @@ const Cu = Components.utils;
|
|||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
|
||||
|
||||
|
@ -159,6 +160,8 @@ function ResponsiveUI(aWindow, aTab)
|
|||
this.rotate();
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
switchToFloatingScrollbars(this.tab);
|
||||
}
|
||||
|
||||
ResponsiveUI.prototype = {
|
||||
|
@ -179,6 +182,7 @@ ResponsiveUI.prototype = {
|
|||
close: function RUI_unload() {
|
||||
if (this.closing)
|
||||
return;
|
||||
switchToNativeScrollbars(this.tab);
|
||||
this.closing = true;
|
||||
|
||||
this.unCheckMenus();
|
||||
|
|
|
@ -31,11 +31,27 @@ function test() {
|
|||
instance = gBrowser.selectedTab.__responsiveUI;
|
||||
ok(instance, "instance of the module is attached to the tab.");
|
||||
|
||||
ensureScrollbarsAreFloating();
|
||||
|
||||
instance.transitionsEnabled = false;
|
||||
|
||||
testPresets();
|
||||
}
|
||||
|
||||
function ensureScrollbarsAreFloating() {
|
||||
let body = gBrowser.contentDocument.body;
|
||||
let html = gBrowser.contentDocument.documentElement;
|
||||
|
||||
let originalWidth = body.getBoundingClientRect().width;
|
||||
|
||||
html.style.overflowY = "scroll"; // Force scrollbars
|
||||
// Flush. Should not be needed as getBoundingClientRect() should flush,
|
||||
// but just in case.
|
||||
gBrowser.contentWindow.getComputedStyle(html).overflowY;
|
||||
let newWidth = body.getBoundingClientRect().width;
|
||||
is(originalWidth, newWidth, "Floating scrollbars are presents");
|
||||
}
|
||||
|
||||
function testPresets() {
|
||||
function testOnePreset(c) {
|
||||
if (c == 0) {
|
||||
|
|
|
@ -765,7 +765,7 @@ OutputPanel.prototype.remove = function OP_remove()
|
|||
this.canHide = true;
|
||||
}
|
||||
|
||||
if (this._panel) {
|
||||
if (this._panel && this._panel.hidePopup) {
|
||||
this._panel.hidePopup();
|
||||
}
|
||||
|
||||
|
@ -992,7 +992,9 @@ TooltipPanel.prototype.remove = function TP_remove()
|
|||
if (isLinux) {
|
||||
this.canHide = true;
|
||||
}
|
||||
this._panel.hidePopup();
|
||||
if (this._panel && this._panel.hidePopup) {
|
||||
this._panel.hidePopup();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/* 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 EXPORTED_SYMBOLS = [ "switchToFloatingScrollbars", "switchToNativeScrollbars" ];
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let URL = Services.io.newURI("chrome://browser/skin/devtools/floating-scrollbars.css", null, null);
|
||||
|
||||
let trackedTabs = new WeakMap();
|
||||
|
||||
/**
|
||||
* Switch to floating scrollbars, à la mobile.
|
||||
*
|
||||
* @param aTab the targeted tab.
|
||||
*
|
||||
*/
|
||||
function switchToFloatingScrollbars(aTab) {
|
||||
let mgr = trackedTabs.get(aTab);
|
||||
if (!mgr) {
|
||||
mgr = new ScrollbarManager(aTab);
|
||||
}
|
||||
mgr.switchToFloating();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to original native scrollbars.
|
||||
*
|
||||
* @param aTab the targeted tab.
|
||||
*
|
||||
*/
|
||||
function switchToNativeScrollbars(aTab) {
|
||||
let mgr = trackedTabs.get(aTab);
|
||||
if (mgr) {
|
||||
mgr.reset();
|
||||
}
|
||||
}
|
||||
|
||||
function ScrollbarManager(aTab) {
|
||||
trackedTabs.set(aTab, this);
|
||||
|
||||
this.attachedTab = aTab;
|
||||
this.attachedBrowser = aTab.linkedBrowser;
|
||||
|
||||
this.reset = this.reset.bind(this);
|
||||
this.switchToFloating = this.switchToFloating.bind(this);
|
||||
|
||||
this.attachedTab.addEventListener("TabClose", this.reset, true);
|
||||
this.attachedBrowser.addEventListener("DOMContentLoaded", this.switchToFloating, true);
|
||||
}
|
||||
|
||||
ScrollbarManager.prototype = {
|
||||
get win() {
|
||||
return this.attachedBrowser.contentWindow;
|
||||
},
|
||||
|
||||
/*
|
||||
* Change the look of the scrollbars.
|
||||
*/
|
||||
switchToFloating: function() {
|
||||
let windows = this.getInnerWindows(this.win);
|
||||
windows.forEach(this.injectStyleSheet);
|
||||
this.forceStyle();
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* Reset the look of the scrollbars.
|
||||
*/
|
||||
reset: function() {
|
||||
let windows = this.getInnerWindows(this.win);
|
||||
windows.forEach(this.removeStyleSheet);
|
||||
this.forceStyle(this.attachedBrowser);
|
||||
this.attachedBrowser.removeEventListener("DOMContentLoaded", this.switchToFloating, true);
|
||||
this.attachedTab.removeEventListener("TabClose", this.reset, true);
|
||||
trackedTabs.delete(this.attachedTab);
|
||||
},
|
||||
|
||||
/*
|
||||
* Toggle the display property of the window to force the style to be applied.
|
||||
*/
|
||||
forceStyle: function() {
|
||||
let parentWindow = this.attachedBrowser.ownerDocument.defaultView;
|
||||
let display = parentWindow.getComputedStyle(this.attachedBrowser).display; // Save display value
|
||||
this.attachedBrowser.style.display = "none";
|
||||
parentWindow.getComputedStyle(this.attachedBrowser).display; // Flush
|
||||
this.attachedBrowser.style.display = display; // Restore
|
||||
},
|
||||
|
||||
/*
|
||||
* return all the window objects present in the hiearchy of a window.
|
||||
*/
|
||||
getInnerWindows: function(win) {
|
||||
let iframes = win.document.querySelectorAll("iframe");
|
||||
let innerWindows = [];
|
||||
for (let iframe of iframes) {
|
||||
innerWindows = innerWindows.concat(this.getInnerWindows(iframe.contentWindow));
|
||||
}
|
||||
return [win].concat(innerWindows);
|
||||
},
|
||||
|
||||
/*
|
||||
* Append the new scrollbar style.
|
||||
*/
|
||||
injectStyleSheet: function(win) {
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
try {
|
||||
winUtils.loadSheet(URL, win.AGENT_SHEET);
|
||||
}catch(e) {}
|
||||
},
|
||||
|
||||
/*
|
||||
* Remove the injected stylesheet.
|
||||
*/
|
||||
removeStyleSheet: function(win) {
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
try {
|
||||
winUtils.removeSheet(URL, win.AGENT_SHEET);
|
||||
}catch(e) {}
|
||||
},
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
// Tests that our Promise implementation works properly
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/devtools/Promise.jsm", tempScope);
|
||||
Cu.import("resource://gre/modules/devtools/_Promise.jsm", tempScope);
|
||||
let Promise = tempScope.Promise;
|
||||
|
||||
function test() {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
var imports = {};
|
||||
Cu.import("resource:///modules/devtools/Templater.jsm", imports);
|
||||
Cu.import("resource:///modules/devtools/Promise.jsm", imports);
|
||||
Cu.import("resource://gre/modules/devtools/_Promise.jsm", imports);
|
||||
|
||||
function test() {
|
||||
addTab("http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html", function() {
|
||||
|
|
|
@ -1629,13 +1629,6 @@ TextPropertyEditor.prototype = {
|
|||
tabindex: "0",
|
||||
});
|
||||
|
||||
editableField({
|
||||
start: this._onStartEditing,
|
||||
element: this.valueSpan,
|
||||
done: this._onValueDone,
|
||||
advanceChars: ';'
|
||||
});
|
||||
|
||||
// Save the initial value as the last committed value,
|
||||
// for restoring after pressing escape.
|
||||
this.committed = { name: this.prop.name,
|
||||
|
@ -1655,6 +1648,15 @@ TextPropertyEditor.prototype = {
|
|||
this.computed = createChild(this.element, "ul", {
|
||||
class: "ruleview-computedlist",
|
||||
});
|
||||
|
||||
editableField({
|
||||
start: this._onStartEditing,
|
||||
element: this.valueSpan,
|
||||
done: this._onValueDone,
|
||||
validate: this._validate.bind(this),
|
||||
warning: this.warning,
|
||||
advanceChars: ';'
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1841,17 +1843,30 @@ TextPropertyEditor.prototype = {
|
|||
/**
|
||||
* Validate this property.
|
||||
*
|
||||
* @param {String} [aValue]
|
||||
* Override the actual property value used for validation without
|
||||
* applying property values e.g. validate as you type.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* True if the property value is valid, false otherwise.
|
||||
*/
|
||||
_validate: function TextPropertyEditor_validate()
|
||||
_validate: function TextPropertyEditor_validate(aValue)
|
||||
{
|
||||
let name = this.prop.name;
|
||||
let value = this.prop.value;
|
||||
let value = typeof aValue == "undefined" ? this.prop.value : aValue;
|
||||
let val = this._parseValue(value);
|
||||
let style = this.doc.createElementNS(HTML_NS, "div").style;
|
||||
let prefs = Services.prefs;
|
||||
|
||||
style.setProperty(name, value, null);
|
||||
// We toggle output of errors whilst the user is typing a property value.
|
||||
let prefVal = Services.prefs.getBoolPref("layout.css.report_errors");
|
||||
prefs.setBoolPref("layout.css.report_errors", false);
|
||||
|
||||
try {
|
||||
style.setProperty(name, val.value, val.priority);
|
||||
} finally {
|
||||
prefs.setBoolPref("layout.css.report_errors", prefVal);
|
||||
}
|
||||
return !!style.getPropertyValue(name);
|
||||
},
|
||||
};
|
||||
|
@ -1971,6 +1986,7 @@ function InplaceEditor(aOptions, aEvent)
|
|||
this._onBlur = this._onBlur.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onInput = this._onInput.bind(this);
|
||||
this._onKeyup = this._onKeyup.bind(this);
|
||||
|
||||
this._createInput();
|
||||
this._autosize();
|
||||
|
@ -1998,6 +2014,13 @@ function InplaceEditor(aOptions, aEvent)
|
|||
this.input.addEventListener("input", this._onInput, false);
|
||||
this.input.addEventListener("mousedown", function(aEvt) { aEvt.stopPropagation(); }, false);
|
||||
|
||||
this.warning = aOptions.warning;
|
||||
this.validate = aOptions.validate;
|
||||
|
||||
if (this.warning && this.validate) {
|
||||
this.input.addEventListener("keyup", this._onKeyup, false);
|
||||
}
|
||||
|
||||
if (aOptions.start) {
|
||||
aOptions.start(this, aEvent);
|
||||
}
|
||||
|
@ -2026,6 +2049,7 @@ InplaceEditor.prototype = {
|
|||
|
||||
this.input.removeEventListener("blur", this._onBlur, false);
|
||||
this.input.removeEventListener("keypress", this._onKeyPress, false);
|
||||
this.input.removeEventListener("keyup", this._onKeyup, false);
|
||||
this.input.removeEventListener("oninput", this._onInput, false);
|
||||
this._stopAutosize();
|
||||
|
||||
|
@ -2128,10 +2152,12 @@ InplaceEditor.prototype = {
|
|||
/**
|
||||
* Handle loss of focus by calling done if it hasn't been called yet.
|
||||
*/
|
||||
_onBlur: function InplaceEditor_onBlur(aEvent)
|
||||
_onBlur: function InplaceEditor_onBlur(aEvent, aDoNotClear)
|
||||
{
|
||||
this._apply();
|
||||
this._clear();
|
||||
if (!aDoNotClear) {
|
||||
this._clear();
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyPress: function InplaceEditor_onKeyPress(aEvent)
|
||||
|
@ -2193,11 +2219,26 @@ InplaceEditor.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the input field's keyup event.
|
||||
*/
|
||||
_onKeyup: function(aEvent) {
|
||||
// Validate the entered value.
|
||||
this.warning.hidden = this.validate(this.input.value);
|
||||
this._applied = false;
|
||||
this._onBlur(null, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle changes the input text.
|
||||
*/
|
||||
_onInput: function InplaceEditor_onInput(aEvent)
|
||||
{
|
||||
// Validate the entered value.
|
||||
if (this.warning && this.validate) {
|
||||
this.warning.hidden = this.validate(this.input.value);
|
||||
}
|
||||
|
||||
// Update size if we're autosizing.
|
||||
if (this._measurement) {
|
||||
this._updateSize();
|
||||
|
|
|
@ -170,6 +170,8 @@ function testEditProperty()
|
|||
|
||||
for each (let ch in "red;") {
|
||||
EventUtils.sendChar(ch, ruleDialog);
|
||||
is(propEditor.warning.hidden, ch == "d" || ch == ";",
|
||||
"warning triangle is hidden or shown as appropriate");
|
||||
}
|
||||
});
|
||||
for each (let ch in "border-color:") {
|
||||
|
@ -201,7 +203,6 @@ function testDisableProperty()
|
|||
|
||||
function finishTest()
|
||||
{
|
||||
ruleView.element.removeEventListener("CssRuleViewChanged", ruleViewChanged, false);
|
||||
ruleView.clear();
|
||||
ruleDialog.close();
|
||||
ruleDialog = ruleView = null;
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
- application menu item that opens the debugger UI. -->
|
||||
<!ENTITY debuggerMenu.label2 "Debugger">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerMenu.accesskey): This is accesskey for the
|
||||
- Tools meny entry of Debugger that opens the debugger UI. -->
|
||||
<!ENTITY debuggerMenu.accesskey "D">
|
||||
|
||||
<!-- LOCALIZATION NOTE (remoteDebuggerMenu.label): This is the label for the
|
||||
- application menu item that opens the remote debugger UI. -->
|
||||
<!ENTITY remoteDebuggerMenu.label "Remote Debugger">
|
||||
|
|
|
@ -380,6 +380,14 @@ dbgDesc=Manage debugger
|
|||
# set of commands that control the debugger.
|
||||
dbgManual=Commands to interrupt or resume the main thread, step in, out and over lines of code
|
||||
|
||||
# LOCALIZATION NOTE (dbgOpen) A very short string used to describe the function
|
||||
# of the dbg open command.
|
||||
dbgOpen=Open the debugger
|
||||
|
||||
# LOCALIZATION NOTE (dbgClose) A very short string used to describe the function
|
||||
# of the dbg close command.
|
||||
dbgClose=Close the debugger
|
||||
|
||||
# LOCALIZATION NOTE (dbgInterrupt) A very short string used to describe the
|
||||
# function of the dbg interrupt command.
|
||||
dbgInterrupt=Pauses the main thread
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
scrollbar {
|
||||
-moz-appearance: none !important;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
z-index: 2147483647;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
scrollbar[orient="vertical"] {
|
||||
-moz-margin-start: -8px;
|
||||
min-width: 8px;
|
||||
max-width: 8px;
|
||||
}
|
||||
|
||||
scrollbar[orient="horizontal"] {
|
||||
margin-top: -8px;
|
||||
min-height: 8px;
|
||||
max-height: 8px;
|
||||
}
|
||||
|
||||
scrollbar thumb {
|
||||
-moz-appearance: none !important;
|
||||
border-width: 0px !important;
|
||||
background-color: rgba(0,0,0,0.2) !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
scrollbar scrollbarbutton, scrollbar gripper {
|
||||
display: none;
|
||||
}
|
|
@ -165,6 +165,7 @@ browser.jar:
|
|||
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
|
||||
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
|
||||
skin/classic/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-16-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
scrollbar {
|
||||
-moz-appearance: none;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
border: 0px solid transparent;
|
||||
z-index: 2147483647;
|
||||
-moz-box-align: start;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
scrollbar[orient="vertical"] {
|
||||
-moz-margin-start: -8px;
|
||||
min-width: 8px;
|
||||
max-width: 8px;
|
||||
}
|
||||
|
||||
scrollbar[orient="horizontal"] {
|
||||
margin-top: -8px;
|
||||
min-height: 8px;
|
||||
max-height: 8px;
|
||||
}
|
||||
|
||||
thumb {
|
||||
-moz-appearance: none !important;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
border-radius: 3px;
|
||||
}
|
|
@ -235,6 +235,7 @@ browser.jar:
|
|||
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
|
||||
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
|
||||
skin/classic/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
scrollbar {
|
||||
-moz-appearance: none !important;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
z-index: 2147483647;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
scrollbar[orient="vertical"] {
|
||||
-moz-margin-start: -8px;
|
||||
min-width: 8px;
|
||||
max-width: 8px;
|
||||
}
|
||||
|
||||
scrollbar[orient="horizontal"] {
|
||||
margin-top: -8px;
|
||||
min-height: 8px;
|
||||
max-height: 8px;
|
||||
}
|
||||
|
||||
scrollbar thumb {
|
||||
-moz-appearance: none !important;
|
||||
border-width: 0px !important;
|
||||
background-color: rgba(0,0,0,0.2) !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
scrollbar scrollbarbutton, scrollbar gripper {
|
||||
display: none;
|
||||
}
|
|
@ -192,6 +192,7 @@ browser.jar:
|
|||
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
|
||||
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
|
||||
skin/classic/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
@ -395,6 +396,7 @@ browser.jar:
|
|||
skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
|
||||
skin/classic/aero/browser/devtools/responsive-background.png (devtools/responsive-background.png)
|
||||
skin/classic/aero/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
|
||||
skin/classic/aero/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/aero/browser/sync-throbber.png
|
||||
skin/classic/aero/browser/sync-16.png
|
||||
|
|
|
@ -487,6 +487,7 @@ function ThreadClient(aClient, aActor) {
|
|||
this._frameCache = [];
|
||||
this._scriptCache = {};
|
||||
this._pauseGrips = {};
|
||||
this._threadGrips = {};
|
||||
}
|
||||
|
||||
ThreadClient.prototype = {
|
||||
|
@ -861,30 +862,74 @@ ThreadClient.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Return an instance of LongStringClient for the given long string grip.
|
||||
* Get or create a long string client, checking the grip client cache if it
|
||||
* already exists.
|
||||
*
|
||||
* @param aGrip Object
|
||||
* The long string grip returned by the protocol.
|
||||
* @param aGripCacheName String
|
||||
* The property name of the grip client cache to check for existing
|
||||
* clients in.
|
||||
*/
|
||||
_longString: function TC__longString(aGrip, aGripCacheName) {
|
||||
if (aGrip.actor in this[aGripCacheName]) {
|
||||
return this[aGripCacheName][aGrip.actor];
|
||||
}
|
||||
|
||||
let client = new LongStringClient(this._client, aGrip);
|
||||
this[aGripCacheName][aGrip.actor] = client;
|
||||
return client;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an instance of LongStringClient for the given long string grip that
|
||||
* is scoped to the current pause.
|
||||
*
|
||||
* @param aGrip Object
|
||||
* The long string grip returned by the protocol.
|
||||
*/
|
||||
longString: function TC_longString(aGrip) {
|
||||
if (aGrip.actor in this._pauseGrips) {
|
||||
return this._pauseGrips[aGrip.actor];
|
||||
}
|
||||
pauseLongString: function TC_pauseLongString(aGrip) {
|
||||
return this._longString(aGrip, "_pauseGrips");
|
||||
},
|
||||
|
||||
let client = new LongStringClient(this._client, aGrip);
|
||||
this._pauseGrips[aGrip.actor] = client;
|
||||
return client;
|
||||
/**
|
||||
* Return an instance of LongStringClient for the given long string grip that
|
||||
* is scoped to the thread lifetime.
|
||||
*
|
||||
* @param aGrip Object
|
||||
* The long string grip returned by the protocol.
|
||||
*/
|
||||
threadLongString: function TC_threadLongString(aGrip) {
|
||||
return this._longString(aGrip, "_threadGrips");
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear and invalidate all the grip clients from the given cache.
|
||||
*
|
||||
* @param aGripCacheName
|
||||
* The property name of the grip cache we want to clear.
|
||||
*/
|
||||
_clearGripClients: function TC_clearGrips(aGripCacheName) {
|
||||
for each (let grip in this[aGripCacheName]) {
|
||||
grip.valid = false;
|
||||
}
|
||||
this[aGripCacheName] = {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate pause-lifetime grip clients and clear the list of
|
||||
* current grip clients.
|
||||
*/
|
||||
_clearPauseGrips: function TC_clearPauseGrips(aPacket) {
|
||||
for each (let grip in this._pauseGrips) {
|
||||
grip.valid = false;
|
||||
}
|
||||
this._pauseGrips = {};
|
||||
_clearPauseGrips: function TC_clearPauseGrips() {
|
||||
this._clearGripClients("_pauseGrips");
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate pause-lifetime grip clients and clear the list of
|
||||
* current grip clients.
|
||||
*/
|
||||
_clearThreadGrips: function TC_clearPauseGrips() {
|
||||
this._clearGripClients("_threadGrips");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -895,8 +940,17 @@ ThreadClient.prototype = {
|
|||
this._state = ThreadStateTypes[aPacket.type];
|
||||
this._clearFrames();
|
||||
this._clearPauseGrips();
|
||||
aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
|
||||
this._client._eventsEnabled && this.notify(aPacket.type, aPacket);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an instance of SourceClient for the given actor.
|
||||
*/
|
||||
source: function TC_source(aActor) {
|
||||
return new SourceClient(this._client, aActor);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
eventSource(ThreadClient.prototype);
|
||||
|
@ -1039,6 +1093,55 @@ LongStringClient.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A SourceClient provides a way to access the source text of a script.
|
||||
*
|
||||
* @param aClient DebuggerClient
|
||||
* The debugger client parent.
|
||||
* @param aActor String
|
||||
* The name of the source actor.
|
||||
*/
|
||||
function SourceClient(aClient, aActor) {
|
||||
this._actor = aActor;
|
||||
this._client = aClient;
|
||||
}
|
||||
|
||||
SourceClient.prototype = {
|
||||
/**
|
||||
* Get a long string grip for this SourceClient's source.
|
||||
*/
|
||||
source: function SC_source(aCallback) {
|
||||
let packet = {
|
||||
to: this._actor,
|
||||
type: "source"
|
||||
};
|
||||
this._client.request(packet, function (aResponse) {
|
||||
if (aResponse.error) {
|
||||
aCallback(aResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof aResponse.source === "string") {
|
||||
aCallback(aResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
let longString = this._client.activeThread.threadLongString(
|
||||
aResponse.source);
|
||||
longString.substring(0, longString.length, function (aResponse) {
|
||||
if (aResponse.error) {
|
||||
aCallback(aResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
aCallback({
|
||||
source: aResponse.substring
|
||||
});
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Breakpoint clients are used to remove breakpoints that are no longer used.
|
||||
*
|
||||
|
|
|
@ -546,10 +546,12 @@ ThreadActor.prototype = {
|
|||
if (!this._scripts[url][i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let script = {
|
||||
url: url,
|
||||
startLine: i,
|
||||
lineCount: this._scripts[url][i].lineCount
|
||||
lineCount: this._scripts[url][i].lineCount,
|
||||
source: this.sourceGrip(this._scripts[url][i], this)
|
||||
};
|
||||
scripts.push(script);
|
||||
}
|
||||
|
@ -781,13 +783,16 @@ ThreadActor.prototype = {
|
|||
|
||||
/**
|
||||
* Create a grip for the given debuggee value. If the value is an
|
||||
* object, will create a pause-lifetime actor.
|
||||
* object, will create an actor with the given lifetime.
|
||||
*/
|
||||
createValueGrip: function TA_createValueGrip(aValue) {
|
||||
createValueGrip: function TA_createValueGrip(aValue, aPool=false) {
|
||||
if (!aPool) {
|
||||
aPool = this._pausePool;
|
||||
}
|
||||
let type = typeof(aValue);
|
||||
|
||||
if (type === "string" && this._stringIsLong(aValue)) {
|
||||
return this.longStringGrip(aValue);
|
||||
return this.longStringGrip(aValue, aPool);
|
||||
}
|
||||
|
||||
if (type === "boolean" || type === "string" || type === "number") {
|
||||
|
@ -803,7 +808,7 @@ ThreadActor.prototype = {
|
|||
}
|
||||
|
||||
if (typeof(aValue) === "object") {
|
||||
return this.pauseObjectGrip(aValue);
|
||||
return this.objectGrip(aValue, aPool);
|
||||
}
|
||||
|
||||
dbg_assert(false, "Failed to provide a grip for: " + aValue);
|
||||
|
@ -881,26 +886,44 @@ ThreadActor.prototype = {
|
|||
*
|
||||
* @param aString String
|
||||
* The string we are creating a grip for.
|
||||
* @param aPool ActorPool
|
||||
* The actor pool where the new actor will be added.
|
||||
*/
|
||||
longStringGrip: function TA_longStringGrip(aString) {
|
||||
if (!this._pausePool) {
|
||||
throw new Error("LongString grip requested while not paused.");
|
||||
longStringGrip: function TA_longStringGrip(aString, aPool) {
|
||||
if (!aPool.longStringActors) {
|
||||
aPool.longStringActors = {};
|
||||
}
|
||||
|
||||
if (!this._pausePool.longStringActors) {
|
||||
this._pausePool.longStringActors = {};
|
||||
}
|
||||
|
||||
if (this._pausePool.longStringActors.hasOwnProperty(aString)) {
|
||||
return this._pausePool.longStringActors[aString].grip();
|
||||
if (aPool.longStringActors.hasOwnProperty(aString)) {
|
||||
return aPool.longStringActors[aString].grip();
|
||||
}
|
||||
|
||||
let actor = new LongStringActor(aString, this);
|
||||
this._pausePool.addActor(actor);
|
||||
this._pausePool.longStringActors[aString] = actor;
|
||||
aPool.addActor(actor);
|
||||
aPool.longStringActors[aString] = actor;
|
||||
return actor.grip();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a long string grip that is scoped to a pause.
|
||||
*
|
||||
* @param aString String
|
||||
* The string we are creating a grip for.
|
||||
*/
|
||||
pauseLongStringGrip: function TA_pauseLongStringGrip (aString) {
|
||||
return this.longStringGrip(aString, this._pausePool);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a long string grip that is scoped to a thread.
|
||||
*
|
||||
* @param aString String
|
||||
* The string we are creating a grip for.
|
||||
*/
|
||||
threadLongStringGrip: function TA_pauseLongStringGrip (aString) {
|
||||
return this.longStringGrip(aString, this._threadLifetimePool);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the string is long enough to use a LongStringActor instead
|
||||
* of passing the value directly over the protocol.
|
||||
|
@ -912,6 +935,26 @@ ThreadActor.prototype = {
|
|||
return aString.length >= DebuggerServer.LONG_STRING_LENGTH;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a source grip for the given script.
|
||||
*/
|
||||
sourceGrip: function TA_sourceGrip(aScript) {
|
||||
// TODO: Once we have Debugger.Source, this should be replaced with a
|
||||
// weakmap mapping Debugger.Source instances to SourceActor instances.
|
||||
if (!this.threadLifetimePool.sourceActors) {
|
||||
this.threadLifetimePool.sourceActors = {};
|
||||
}
|
||||
|
||||
if (this.threadLifetimePool.sourceActors[aScript.url]) {
|
||||
return this.threadLifetimePool.sourceActors[aScript.url].grip();
|
||||
}
|
||||
|
||||
let actor = new SourceActor(aScript, this);
|
||||
this.threadLifetimePool.addActor(actor);
|
||||
this.threadLifetimePool.sourceActors[aScript.url] = actor;
|
||||
return actor.grip();
|
||||
},
|
||||
|
||||
// JS Debugger API hooks.
|
||||
|
||||
/**
|
||||
|
@ -981,7 +1024,8 @@ ThreadActor.prototype = {
|
|||
type: "newScript",
|
||||
url: aScript.url,
|
||||
startLine: aScript.startLine,
|
||||
lineCount: aScript.lineCount
|
||||
lineCount: aScript.lineCount,
|
||||
source: this.sourceGrip(aScript, this)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -1133,6 +1177,176 @@ function update(aTarget, aNewAttrs) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* A SourceActor provides information about the source of a script.
|
||||
*
|
||||
* @param aScript Debugger.Script
|
||||
* The script whose source we are representing.
|
||||
* @param aThreadActor ThreadActor
|
||||
* The current thread actor.
|
||||
*/
|
||||
function SourceActor(aScript, aThreadActor) {
|
||||
this._threadActor = aThreadActor;
|
||||
this._script = aScript;
|
||||
}
|
||||
|
||||
SourceActor.prototype = {
|
||||
constructor: SourceActor,
|
||||
actorPrefix: "source",
|
||||
|
||||
get threadActor() { return this._threadActor; },
|
||||
|
||||
grip: function SA_grip() {
|
||||
return this.actorID;
|
||||
},
|
||||
|
||||
disconnect: function LSA_disconnect() {
|
||||
if (this.registeredPool && this.registeredPool.sourceActors) {
|
||||
delete this.registeredPool.sourceActors[this.actorID];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "source" packet.
|
||||
*/
|
||||
onSource: function SA_onSource(aRequest) {
|
||||
this
|
||||
._loadSource()
|
||||
.chainPromise(function(aSource) {
|
||||
return this._threadActor.createValueGrip(
|
||||
aSource, this.threadActor.threadLifetimePool);
|
||||
}.bind(this))
|
||||
.chainPromise(function (aSourceGrip) {
|
||||
return {
|
||||
from: this.actorID,
|
||||
source: aSourceGrip
|
||||
};
|
||||
}.bind(this))
|
||||
.trap(function (aError) {
|
||||
return {
|
||||
"from": this.actorID,
|
||||
"error": "loadSourceError",
|
||||
"message": "Could not load the source for " + this._script.url + "."
|
||||
};
|
||||
}.bind(this))
|
||||
.chainPromise(function (aPacket) {
|
||||
this.conn.send(aPacket);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a given string, encoded in a given character set, to unicode.
|
||||
* @param string aString
|
||||
* A string.
|
||||
* @param string aCharset
|
||||
* A character set.
|
||||
* @return string
|
||||
* A unicode string.
|
||||
*/
|
||||
_convertToUnicode: function SS__convertToUnicode(aString, aCharset) {
|
||||
// Decoding primitives.
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
|
||||
try {
|
||||
converter.charset = aCharset || "UTF-8";
|
||||
return converter.ConvertToUnicode(aString);
|
||||
} catch(e) {
|
||||
return aString;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a request to load the desired URL and returns a promise.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL we will request.
|
||||
* @returns Promise
|
||||
*
|
||||
* XXX: It may be better to use nsITraceableChannel to get to the sources
|
||||
* without relying on caching when we can (not for eval, etc.):
|
||||
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
|
||||
*/
|
||||
_loadSource: function SA__loadSource() {
|
||||
let promise = new Promise();
|
||||
let url = this._script.url;
|
||||
let scheme;
|
||||
try {
|
||||
scheme = Services.io.extractScheme(url);
|
||||
} catch (e) {
|
||||
// In the xpcshell tests, the script url is the absolute path of the test
|
||||
// file, which will make a malformed URI error be thrown. Add the file
|
||||
// scheme prefix ourselves.
|
||||
url = "file://" + url;
|
||||
scheme = Services.io.extractScheme(url);
|
||||
}
|
||||
|
||||
switch (scheme) {
|
||||
case "file":
|
||||
case "chrome":
|
||||
case "resource":
|
||||
try {
|
||||
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
|
||||
if (!Components.isSuccessCode(aStatus)) {
|
||||
promise.reject(new Error("Request failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
|
||||
promise.resolve(this._convertToUnicode(source));
|
||||
aStream.close();
|
||||
}.bind(this));
|
||||
} catch (ex) {
|
||||
promise.reject(new Error("Request failed"));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
let channel;
|
||||
try {
|
||||
channel = Services.io.newChannel(url, null, null);
|
||||
} catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
|
||||
// On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
|
||||
// newChannel won't be able to handle it.
|
||||
url = "file:///" + url;
|
||||
channel = Services.io.newChannel(url, null, null);
|
||||
}
|
||||
let chunks = [];
|
||||
let streamListener = {
|
||||
onStartRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
promise.reject("Request failed");
|
||||
}
|
||||
},
|
||||
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
|
||||
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
promise.reject("Request failed");
|
||||
return;
|
||||
}
|
||||
|
||||
promise.resolve(this._convertToUnicode(chunks.join(""),
|
||||
channel.contentCharset));
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
channel.loadFlags = channel.LOAD_FROM_CACHE;
|
||||
channel.asyncOpen(streamListener, null);
|
||||
break;
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
SourceActor.prototype.requestTypes = {
|
||||
"source": SourceActor.prototype.onSource
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an actor for the specified object.
|
||||
*
|
||||
|
|
|
@ -24,6 +24,8 @@ let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
|
|||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
addDebuggerToGlobal(this);
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/_Promise.jsm");
|
||||
|
||||
function dumpn(str) {
|
||||
if (wantLogging) {
|
||||
dump("DBG-SERVER: " + str + "\n");
|
||||
|
|
|
@ -35,8 +35,9 @@ function test_simple_listscripts()
|
|||
// Check the return value.
|
||||
do_check_true(!!script);
|
||||
do_check_eq(script.url, path);
|
||||
do_check_eq(script.startLine, 46);
|
||||
do_check_eq(script.startLine, gDebuggee.line0);
|
||||
do_check_eq(script.lineCount, 4);
|
||||
do_check_true(!!script.source);
|
||||
gThreadClient.resume(function () {
|
||||
finishClient(gClient);
|
||||
});
|
||||
|
|
|
@ -48,7 +48,7 @@ function test_longstring_grip()
|
|||
do_check_eq(grip.length, longString.length);
|
||||
do_check_eq(grip.initial, longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH));
|
||||
|
||||
let longStringClient = gThreadClient.longString(grip);
|
||||
let longStringClient = gThreadClient.pauseLongString(grip);
|
||||
longStringClient.substring(22, 28, function (aResponse) {
|
||||
try {
|
||||
do_check_eq(aResponse.substring, "monkey");
|
||||
|
|
|
@ -36,7 +36,7 @@ function test_longstring_grip()
|
|||
actor: "123fakeActor123",
|
||||
initial: ""
|
||||
};
|
||||
let longStringClient = gThreadClient.longString(fakeLongStringGrip);
|
||||
let longStringClient = gThreadClient.pauseLongString(fakeLongStringGrip);
|
||||
longStringClient.substring(22, 28, function (aResponse) {
|
||||
try {
|
||||
do_check_true(!!aResponse.error,
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/* -*- Mode: javascript; js-indent-level: 2; -*- */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var gDebuggee;
|
||||
var gClient;
|
||||
var gThreadClient;
|
||||
|
||||
// This test ensures that we can create SourceActors and SourceClients properly,
|
||||
// and that they can communicate over the protocol to fetch the source text for
|
||||
// a given script.
|
||||
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-grips");
|
||||
gDebuggee.eval(function stopMe(arg1) {
|
||||
debugger;
|
||||
}.toString());
|
||||
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function() {
|
||||
attachTestGlobalClientAndResume(gClient, "test-grips", function(aResponse, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
gThreadClient.addListener("unsolicitedPause", unsolicitedPauseListener);
|
||||
test_source();
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function unsolicitedPauseListener(aEvent, aPacket, aContinue) {
|
||||
gContinue = aContinue;
|
||||
}
|
||||
|
||||
function test_source()
|
||||
{
|
||||
DebuggerServer.LONG_STRING_LENGTH = 200;
|
||||
|
||||
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||
gThreadClient.getScripts(function (aResponse) {
|
||||
do_check_true(!!aResponse);
|
||||
do_check_true(!!aResponse.scripts);
|
||||
|
||||
let script = aResponse.scripts.filter(function (s) {
|
||||
return s.url.match(/test_source-01.js$/);
|
||||
})[0];
|
||||
|
||||
do_check_true(!!script);
|
||||
do_check_true(!!script.source);
|
||||
|
||||
let sourceClient = gThreadClient.source(script.source);
|
||||
sourceClient.source(function (aResponse) {
|
||||
do_check_true(!!aResponse);
|
||||
do_check_true(!aResponse.error);
|
||||
do_check_true(!!aResponse.source);
|
||||
|
||||
let f = do_get_file("test_source-01.js", false);
|
||||
let s = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
s.init(f, -1, -1, false);
|
||||
|
||||
do_check_eq(NetUtil.readInputStreamToString(s, s.available()),
|
||||
aResponse.source);
|
||||
|
||||
s.close();
|
||||
gThreadClient.resume(function () {
|
||||
finishClient(gClient);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
gDebuggee.eval('stopMe()');
|
||||
}
|
|
@ -64,4 +64,5 @@ tail =
|
|||
[test_longstringactor.js]
|
||||
[test_longstringgrips-01.js]
|
||||
[test_longstringgrips-02.js]
|
||||
[test_source-01.js]
|
||||
[test_breakpointstore.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче