Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-07-24 18:22:45 -04:00
commit 6cc28e31e6
52 changed files with 1528 additions and 383 deletions

View File

@ -23,27 +23,53 @@ let gDropTargetShim = {
/**
* Initializes the drop target shim.
*/
init: function DropTargetShim_init() {
let node = gGrid.node;
init: function () {
gGrid.node.addEventListener("dragstart", this, true);
},
// Add drag event handlers.
node.addEventListener("dragstart", this, true);
node.addEventListener("dragend", this, true);
/**
* Add all event listeners needed during a drag operation.
*/
_addEventListeners: function () {
gGrid.node.addEventListener("dragend", this);
let docElement = document.documentElement;
docElement.addEventListener("dragover", this);
docElement.addEventListener("dragenter", this);
docElement.addEventListener("drop", this);
},
/**
* Remove all event listeners that were needed during a drag operation.
*/
_removeEventListeners: function () {
gGrid.node.removeEventListener("dragend", this);
let docElement = document.documentElement;
docElement.removeEventListener("dragover", this);
docElement.removeEventListener("dragenter", this);
docElement.removeEventListener("drop", this);
},
/**
* Handles all shim events.
*/
handleEvent: function DropTargetShim_handleEvent(aEvent) {
handleEvent: function (aEvent) {
switch (aEvent.type) {
case "dragstart":
this._start(aEvent);
this._dragstart(aEvent);
break;
case "dragenter":
aEvent.preventDefault();
break;
case "dragover":
this._dragover(aEvent);
break;
case "drop":
this._drop(aEvent);
break;
case "dragend":
this._end(aEvent);
this._dragend(aEvent);
break;
}
},
@ -52,20 +78,76 @@ let gDropTargetShim = {
* Handles the 'dragstart' event.
* @param aEvent The 'dragstart' event.
*/
_start: function DropTargetShim_start(aEvent) {
_dragstart: function (aEvent) {
if (aEvent.target.classList.contains("newtab-link")) {
gGrid.lock();
// XXX bug 505521 - Listen for dragover on the document.
document.documentElement.addEventListener("dragover", this, false);
this._addEventListeners();
}
},
/**
* Handles the 'drag' event and determines the current drop target.
* @param aEvent The 'drag' event.
* Handles the 'dragover' event.
* @param aEvent The 'dragover' event.
*/
_drag: function DropTargetShim_drag(aEvent) {
_dragover: function (aEvent) {
// XXX bug 505521 - Use the dragover event to retrieve the
// current mouse coordinates while dragging.
let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
gDrag.drag(sourceNode._newtabSite, aEvent);
// Find the current drop target, if there's one.
this._updateDropTarget(aEvent);
// If we have a valid drop target,
// let the drag-and-drop service know.
if (this._lastDropTarget) {
aEvent.preventDefault();
}
},
/**
* Handles the 'drop' event.
* @param aEvent The 'drop' event.
*/
_drop: function (aEvent) {
// We're accepting all drops.
aEvent.preventDefault();
// Make sure to determine the current drop target
// in case the dragover event hasn't been fired.
this._updateDropTarget(aEvent);
// A site was successfully dropped.
this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
},
/**
* Handles the 'dragend' event.
* @param aEvent The 'dragend' event.
*/
_dragend: function (aEvent) {
if (this._lastDropTarget) {
if (aEvent.dataTransfer.mozUserCancelled) {
// The drag operation was cancelled.
this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
}
// Clean up.
this._lastDropTarget = null;
this._cellPositions = null;
}
gGrid.unlock();
this._removeEventListeners();
},
/**
* Tries to find the current drop target and will fire
* appropriate dragenter, dragexit, and dragleave events.
* @param aEvent The current drag event.
*/
_updateDropTarget: function (aEvent) {
// Let's see if we find a drop target.
let target = this._findDropTarget(aEvent);
@ -86,54 +168,12 @@ let gDropTargetShim = {
}
},
/**
* Handles the 'dragover' event as long as bug 505521 isn't fixed to get
* current mouse cursor coordinates while dragging.
* @param aEvent The 'dragover' event.
*/
_dragover: function DropTargetShim_dragover(aEvent) {
let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
gDrag.drag(sourceNode._newtabSite, aEvent);
this._drag(aEvent);
},
/**
* Handles the 'dragend' event.
* @param aEvent The 'dragend' event.
*/
_end: function DropTargetShim_end(aEvent) {
// Make sure to determine the current drop target in case the dragenter
// event hasn't been fired.
this._drag(aEvent);
if (this._lastDropTarget) {
if (aEvent.dataTransfer.mozUserCancelled) {
// The drag operation was cancelled.
this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
} else {
// A site was successfully dropped.
this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
}
// Clean up.
this._lastDropTarget = null;
this._cellPositions = null;
}
gGrid.unlock();
// XXX bug 505521 - Remove the document's dragover listener.
document.documentElement.removeEventListener("dragover", this, false);
},
/**
* Determines the current drop target by matching the dragged site's position
* against all cells in the grid.
* @return The currently hovered drop target or null.
*/
_findDropTarget: function DropTargetShim_findDropTarget() {
_findDropTarget: function () {
// These are the minimum intersection values - we want to use the cell if
// the site is >= 50% hovering its position.
let minWidth = gDrag.cellWidth / 2;
@ -174,13 +214,12 @@ let gDropTargetShim = {
* @param aType The event type.
* @param aTarget The target node that receives the event.
*/
_dispatchEvent:
function DropTargetShim_dispatchEvent(aEvent, aType, aTarget) {
_dispatchEvent: function (aEvent, aType, aTarget) {
let node = aTarget.node;
let event = document.createEvent("DragEvents");
event.initDragEvent(aType, true, true, window, 0, 0, 0, 0, 0, false, false,
// The event should not bubble to prevent recursion.
event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false,
false, false, 0, node, aEvent.dataTransfer);
node.dispatchEvent(event);

View File

@ -195,9 +195,6 @@ Site.prototype = {
case "dragstart":
gDrag.start(this, aEvent);
break;
case "drag":
gDrag.drag(this, aEvent);
break;
case "dragend":
gDrag.end(this, aEvent);
break;

View File

@ -156,7 +156,7 @@ let gTransformation = {
finish();
} else {
this.setSitePosition(aSite, targetPosition);
this._whenTransitionEnded(aSite.node, finish);
this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
}
},
@ -202,15 +202,19 @@ let gTransformation = {
* Listens for the 'transitionend' event on a given node and calls the given
* callback.
* @param aNode The node that is transitioned.
* @param aProperties The properties we'll wait to be transitioned.
* @param aCallback The callback to call when finished.
*/
_whenTransitionEnded:
function Transformation_whenTransitionEnded(aNode, aCallback) {
function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
aNode.addEventListener("transitionend", function onEnd() {
aNode.removeEventListener("transitionend", onEnd, false);
aCallback();
}, false);
let props = new Set(aProperties);
aNode.addEventListener("transitionend", function onEnd(e) {
if (props.has(e.propertyName)) {
aNode.removeEventListener("transitionend", onEnd);
aCallback();
}
});
},
/**
@ -236,8 +240,9 @@ let gTransformation = {
if (aCallback)
aCallback();
} else {
if (aCallback)
this._whenTransitionEnded(aNode, aCallback);
if (aCallback) {
this._whenTransitionEnded(aNode, ["opacity"], aCallback);
}
aNode.style.opacity = aOpacity;
}

View File

@ -566,19 +566,6 @@ let SessionStoreInternal = {
});
},
_initWindow: function ssi_initWindow(aWindow) {
if (aWindow) {
this.onLoad(aWindow);
} else if (this._loadState == STATE_STOPPED) {
// If init is being called with a null window, it's possible that we
// just want to tell sessionstore that a session is live (as is the case
// with starting Firefox with -private, for example; see bug 568816),
// so we should mark the load state as running to make sure that
// things like setBrowserState calls will succeed in restoring the session.
this._loadState = STATE_RUNNING;
}
},
/**
* Start tracking a window.
*
@ -586,11 +573,15 @@ let SessionStoreInternal = {
* initialized yet.
*/
init: function ssi_init(aWindow) {
if (!aWindow) {
throw new Error("init() must be called with a valid window.");
}
let self = this;
this.initService();
this._promiseInitialization.promise.then(
function onSuccess() {
self._initWindow(aWindow);
self.onLoad(aWindow);
}
);
},

View File

@ -423,6 +423,7 @@ function StackFrames() {
this._onResumed = this._onResumed.bind(this);
this._onFrames = this._onFrames.bind(this);
this._onFramesCleared = this._onFramesCleared.bind(this);
this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
this._afterFramesCleared = this._afterFramesCleared.bind(this);
this.evaluate = this.evaluate.bind(this);
}
@ -447,6 +448,7 @@ StackFrames.prototype = {
this.activeThread.addListener("resumed", this._onResumed);
this.activeThread.addListener("framesadded", this._onFrames);
this.activeThread.addListener("framescleared", this._onFramesCleared);
window.addEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false);
this._handleTabNavigation();
},
@ -462,6 +464,7 @@ StackFrames.prototype = {
this.activeThread.removeListener("resumed", this._onResumed);
this.activeThread.removeListener("framesadded", this._onFrames);
this.activeThread.removeListener("framescleared", this._onFramesCleared);
window.removeEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false);
},
/**
@ -594,12 +597,22 @@ StackFrames.prototype = {
// Make sure all the previous stackframes are removed before re-adding them.
DebuggerView.StackFrames.empty();
let previousBlackBoxed = null;
for (let frame of this.activeThread.cachedFrames) {
let { depth, where: { url, line } } = frame;
let { depth, where: { url, line }, isBlackBoxed } = frame;
let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
let frameTitle = StackFrameUtils.getFrameTitle(frame);
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
if (isBlackBoxed) {
if (previousBlackBoxed == url) {
continue;
}
previousBlackBoxed = url;
} else {
previousBlackBoxed = null;
}
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth, isBlackBoxed);
}
if (this.currentFrame == null) {
DebuggerView.StackFrames.selectedDepth = 0;
@ -626,6 +639,18 @@ StackFrames.prototype = {
window.setTimeout(this._afterFramesCleared, FRAME_STEP_CLEAR_DELAY);
},
/**
* Handler for the debugger's BlackBoxChange notification.
*/
_onBlackBoxChange: function() {
if (this.activeThread.state == "paused") {
// We have to clear out the existing frames and refetch them to get their
// updated black boxed status.
this.activeThread._clearFrames();
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
}
},
/**
* Called soon after the thread client's framescleared notification.
*/
@ -1000,6 +1025,27 @@ SourceScripts.prototype = {
window.dispatchEvent(document, "Debugger:AfterSourcesAdded");
},
/**
* Set the black boxed status of the given source.
*
* @param Object aSource
* The source form.
* @param bool aBlackBoxFlag
* True to black box the source, false to un-black box it.
*/
blackBox: function(aSource, aBlackBoxFlag) {
const sourceClient = this.activeThread.source(aSource);
sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](function({ error, message }) {
if (error) {
let msg = "Could not toggle black boxing for "
+ aSource.url + ": " + message;
dumpn(msg);
return void Cu.reportError(msg);
}
window.dispatchEvent(document, "Debugger:BlackBoxChange", sourceClient);
});
},
/**
* Gets a specified source's text.
*

View File

@ -18,6 +18,7 @@ function SourcesView() {
this._onSourceSelect = this._onSourceSelect.bind(this);
this._onSourceClick = this._onSourceClick.bind(this);
this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
this._onSourceCheck = this._onSourceCheck.bind(this);
this._onBreakpointClick = this._onBreakpointClick.bind(this);
this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
@ -34,9 +35,12 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
initialize: function() {
dumpn("Initializing the SourcesView");
this.widget = new SideMenuWidget(document.getElementById("sources"));
this.widget = new SideMenuWidget(document.getElementById("sources"), {
showCheckboxes: true
});
this.emptyText = L10N.getStr("noSourcesText");
this.unavailableText = L10N.getStr("noMatchingSourcesText");
this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
this._commandset = document.getElementById("debuggerCommands");
this._popupset = document.getElementById("debuggerPopupset");
@ -48,6 +52,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
this.widget.addEventListener("select", this._onSourceSelect, false);
this.widget.addEventListener("click", this._onSourceClick, false);
this.widget.addEventListener("check", this._onSourceCheck, false);
this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
@ -70,6 +75,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
this.widget.removeEventListener("select", this._onSourceSelect, false);
this.widget.removeEventListener("click", this._onSourceClick, false);
this.widget.removeEventListener("check", this._onSourceCheck, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
@ -109,6 +115,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this.push([label, url, group], {
staged: aOptions.staged, /* stage the item to be appended later? */
attachment: {
checkboxState: !aSource.isBlackBoxed,
checkboxTooltip: this._blackBoxCheckboxTooltip,
source: aSource
}
});
@ -639,6 +647,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
DebuggerView.Filtering.target = this;
},
/**
* The check listener for the sources container.
*/
_onSourceCheck: function({ detail: { checked }, target }) {
let item = this.getItemForElement(target);
DebuggerController.SourceScripts.blackBox(item.attachment.source, !checked);
},
/**
* The click listener for a breakpoint container.
*/

View File

@ -424,8 +424,10 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @param boolean aIsBlackBoxed
* Whether or not the frame is black boxed.
*/
addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
// Create the element node and menu entry for the stack frame item.
let frameView = this._createFrameView.apply(this, arguments);
let menuEntry = this._createMenuEntry.apply(this, arguments);
@ -471,29 +473,35 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @param boolean aIsBlackBoxed
* Whether or not the frame is black boxed.
* @return nsIDOMNode
* The stack frame view.
*/
_createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
let frameDetails =
SourceUtils.trimUrlLength(
SourceUtils.getSourceLabel(aSourceLocation),
STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
STACK_FRAMES_SOURCE_URL_TRIM_SECTION) + SEARCH_LINE_FLAG + aLineNumber;
let frameTitleNode = document.createElement("label");
frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
frameTitleNode.setAttribute("value", aFrameTitle);
let frameDetailsNode = document.createElement("label");
frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
frameDetailsNode.setAttribute("value", frameDetails);
_createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
let container = document.createElement("hbox");
container.id = "stackframe-" + aDepth;
container.className = "dbg-stackframe";
container.appendChild(frameTitleNode);
let frameDetails = SourceUtils.trimUrlLength(
SourceUtils.getSourceLabel(aSourceLocation),
STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
if (aIsBlackBoxed) {
container.classList.add("dbg-stackframe-black-boxed");
} else {
let frameTitleNode = document.createElement("label");
frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
frameTitleNode.setAttribute("value", aFrameTitle);
container.appendChild(frameTitleNode);
frameDetails += SEARCH_LINE_FLAG + aLineNumber;
}
let frameDetailsNode = document.createElement("label");
frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
frameDetailsNode.setAttribute("value", frameDetails);
container.appendChild(frameDetailsNode);
return container;
@ -510,10 +518,12 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @param boolean aIsBlackBoxed
* Whether or not the frame is black boxed.
* @return object
* An object containing the stack frame command and menu item.
*/
_createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
_createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
let frameDescription =
SourceUtils.trimUrlLength(
SourceUtils.getSourceLabel(aSourceLocation),

View File

@ -12,6 +12,10 @@ include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_TESTS = \
browser_dbg_aaa_run_first_leaktest.js \
browser_dbg_blackboxing-01.js \
browser_dbg_blackboxing-02.js \
browser_dbg_blackboxing-03.js \
browser_dbg_blackboxing-04.js \
browser_dbg_clean-exit.js \
browser_dbg_cmd.js \
browser_dbg_cmd_break.js \
@ -111,6 +115,11 @@ MOCHITEST_BROWSER_TESTS = \
$(NULL)
MOCHITEST_BROWSER_PAGES = \
browser_dbg_blackboxing.html \
blackboxing_blackboxme.js \
blackboxing_one.js \
blackboxing_two.js \
blackboxing_three.js \
browser_dbg_cmd_break.html \
browser_dbg_cmd.html \
testactors.js \

View File

@ -0,0 +1,9 @@
function blackboxme(fn) {
(function one() {
(function two() {
(function three() {
fn();
}());
}());
}());
}

View File

@ -0,0 +1 @@
function one() { two(); }

View File

@ -0,0 +1 @@
function three() { doDebuggerStatement(); }

View File

@ -0,0 +1 @@
function two() { three(); }

View File

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that if we black box a source and then refresh, it is still black boxed.
*/
const TAB_URL = EXAMPLE_URL + "binary_search.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
testBlackBoxSource();
});
}
function testBlackBoxSource() {
once(gDebugger, "Debugger:SourceShown", function () {
const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
ok(checkbox, "Should get the checkbox for black boxing the source");
ok(checkbox.checked, "Should not be black boxed by default");
once(gDebugger, "Debugger:BlackBoxChange", function (event) {
const sourceClient = event.detail;
ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
ok(!checkbox.checked, "The checkbox should no longer be checked.");
testBlackBoxReload();
});
checkbox.click();
});
}
function testBlackBoxReload() {
once(gDebugger, "Debugger:SourceShown", function () {
const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
ok(checkbox, "Should get the checkbox for black boxing the source");
ok(!checkbox.checked, "Should still be black boxed");
closeDebuggerAndFinish();
});
gDebuggee.location.reload();
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that black boxed frames are compressed into a single frame on the stack
* view.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js"
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
testBlackBoxSource();
});
}
function testBlackBoxSource() {
once(gDebugger, "Debugger:SourceShown", function () {
const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
ok(checkbox, "Should get the checkbox for blackBoxing the source");
once(gDebugger, "Debugger:BlackBoxChange", function (event) {
const sourceClient = event.detail;
ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
testBlackBoxStack();
});
checkbox.click();
});
}
function testBlackBoxStack() {
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", function () {
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 3,
"Should only get 3 frames");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
"And one of them should be the combined black boxed frames");
closeDebuggerAndFinish();
});
gDebuggee.runTest();
}
function getBlackBoxCheckbox(url) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\""
+ url + "\"] .side-menu-widget-item-checkbox");
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,96 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that black boxed frames are compressed into a single frame on the stack
* view when we are already paused.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js"
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
once(gDebugger, "Debugger:SourceShown", function () {
testBlackBoxStack();
});
});
}
function testBlackBoxStack() {
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", function () {
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 6,
"Should get 6 frames");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
"And none of them are black boxed");
testBlackBoxSource();
});
gDebuggee.runTest();
}
function testBlackBoxSource() {
const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
ok(checkbox, "Should get the checkbox for black boxing the source");
once(gDebugger, "Debugger:BlackBoxChange", function (event) {
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", function () {
const sourceClient = event.detail;
ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 3,
"Should only get 3 frames");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
"And one of them is the combined black boxed frames");
closeDebuggerAndFinish();
});
});
checkbox.click();
}
function getBlackBoxCheckbox(url) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\""
+ url + "\"] .side-menu-widget-item-checkbox");
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we get a stack frame for each black boxed source, not a single one
* for all of them.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
once(gDebugger, "Debugger:SourceShown", function () {
blackBoxSources();
});
});
}
function blackBoxSources() {
let timesFired = 0;
gDebugger.addEventListener("Debugger:BlackBoxChange", function _onBlackboxChange() {
if (++timesFired !== 3) {
return;
}
gDebugger.removeEventListener("Debugger:BlackBoxChange", _onBlackboxChange, false);
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", testStackFrames);
gDebuggee.one();
}, false);
getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_one.js").click();
getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_two.js").click();
getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_three.js").click();
}
function testStackFrames() {
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 4,
"Should get 4 frames (one -> two -> three -> doDebuggerStatement)");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
"And one, two, and three should each have their own black boxed frame.");
closeDebuggerAndFinish();
}
function getBlackBoxCheckbox(url) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\""
+ url + "\"] .side-menu-widget-item-checkbox");
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Browser Debugger Blackbox Test</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript" src="blackboxing_blackboxme.js"></script>
<script type="text/javascript" src="blackboxing_one.js"></script>
<script type="text/javascript" src="blackboxing_two.js"></script>
<script type="text/javascript" src="blackboxing_three.js"></script>
<script>
function runTest() {
blackboxme(doDebuggerStatement);
}
function doDebuggerStatement() {
debugger;
}
</script>
</head>
<body>
</body>
</html>

View File

@ -484,11 +484,13 @@ HTMLBreadcrumbs.prototype = {
if (originalLength > 0) {
stopNode = this.nodeHierarchy[originalLength - 1].node;
}
while (toAppend && toAppend.tagName && toAppend != stopNode) {
let button = this.buildButton(toAppend);
fragment.insertBefore(button, lastButtonInserted);
lastButtonInserted = button;
this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button});
while (toAppend && toAppend != stopNode) {
if (toAppend.tagName) {
let button = this.buildButton(toAppend);
fragment.insertBefore(button, lastButtonInserted);
lastButtonInserted = button;
this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button});
}
toAppend = toAppend.parentNode();
}
this.container.appendChild(fragment, this.container.firstChild);

View File

@ -156,14 +156,17 @@ InspectorPanel.prototype = {
/**
* Return a promise that will resolve to the default node for selection.
*/
_getDefaultNodeForSelection : function() {
_getDefaultNodeForSelection: function() {
if (this._defaultNode) {
return this._defaultNode;
}
let walker = this.walker;
// if available set body node as default selected node
// else set documentElement
return walker.querySelector(this.walker.rootNode, "body").then(front => {
return walker.getRootNode().then(rootNode => {
return walker.querySelector(rootNode, "body");
}).then(front => {
if (front) {
return front;
}
@ -285,33 +288,21 @@ InspectorPanel.prototype = {
*/
onNavigatedAway: function InspectorPanel_onNavigatedAway(event, payload) {
let newWindow = payload._navPayload || payload;
this.walker.release().then(null, console.error);
this.walker = null;
this._defaultNode = null;
this.selection.setNodeFront(null);
this.selection.setWalker(null);
this._destroyMarkup();
this.isDirty = false;
this.target.inspector.getWalker().then(walker => {
this._getDefaultNodeForSelection().then(defaultNode => {
if (this._destroyPromise) {
walker.release().then(null, console.error);
return;
}
this.selection.setNodeFront(defaultNode, "navigateaway");
this.walker = walker;
this.selection.setWalker(walker);
this._getDefaultNodeForSelection().then(defaultNode => {
if (this._destroyPromise) {
return;
}
this.selection.setNodeFront(defaultNode, "navigateaway");
this._initMarkup();
this.once("markuploaded", () => {
this.markup.expandNode(this.selection.nodeFront);
this.setupSearchBox();
});
this._initMarkup();
this.once("markuploaded", () => {
this.markup.expandNode(this.selection.nodeFront);
this.setupSearchBox();
});
});
},

View File

@ -74,7 +74,28 @@ function performTestComparisons2()
is(i.selection.node, div2, "selection matches div2 node");
is(getHighlitNode(), div2, "highlighter matches selection");
finish();
selectRoot();
}
function selectRoot()
{
// Select the root document element to clear the breadcrumbs.
let i = getActiveInspector();
i.selection.setNode(doc.documentElement);
i.once("inspector-updated", selectIframe);
}
function selectIframe()
{
// Directly select an element in an iframe (without navigating to it
// with mousemoves).
let i = getActiveInspector();
i.selection.setNode(div2);
i.once("inspector-updated", () => {
let breadcrumbs = i.breadcrumbs;
is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items");
finish();
});
}
function test() {

View File

@ -14,9 +14,12 @@
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
let require = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
let { Cc, Ci, Cu } = require("chrome");
let promise = require("sdk/core/promise");
let Telemetry = require("devtools/shared/telemetry");
let TargetFactory = require("devtools/framework/target").TargetFactory;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -27,16 +30,30 @@ Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
Cu.import("resource://gre/modules/jsdebugger.jsm");
Cu.import("resource:///modules/devtools/gDevTools.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
"resource:///modules/devtools/VariablesView.jsm");
"resource:///modules/devtools/VariablesView.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://gre/modules/devtools/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
"resource:///modules/devtools/VariablesViewController.jsm");
let Telemetry = devtools.require("devtools/shared/telemetry");
XPCOMUtils.defineLazyModuleGetter(this, "GripClient",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
XPCOMUtils.defineLazyGetter(this, "REMOTE_TIMEOUT", () =>
Services.prefs.getIntPref("devtools.debugger.remote-timeout")
);
const SCRATCHPAD_CONTEXT_CONTENT = 1;
const SCRATCHPAD_CONTEXT_BROWSER = 2;
const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
@ -237,12 +254,6 @@ var Scratchpad = {
*/
get browserWindow() Services.wm.getMostRecentWindow("navigator:browser"),
/**
* Reference to the last chrome window of type navigator:browser. We use this
* to check if the chrome window changed since the last code evaluation.
*/
_previousWindow: null,
/**
* Get the gBrowser object of the most recent browser window.
*/
@ -252,11 +263,6 @@ var Scratchpad = {
return recentWin ? recentWin.gBrowser : null;
},
/**
* Cached Cu.Sandbox object for the active tab content window object.
*/
_contentSandbox: null,
/**
* Unique name for the current Scratchpad instance. Used to distinguish
* Scratchpad windows between each other. See bug 661762.
@ -278,73 +284,6 @@ var Scratchpad = {
return this._sidebar;
},
/**
* Get the Cu.Sandbox object for the active tab content window object. Note
* that the returned object is cached for later reuse. The cached object is
* kept only for the current location in the current tab of the current
* browser window and it is reset for each context switch,
* navigator:browser window switch, tab switch or navigation.
*/
get contentSandbox()
{
if (!this.browserWindow) {
Cu.reportError(this.strings.
GetStringFromName("browserWindow.unavailable"));
return;
}
if (!this._contentSandbox ||
this.browserWindow != this._previousBrowserWindow ||
this._previousBrowser != this.gBrowser.selectedBrowser ||
this._previousLocation != this.gBrowser.contentWindow.location.href) {
let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
this._contentSandbox = new Cu.Sandbox(contentWindow,
{ sandboxPrototype: contentWindow, wantXrays: false,
sandboxName: 'scratchpad-content'});
this._contentSandbox.__SCRATCHPAD__ = this;
this._previousBrowserWindow = this.browserWindow;
this._previousBrowser = this.gBrowser.selectedBrowser;
this._previousLocation = contentWindow.location.href;
}
return this._contentSandbox;
},
/**
* Cached Cu.Sandbox object for the most recently active navigator:browser
* chrome window object.
*/
_chromeSandbox: null,
/**
* Get the Cu.Sandbox object for the most recently active navigator:browser
* chrome window object. Note that the returned object is cached for later
* reuse. The cached object is kept only for the current browser window and it
* is reset for each context switch or navigator:browser window switch.
*/
get chromeSandbox()
{
if (!this.browserWindow) {
Cu.reportError(this.strings.
GetStringFromName("browserWindow.unavailable"));
return;
}
if (!this._chromeSandbox ||
this.browserWindow != this._previousBrowserWindow) {
this._chromeSandbox = new Cu.Sandbox(this.browserWindow,
{ sandboxPrototype: this.browserWindow, wantXrays: false,
sandboxName: 'scratchpad-chrome'});
this._chromeSandbox.__SCRATCHPAD__ = this;
addDebuggerToGlobal(this._chromeSandbox);
this._previousBrowserWindow = this.browserWindow;
}
return this._chromeSandbox;
},
/**
* Drop the editor selection.
*/
@ -387,30 +326,38 @@ var Scratchpad = {
* @return Promise
* The promise for the script evaluation result.
*/
evalForContext: function SP_evaluateForContext(aString)
evaluate: function SP_evaluate(aString)
{
let deferred = promise.defer();
let connection;
if (this.executionContext == SCRATCHPAD_CONTEXT_CONTENT) {
connection = ScratchpadTab.consoleFor(this.gBrowser.selectedTab);
}
else {
connection = ScratchpadWindow.consoleFor(this.browserWindow);
}
// This setTimeout is temporary and will be replaced by DebuggerClient
// execution in a future patch (bug 825039). The purpose for using
// setTimeout is to ensure there is no accidental dependency on the
// promise being resolved synchronously, which can cause subtle bugs.
setTimeout(() => {
let chrome = this.executionContext != SCRATCHPAD_CONTEXT_CONTENT;
let sandbox = chrome ? this.chromeSandbox : this.contentSandbox;
let name = this.uniqueName;
let evalOptions = { url: this.uniqueName };
try {
let result = Cu.evalInSandbox(aString, sandbox, "1.8", name, 1);
deferred.resolve([aString, undefined, result]);
}
catch (ex) {
deferred.resolve([aString, ex]);
}
}, 0);
return connection.then(({ debuggerClient, webConsoleClient }) => {
let deferred = promise.defer();
return deferred.promise;
},
webConsoleClient.evaluateJS(aString, aResponse => {
this.debuggerClient = debuggerClient;
this.webConsoleClient = webConsoleClient;
if (aResponse.error) {
deferred.reject(aResponse);
}
else if (aResponse.exception) {
deferred.resolve([aString, aResponse]);
}
else {
deferred.resolve([aString, undefined, aResponse.result]);
}
}, evalOptions);
return deferred.promise;
});
},
/**
* Execute the selected text (if any) or the entire editor content in the
@ -422,7 +369,7 @@ var Scratchpad = {
execute: function SP_execute()
{
let selection = this.selectedText || this.getText();
return this.evalForContext(selection);
return this.evaluate(selection);
},
/**
@ -434,16 +381,22 @@ var Scratchpad = {
*/
run: function SP_run()
{
let execPromise = this.execute();
execPromise.then(([, aError, ]) => {
let deferred = promise.defer();
let reject = aReason => deferred.reject(aReason);
this.execute().then(([aString, aError, aResult]) => {
let resolve = () => deferred.resolve([aString, aError, aResult]);
if (aError) {
this.writeAsErrorComment(aError);
this.writeAsErrorComment(aError.exception).then(resolve, reject);
}
else {
this.deselect();
resolve();
}
});
return execPromise;
}, reject);
return deferred.promise;
},
/**
@ -463,11 +416,10 @@ var Scratchpad = {
let resolve = () => deferred.resolve([aString, aError, aResult]);
if (aError) {
this.writeAsErrorComment(aError);
resolve();
this.writeAsErrorComment(aError.exception).then(resolve, reject);
}
else if (!isObject(aResult)) {
this.writeAsComment(aResult);
else if (VariablesView.isPrimitive({ value: aResult })) {
this.writeAsComment(aResult.type || aResult);
resolve();
}
else {
@ -526,16 +478,39 @@ var Scratchpad = {
*/
display: function SP_display()
{
let execPromise = this.execute();
execPromise.then(([aString, aError, aResult]) => {
let deferred = promise.defer();
let reject = aReason => deferred.reject(aReason);
this.execute().then(([aString, aError, aResult]) => {
let resolve = () => deferred.resolve([aString, aError, aResult]);
if (aError) {
this.writeAsErrorComment(aError);
this.writeAsErrorComment(aError.exception).then(resolve, reject);
}
else if (VariablesView.isPrimitive({ value: aResult })) {
this.writeAsComment(aResult.type || aResult);
resolve();
}
else {
this.writeAsComment(aResult);
let gripClient = new GripClient(this.debuggerClient, aResult);
gripClient.getDisplayString(aResponse => {
if (aResponse.error) {
reject(aResponse);
}
else {
let string = aResponse.displayString;
if (string && string.type == "null") {
string = "Exception: " +
this.strings.GetStringFromName("stringConversionFailed");
}
this.writeAsComment(string);
resolve();
}
});
}
});
return execPromise;
}, reject);
return deferred.promise;
},
/**
@ -563,28 +538,119 @@ var Scratchpad = {
* Write out an error at the current insertion point as a block comment
* @param object aValue
* The Error object to write out the message and stack trace
* @return Promise
* The promise that indicates when writing the comment completes.
*/
writeAsErrorComment: function SP_writeAsErrorComment(aError)
{
let stack = "";
if (aError.stack) {
stack = aError.stack;
let deferred = promise.defer();
if (VariablesView.isPrimitive({ value: aError })) {
deferred.resolve(aError);
}
else if (aError.fileName) {
if (aError.lineNumber) {
stack = "@" + aError.fileName + ":" + aError.lineNumber;
else {
let reject = aReason => deferred.reject(aReason);
let gripClient = new GripClient(this.debuggerClient, aError);
// Because properties on Error objects are lazily added, this roundabout
// way of getting all the properties is required, rather than simply
// using getPrototypeAndProperties. See bug 724768.
let names = ["message", "stack", "fileName", "lineNumber"];
let promises = names.map(aName => {
let deferred = promise.defer();
gripClient.getProperty(aName, aResponse => {
if (aResponse.error) {
deferred.reject(aResponse);
}
else {
deferred.resolve({
name: aName,
descriptor: aResponse.descriptor
});
}
});
return deferred.promise;
});
{
// We also need to use getPrototypeAndProperties to retrieve any
// safeGetterValues in case this is a DOM error.
let deferred = promise.defer();
gripClient.getPrototypeAndProperties(aResponse => {
if (aResponse.error) {
deferred.reject(aResponse);
}
else {
deferred.resolve(aResponse);
}
});
promises.push(deferred.promise);
}
else {
stack = "@" + aError.fileName;
}
}
else if (aError.lineNumber) {
stack = "@" + aError.lineNumber;
promise.all(promises).then(aProperties => {
let error = {};
let safeGetters;
// Combine all the property descriptor/getter values into one object.
for (let property of aProperties) {
if (property.descriptor) {
error[property.name] = property.descriptor.value;
}
else if (property.safeGetterValues) {
safeGetters = property.safeGetterValues;
}
}
if (safeGetters) {
for (let key of Object.keys(safeGetters)) {
if (!error.hasOwnProperty(key)) {
error[key] = safeGetters[key].getterValue;
}
}
}
// Assemble the best possible stack we can given the properties we have.
let stack;
if (typeof error.stack == "string") {
stack = error.stack;
}
else if (typeof error.fileName == "number") {
stack = "@" + error.fileName;
if (typeof error.lineNumber == "number") {
stack += ":" + error.lineNumber;
}
}
else if (typeof error.lineNumber == "number") {
stack = "@" + error.lineNumber;
}
stack = stack ? "\n" + stack.replace(/\n$/, "") : "";
if (typeof error.message == "string") {
deferred.resolve(error.message + stack);
}
else {
gripClient.getDisplayString(aResult => {
if (aResult.error) {
deferred.reject(aResult);
}
else if (aResult.displayString.type == "null") {
deferred.resolve(stack);
}
else {
deferred.resolve(aResult.displayString + stack);
}
}, reject);
}
}, reject);
}
let newComment = "Exception: " + ( aError.message || aError) + ( stack == "" ? stack : "\n" + stack.replace(/\n$/, "") );
this.writeAsComment(newComment);
return deferred.promise.then(aMessage => {
console.log(aMessage);
this.writeAsComment("Exception: " + aMessage);
});
},
// Menu Operations
@ -1074,7 +1140,7 @@ var Scratchpad = {
*/
openWebConsole: function SP_openWebConsole()
{
let target = devtools.TargetFactory.forTab(this.gBrowser.selectedTab);
let target = TargetFactory.forTab(this.gBrowser.selectedTab);
gDevTools.showToolbox(target, "webconsole");
this.browserWindow.focus();
},
@ -1094,7 +1160,6 @@ var Scratchpad = {
content.setAttribute("checked", true);
this.executionContext = SCRATCHPAD_CONTEXT_CONTENT;
this.notificationBox.removeAllNotifications(false);
this.resetContext();
},
/**
@ -1120,19 +1185,6 @@ var Scratchpad = {
null,
this.notificationBox.PRIORITY_WARNING_HIGH,
null);
this.resetContext();
},
/**
* Reset the cached Cu.Sandbox object for the current context.
*/
resetContext: function SP_resetContext()
{
this._chromeSandbox = null;
this._contentSandbox = null;
this._previousWindow = null;
this._previousBrowser = null;
this._previousLocation = null;
},
/**
@ -1229,9 +1281,7 @@ var Scratchpad = {
}
this.initialized = true;
this._triggerObservers("Ready");
this.populateRecentFilesMenu();
PreferenceObserver.init();
},
@ -1299,8 +1349,6 @@ var Scratchpad = {
return;
}
this.resetContext();
// This event is created only after user uses 'reload and run' feature.
if (this._reloadAndRunEvent) {
this.gBrowser.selectedBrowser.removeEventListener("load",
@ -1313,6 +1361,12 @@ var Scratchpad = {
this.editor.destroy();
this.editor = null;
if (this._sidebar) {
this._sidebar.destroy();
this._sidebar = null;
}
this.webConsoleClient = null;
this.debuggerClient = null;
this.initialized = false;
},
@ -1480,13 +1534,152 @@ var Scratchpad = {
},
};
/**
* Represents the DebuggerClient connection to a specific tab as used by the
* Scratchpad.
*
* @param object aTab
* The tab to connect to.
*/
function ScratchpadTab(aTab)
{
this._tab = aTab;
}
let scratchpadTargets = new WeakMap();
/**
* Returns the object containing the DebuggerClient and WebConsoleClient for a
* given tab or window.
*
* @param object aSubject
* The tab or window to obtain the connection for.
* @return Promise
* The promise for the connection information.
*/
ScratchpadTab.consoleFor = function consoleFor(aSubject)
{
if (!scratchpadTargets.has(aSubject)) {
scratchpadTargets.set(aSubject, new this(aSubject));
}
return scratchpadTargets.get(aSubject).connect();
};
ScratchpadTab.prototype = {
/**
* The promise for the connection.
*/
_connector: null,
/**
* Initialize a debugger client and connect it to the debugger server.
*
* @return Promise
* The promise for the result of connecting to this tab or window.
*/
connect: function ST_connect()
{
if (this._connector) {
return this._connector;
}
let deferred = promise.defer();
this._connector = deferred.promise;
let connectTimer = setTimeout(() => {
deferred.reject({
error: "timeout",
message: Scratchpad.strings.GetStringFromName("connectionTimeout"),
});
}, REMOTE_TIMEOUT);
deferred.promise.then(() => clearTimeout(connectTimer));
this._attach().then(aTarget => {
let consoleActor = aTarget.form.consoleActor;
let client = aTarget.client;
client.attachConsole(consoleActor, [], (aResponse, aWebConsoleClient) => {
if (aResponse.error) {
reportError("attachConsole", aResponse);
deferred.reject(aResponse);
}
else {
deferred.resolve({
webConsoleClient: aWebConsoleClient,
debuggerClient: client
});
}
});
});
return deferred.promise;
},
/**
* Attach to this tab.
*
* @return Promise
* The promise for the TabTarget for this tab.
*/
_attach: function ST__attach()
{
let target = TargetFactory.forTab(this._tab);
return target.makeRemote().then(() => target);
},
};
/**
* Represents the DebuggerClient connection to a specific window as used by the
* Scratchpad.
*/
function ScratchpadWindow() {}
ScratchpadWindow.consoleFor = ScratchpadTab.consoleFor;
ScratchpadWindow.prototype = Heritage.extend(ScratchpadTab.prototype, {
/**
* Attach to this window.
*
* @return Promise
* The promise for the target for this window.
*/
_attach: function SW__attach()
{
let deferred = promise.defer();
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
let client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(() => {
client.listTabs(aResponse => {
if (aResponse.error) {
reportError("listTabs", aResponse);
deferred.reject(aResponse);
}
else {
deferred.resolve({ form: aResponse, client: client });
}
});
});
return deferred.promise;
}
});
/**
* Encapsulates management of the sidebar containing the VariablesView for
* object inspection.
*/
function ScratchpadSidebar(aScratchpad)
{
let ToolSidebar = devtools.require("devtools/framework/sidebar").ToolSidebar;
let ToolSidebar = require("devtools/framework/sidebar").ToolSidebar;
let tabbox = document.querySelector("#scratchpad-sidebar");
this._sidebar = new ToolSidebar(tabbox, this, "scratchpad");
this._scratchpad = aScratchpad;
@ -1526,14 +1719,30 @@ ScratchpadSidebar.prototype = {
let deferred = promise.defer();
let onTabReady = () => {
if (!this.variablesView) {
if (this.variablesView) {
this.variablesView.controller.releaseActors();
}
else {
let window = this._sidebar.getWindowForTab("variablesview");
let container = window.document.querySelector("#variables");
this.variablesView = new VariablesView(container, {
searchEnabled: true,
searchPlaceholder: this._scratchpad.strings
.GetStringFromName("propertiesFilterPlaceholder")
});
VariablesViewController.attach(this.variablesView, {
getGripClient: aGrip => {
return new GripClient(this._scratchpad.debuggerClient, aGrip);
},
getLongStringClient: aActor => {
return this._scratchpad.webConsoleClient.longString(aActor);
},
releaseActor: aActor => {
this._scratchpad.debuggerClient.release(aActor);
}
});
}
this._update(aObject).then(() => deferred.resolve());
};
@ -1571,6 +1780,21 @@ ScratchpadSidebar.prototype = {
}
},
/**
* Destroy the sidebar.
*
* @return Promise
* The promise that resolves when the sidebar is destroyed.
*/
destroy: function SS_destroy()
{
if (this.variablesView) {
this.variablesView.controller.releaseActors();
this.variablesView = null;
}
return this._sidebar.destroy();
},
/**
* Update the object currently inspected by the sidebar.
*
@ -1581,24 +1805,31 @@ ScratchpadSidebar.prototype = {
*/
_update: function SS__update(aObject)
{
let deferred = promise.defer();
let view = this.variablesView;
view.empty();
this.variablesView.rawObject = aObject;
let scope = view.addScope();
scope.expanded = true;
scope.locked = true;
// In the future this will work on remote values (bug 825039).
setTimeout(() => deferred.resolve(), 0);
return deferred.promise;
let container = scope.addItem();
return view.controller.expand(container, aObject);
}
};
/**
* Check whether a value is non-primitive.
* Report an error coming over the remote debugger protocol.
*
* @param string aAction
* The name of the action or method that failed.
* @param object aResponse
* The response packet that contains the error.
*/
function isObject(aValue)
function reportError(aAction, aResponse)
{
let type = typeof aValue;
return type == "object" ? aValue != null : type == "function";
Cu.reportError(aAction + " failed: " + aResponse.error + " " +
aResponse.message);
}

View File

@ -49,7 +49,6 @@
<command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
<command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
<command id="sp-cmd-reloadAndRun" oncommand="Scratchpad.reloadAndRun();"/>
<command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/>
<command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
<command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
<command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
@ -205,10 +204,6 @@
key="sp-key-reloadAndRun"
accesskey="&reloadAndRun.accesskey;"
command="sp-cmd-reloadAndRun"/>
<menuitem id="sp-text-resetContext"
label="&resetContext2.label;"
accesskey="&resetContext2.accesskey;"
command="sp-cmd-resetContext"/>
</menupopup>
</menu>
@ -305,11 +300,6 @@
accesskey="&display.accesskey;"
key="sp-key-display"
command="sp-cmd-display"/>
<menuseparator/>
<menuitem id="sp-text-resetContext"
label="&resetContext2.label;"
accesskey="&resetContext2.accesskey;"
command="sp-cmd-resetContext"/>
</menupopup>
</popupset>

View File

@ -36,6 +36,7 @@ MOCHITEST_BROWSER_FILES = \
browser_scratchpad_bug740948_reload_and_run.js \
browser_scratchpad_bug_661762_wrong_window_focus.js \
browser_scratchpad_bug_644413_modeline.js \
browser_scratchpad_bug807924_cannot_convert_to_string.js \
head.js \
# Disable test due to bug 807234 becoming basically permanent

View File

@ -0,0 +1,33 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
openScratchpad(runTests);
}, true);
content.location = "data:text/html;charset=utf8,test display of values" +
" which can't be converted to string in Scratchpad";
}
function runTests()
{
let sp = gScratchpadWindow.Scratchpad;
sp.setText("Object.create(null);");
sp.display().then(([, , aResult]) => {
is(sp.getText(),
"Object.create(null);\n" +
"/*\nException: Cannot convert value to string.\n*/",
"'Cannot convert value to string' comment is shown");
finish();
});
}

View File

@ -89,6 +89,6 @@ function testFocus(sw, hud) {
sp.run().then(function ([selection, error, result]) {
is(selection, "console.log('foo');", "selection is correct");
is(error, undefined, "error is correct");
is(result, undefined, "result is correct");
is(result.type, "undefined", "result is correct");
});
}

View File

@ -44,14 +44,6 @@ function runTests()
ok(!notificationBox.currentNotification,
"there is no notification in content context");
let dsp = sp.contentSandbox.__SCRATCHPAD__;
ok(sp.contentSandbox.__SCRATCHPAD__,
"there is a variable named __SCRATCHPAD__");
ok(sp.contentSandbox.__SCRATCHPAD__.editor,
"scratchpad is actually an instance of Scratchpad");
sp.setText("window.foobarBug636725 = 'aloha';");
ok(!content.wrappedJSObject.foobarBug636725,
@ -76,12 +68,6 @@ function runTests()
isnot(contentMenu.getAttribute("checked"), "true",
"content menuitem is not checked");
ok(sp.chromeSandbox.__SCRATCHPAD__,
"there is a variable named __SCRATCHPAD__");
ok(sp.chromeSandbox.__SCRATCHPAD__.editor,
"scratchpad is actually an instance of Scratchpad");
ok(notificationBox.currentNotification,
"there is a notification in browser context");
@ -107,7 +93,7 @@ function runTests()
"setText() worked with no end for the replace range");
},
then: function([, , result]) {
is(typeof result.addTab, "function",
is(result.class, "XULElement",
"chrome context has access to chrome objects");
}
},
@ -132,17 +118,6 @@ function runTests()
"global variable exists across two different executions");
}
},
{
method: "run",
prepare: function() {
sp.resetContext();
sp.setText("typeof foobarBug636725cache;");
},
then: function([, , result]) {
is(result, "undefined",
"global variable no longer exists after calling resetContext()");
}
},
{
method: "run",
prepare: function() {
@ -170,5 +145,10 @@ function runTests()
}
}];
runAsyncCallbackTests(sp, tests).then(finish);
runAsyncCallbackTests(sp, tests).then(() => {
sp.setBrowserContext();
sp.setText("delete foobarBug636725cache;" +
"delete foobarBug636725cache2;");
sp.run().then(finish);
});
}

View File

@ -29,7 +29,6 @@ function runTests()
"sp-text-run": "run",
"sp-text-inspect": "inspect",
"sp-text-display": "display",
"sp-text-resetContext": "resetContext",
"sp-menu-content": "setContentContext",
"sp-menu-browser": "setBrowserContext",
};

View File

@ -25,21 +25,28 @@ this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
*
* @param nsIDOMNode aNode
* The element associated with the widget.
* @param boolean aShowArrows
* Specifies if items in this container should display horizontal arrows.
* @param Object aOptions
* - showArrows: Specifies if items in this container should display
* horizontal arrows.
* - showCheckboxes: Specifies if items in this container should display
* checkboxes.
*/
this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
this.SideMenuWidget = function SideMenuWidget(aNode, aOptions={}) {
this.document = aNode.ownerDocument;
this.window = this.document.defaultView;
this._parent = aNode;
this._showArrows = aShowArrows;
let { showArrows, showCheckboxes } = aOptions;
this._showArrows = showArrows || false;
this._showCheckboxes = showCheckboxes || false;
// Create an internal scrollbox container.
this._list = this.document.createElement("scrollbox");
this._list.className = "side-menu-widget-container";
this._list.setAttribute("flex", "1");
this._list.setAttribute("orient", "vertical");
this._list.setAttribute("with-arrow", aShowArrows);
this._list.setAttribute("with-arrow", showArrows);
this._list.setAttribute("with-checkboxes", showCheckboxes);
this._list.setAttribute("tabindex", "0");
this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
@ -91,10 +98,12 @@ SideMenuWidget.prototype = {
* A tooltip attribute for the displayed item.
* @param string aGroup [optional]
* The group to place the displayed item into.
* @param Object aAttachment [optional]
* Extra data for the user.
* @return nsIDOMNode
* The element associated with the displayed item.
*/
insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "") {
insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "", aAttachment={}) {
aTooltip = NetworkHelper.convertToUnicode(unescape(aTooltip));
aGroup = NetworkHelper.convertToUnicode(unescape(aGroup));
@ -115,7 +124,7 @@ SideMenuWidget.prototype = {
(this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight);
let group = this._getMenuGroupForName(aGroup);
let item = this._getMenuItemForGroup(group, aContents, aTooltip);
let item = this._getMenuItemForGroup(group, aContents, aTooltip, aAttachment);
let element = item.insertSelfAt(aIndex);
if (this.maintainSelectionVisible) {
@ -397,14 +406,17 @@ SideMenuWidget.prototype = {
* The string or node displayed in the container.
* @param string aTooltip [optional]
* A tooltip attribute for the displayed item.
* @param object aAttachment [optional]
* The attachement object.
*/
_getMenuItemForGroup: function(aGroup, aContents, aTooltip) {
return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows);
_getMenuItemForGroup: function(aGroup, aContents, aTooltip, aAttachment) {
return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows, this._showCheckboxes, aAttachment);
},
window: null,
document: null,
_showArrows: false,
_showCheckboxes: false,
_parent: null,
_list: null,
_boxObject: null,
@ -527,14 +539,30 @@ SideMenuGroup.prototype = {
* The string or node displayed in the container.
* @param boolean aArrowFlag
* True if a horizontal arrow should be shown.
* @param boolean aCheckboxFlag
* True if a checkbox should be shown.
* @param object aAttachment [optional]
* The attachment object.
*/
function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) {
function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag, aCheckboxFlag, aAttachment={}) {
this.document = aGroup.document;
this.window = aGroup.window;
this.ownerView = aGroup;
// Show a horizontal arrow towards the content.
if (aArrowFlag) {
let makeCheckbox = () => {
let checkbox = this.document.createElement("checkbox");
checkbox.className = "side-menu-widget-item-checkbox";
checkbox.setAttribute("checked", aAttachment.checkboxState);
checkbox.setAttribute("tooltiptext", aAttachment.checkboxTooltip);
checkbox.addEventListener("command", function () {
ViewHelpers.dispatchEvent(checkbox, "check", {
checked: checkbox.checked,
});
}, false);
return checkbox;
};
if (aArrowFlag || aCheckboxFlag) {
let container = this._container = this.document.createElement("hbox");
container.className = "side-menu-widget-item";
container.setAttribute("tooltiptext", aTooltip);
@ -542,13 +570,22 @@ function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) {
let target = this._target = this.document.createElement("vbox");
target.className = "side-menu-widget-item-contents";
let arrow = this._arrow = this.document.createElement("hbox");
arrow.className = "side-menu-widget-item-arrow";
// Show a checkbox before the content.
if (aCheckboxFlag) {
let checkbox = this._checkbox = makeCheckbox();
container.appendChild(checkbox);
}
container.appendChild(target);
container.appendChild(arrow);
// Show a horizontal arrow towards the content.
if (aArrowFlag) {
let arrow = this._arrow = this.document.createElement("hbox");
arrow.className = "side-menu-widget-item-arrow";
container.appendChild(arrow);
}
}
// Skip a few redundant nodes when no horizontal arrow is shown.
// Skip a few redundant nodes when no horizontal arrow or checkbox is shown.
else {
let target = this._target = this._container = this.document.createElement("hbox");
target.className = "side-menu-widget-item side-menu-widget-item-contents";

View File

@ -11,6 +11,7 @@ const Cu = Components.utils;
const PANE_APPEARANCE_DELAY = 50;
const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
const WIDGET_FOCUSABLE_NODES = new Set(["vbox", "hbox"]);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -609,7 +610,7 @@ this.WidgetMethods = {
* - relaxed: true if this container should allow dupes & degenerates
* - attachment: some attached primitive/object for the item
* - attributes: a batch of attributes set to the displayed element
* - finalize: function invoked when the item is removed
* - finalize: function invokde when the item is removed
* @return Item
* The item associated with the displayed element if an unstaged push,
* undefined if the item was staged for a later commit.
@ -1117,16 +1118,21 @@ this.WidgetMethods = {
_focusChange: function(aDirection) {
let commandDispatcher = this._commandDispatcher;
let prevFocusedElement = commandDispatcher.focusedElement;
let currFocusedElement;
commandDispatcher.suppressFocusScroll = true;
commandDispatcher[aDirection]();
do {
commandDispatcher.suppressFocusScroll = true;
commandDispatcher[aDirection]();
currFocusedElement = commandDispatcher.focusedElement;
// Make sure the newly focused item is a part of this container. If the
// focus goes out of bounds, revert the previously focused item.
if (!this.getItemForElement(currFocusedElement)) {
prevFocusedElement.focus();
return false;
}
} while (!WIDGET_FOCUSABLE_NODES.has(currFocusedElement.tagName));
// Make sure the newly focused item is a part of this container.
// If the focus goes out of bounds, revert the previously focused item.
if (!this.getItemForElement(commandDispatcher.focusedElement)) {
prevFocusedElement.focus();
return false;
}
// Focus remained within bounds.
return true;
},
@ -1208,7 +1214,10 @@ this.WidgetMethods = {
*/
getItemForElement: function(aElement) {
while (aElement) {
let item = this._itemsByElement.get(aElement);
let item =
this._itemsByElement.get(aElement) ||
this._itemsByElement.get(aElement.nextElementSibling) ||
this._itemsByElement.get(aElement.previousElementSibling);
if (item) {
return item;
}

View File

@ -65,6 +65,11 @@ noMatchingGlobalsText=No matching globals
# when there are no scripts.
noSourcesText=This page has no sources.
# LOCALIZATION NOTE (blackBoxCheckboxTooltip) = The tooltip text to display when
# the user hovers over the checkbox used to toggle black boxing its associated
# source.
blackBoxCheckboxTooltip=Toggle black boxing
# LOCALIZATION NOTE (noMatchingSourcesText): The text to display in the
# sources menu when there are no matching scripts after filtering.
noMatchingSourcesText=No matching sources.

View File

@ -85,3 +85,11 @@ fileNoLongerExists.notification=This file no longer exists.
# LOCALIZATION NOTE (propertiesFilterPlaceholder): this is the text that
# appears in the filter text box for the properties view container.
propertiesFilterPlaceholder=Filter properties
# LOCALIZATION NOTE (stringConversionFailed): Happens when a value cannot be
# converted to a string for inspection.
stringConversionFailed=Cannot convert value to string.
# LOCALIZATION NOTE (connectionTimeout): message displayed when the Remote Scratchpad
# fails to connect to the server due to a timeout.
connectionTimeout=Connection timeout. Check the Error Console on both ends for potential error messages. Reopen the Scratchpad to try again.

View File

@ -17,6 +17,28 @@
-moz-border-start-color: transparent;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
padding: 0;
margin: 0 -4px 0 4px;
}
.side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-position: -24px 0;
width: 24px;
height: 24px;
border: 0;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
/* ListWidget items */
.list-widget-item {

View File

@ -19,6 +19,28 @@
-moz-border-start-color: transparent;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
padding: 0;
margin: 0 -4px 0 4px;
}
.side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-position: -24px 0;
width: 24px;
height: 24px;
border: 0;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
/* ListWidget items */
.list-widget-item {

View File

@ -17,6 +17,28 @@
-moz-border-start-color: transparent;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
padding: 0;
margin: 0 -4px 0 4px;
}
.side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-position: -24px 0;
width: 24px;
height: 24px;
border: 0;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
/* ListWidget items */
.list-widget-item {

View File

@ -0,0 +1,14 @@
var g1 = newGlobal();
var g2 = newGlobal();
g1.eval("function f1() { debugger; evaluate('debugger', {newContext:true}) }");
g2.eval("function f2() { f1(); assertEq(Number(this), 42) }");
g2.f1 = g1.f1;
var dbg = new Debugger(g1,g2);
dbg.onDebuggerStatement = function(frame) {
var target = frame.older;
dbg.onDebuggerStatement = function(frame) {
assertEq(Number(target.this.unsafeDereference()), 42);
}
}
g2.f2.call(42);

View File

@ -1060,7 +1060,7 @@ FormatFrame(JSContext *cx, const NonBuiltinScriptFrameIter &iter, char *buf, int
RootedValue thisVal(cx);
AutoPropertyDescArray thisProps(cx);
if (iter.computeThis()) {
if (iter.computeThis(cx)) {
thisVal = iter.thisv();
if (showThisProps && !thisVal.isPrimitive())
thisProps.fetch(&thisVal.toObject());

View File

@ -3820,7 +3820,7 @@ DebuggerFrame_getThis(JSContext *cx, unsigned argc, Value *vp)
RootedValue thisv(cx);
{
AutoCompartment ac(cx, iter.scopeChain());
if (!iter.computeThis())
if (!iter.computeThis(cx))
return false;
thisv = iter.thisv();
}
@ -4220,7 +4220,7 @@ DebuggerGenericEval(JSContext *cx, const char *fullMethodName, const Value &code
Rooted<Env *> env(cx);
if (iter) {
/* ExecuteInEnv requires 'fp' to have a computed 'this" value. */
if (!iter->computeThis())
if (!iter->computeThis(cx))
return false;
thisv = iter->thisv();
env = GetDebugScopeForFrame(cx, iter->abstractFramePtr());

View File

@ -1097,12 +1097,12 @@ ScriptFrameIter::argsObj() const
}
bool
ScriptFrameIter::computeThis() const
ScriptFrameIter::computeThis(JSContext *cx) const
{
JS_ASSERT(!done());
if (!isIon()) {
JS_ASSERT(data_.cx_);
return ComputeThis(data_.cx_, abstractFramePtr());
assertSameCompartment(cx, scopeChain());
return ComputeThis(cx, abstractFramePtr());
}
return true;
}

View File

@ -1520,7 +1520,7 @@ class ScriptFrameIter
ArgumentsObject &argsObj() const;
// Ensure that thisv is correct, see ComputeThis.
bool computeThis() const;
bool computeThis(JSContext *cx) const;
Value thisv() const;
Value returnValue() const;

View File

@ -494,7 +494,7 @@ BackgroundFileSaver::ProcessStateChange()
// When we are requested to append to an existing file, we should read the
// existing data and ensure we include it as part of the final hash.
if (append && !isContinuation) {
if (mDigestContext && append && !isContinuation) {
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
mActualTarget,

View File

@ -459,6 +459,34 @@ add_task(function test_setTarget_multiple()
destFile.remove(false);
});
add_task(function test_enableAppend()
{
// This test checks append mode with hashing disabled.
let destFile = getTempFile(TEST_FILE_NAME_1);
// Test the case where the file does not already exists first, then the case
// where the file already exists.
for (let i = 0; i < 2; i++) {
let saver = new BackgroundFileSaverOutputStream();
saver.enableAppend();
let completionPromise = promiseSaverComplete(saver);
saver.setTarget(destFile, false);
yield promiseCopyToSaver(TEST_DATA_LONG, saver, true);
saver.finish(Cr.NS_OK);
yield completionPromise;
// Verify results.
let expectedContents = (i == 0 ? TEST_DATA_LONG
: TEST_DATA_LONG + TEST_DATA_LONG);
yield promiseVerifyContents(destFile, expectedContents);
}
// Clean up.
destFile.remove(false);
});
add_task(function test_enableAppend_hash()
{
// This test checks append mode, also verifying that the computed hash

View File

@ -3340,6 +3340,18 @@
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'prototype' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_REMOTE_DISPLAYSTRING_MS": {
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'displayString' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_DISPLAYSTRING_MS": {
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'displayString' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_SUBSTRING_MS": {
"kind": "exponential",
"high": "10000",

View File

@ -1679,6 +1679,17 @@ GripClient.prototype = {
}, {
telemetry: "PROTOTYPE"
}),
/**
* Request the display string of the object.
*
* @param aOnResponse function Called with the request's response.
*/
getDisplayString: DebuggerClient.requester({
type: "displayString"
}, {
telemetry: "DISPLAYSTRING"
}),
};
/**

View File

@ -1581,6 +1581,14 @@ var WalkerActor = protocol.ActorClass({
onFrameLoad: function(window) {
let frame = window.frameElement;
if (!frame && !this.rootDoc) {
this.rootDoc = window.document;
this.rootNode = this.document();
this.queueMutation({
type: "newRoot",
target: this.rootNode.form()
});
}
let frameActor = this._refMap.get(frame);
if (!frameActor) {
return;
@ -1641,6 +1649,11 @@ var WalkerActor = protocol.ActorClass({
return;
}
if (this.rootDoc === doc) {
this.rootDoc = null;
this.rootNode = null;
}
this.queueMutation({
type: "documentUnload",
target: documentActor.actorID
@ -1673,6 +1686,7 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
autoCleanup: true,
initialize: function(client, form) {
this._rootNodeDeferred = promise.defer();
protocol.Front.prototype.initialize.call(this, client, form);
this._orphaned = new Set();
this._retainedOrphans = new Set();
@ -1684,8 +1698,19 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
// Update the object given a form representation off the wire.
form: function(json) {
this.actorID = json.actorID;
this.actorID = json.actor;
this.rootNode = types.getType("domnode").read(json.root, this);
this._rootNodeDeferred.resolve(this.rootNode);
},
/**
* Clients can use walker.rootNode to get the current root node of the
* walker, but during a reload the root node might be null. This
* method returns a promise that will resolve to the root node when it is
* set.
*/
getRootNode: function() {
return this._rootNodeDeferred.promise;
},
/**
@ -1793,8 +1818,19 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
let emitMutations = [];
for (let change of mutations) {
// The target is only an actorID, get the associated front.
let targetID = change.target;
let targetFront = this.get(targetID);
let targetID;
let targetFront;
if (change.type === "newRoot") {
this.rootNode = types.getType("domnode").read(change.target, this);
this._rootNodeDeferred.resolve(this.rootNode);
targetID = this.rootNode.actorID;
targetFront = this.rootNode;
} else {
targetID = change.target;
targetFront = this.get(targetID);
}
if (!targetFront) {
console.trace("Got a mutation for an unexpected actor: " + targetID + ", please file a bug on bugzilla.mozilla.org!");
continue;
@ -1848,6 +1884,11 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
}
}
} else if (change.type === "documentUnload") {
if (targetFront === this.rootNode) {
this.rootNode = null;
this._rootNodeDeferred = promise.defer();
}
// We try to give fronts instead of actorIDs, but these fronts need
// to be destroyed now.
emittedMutation.target = targetFront.actorID;
@ -1991,7 +2032,12 @@ var InspectorActor = protocol.ActorClass({
},
getWalker: method(function(options={}) {
if (this._walkerPromise) {
return this._walkerPromise;
}
let deferred = promise.defer();
this._walkerPromise = deferred.promise;
let window = this.window;
@ -2007,7 +2053,7 @@ var InspectorActor = protocol.ActorClass({
domReady();
}
return deferred.promise;
return this._walkerPromise;
}, {
request: {},
response: {

View File

@ -1966,6 +1966,42 @@ ObjectActor.prototype = {
descriptor: this._propertyDescriptor(aRequest.name) };
},
/**
* Handle a protocol request to provide the display string for the object.
*
* @param aRequest object
* The protocol request object.
*/
onDisplayString: function OA_onDisplayString(aRequest) {
let toString;
try {
// Attempt to locate the object's "toString" method.
let obj = this.obj;
do {
let desc = obj.getOwnPropertyDescriptor("toString");
if (desc) {
toString = desc.value;
break;
}
} while (obj = obj.proto)
} catch (e) {
dumpn(e);
}
let result = null;
if (toString && toString.callable) {
// If a toString method was found then call it on the object.
let ret = toString.call(this.obj).return;
if (typeof ret == "string") {
// Only use the result if it was a returned string.
result = ret;
}
}
return { from: this.actorID,
displayString: this.threadActor.createValueGrip(result) };
},
/**
* A helper method that creates a property descriptor for the provided object,
* properly formatted for sending in a protocol response.
@ -1989,6 +2025,10 @@ ObjectActor.prototype = {
};
}
if (!desc) {
return undefined;
}
let retval = {
configurable: desc.configurable,
enumerable: desc.enumerable
@ -2058,6 +2098,7 @@ ObjectActor.prototype.requestTypes = {
"prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
"prototype": ObjectActor.prototype.onPrototype,
"property": ObjectActor.prototype.onProperty,
"displayString": ObjectActor.prototype.onDisplayString,
"ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
"decompile": ObjectActor.prototype.onDecompile,
"release": ObjectActor.prototype.onRelease,
@ -2090,6 +2131,9 @@ update(PauseScopedObjectActor.prototype, {
onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
onDisplayString:
PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString),
onParameterNames:
PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),

View File

@ -407,7 +407,7 @@ StyleSheetActor.prototype = {
// get parent actor if this sheet was @imported
let parent = this.styleSheet.parentStyleSheet;
if (parent) {
form.parentActor = this.parentActor._sheets.get(parent);
form.parentActor = this.parentActor._sheets.get(parent).form();
}
try {

View File

@ -580,6 +580,7 @@ WebConsoleActor.prototype =
let evalOptions = {
bindObjectActor: aRequest.bindObjectActor,
frameActor: aRequest.frameActor,
url: aRequest.url,
};
let evalInfo = this.evalWithDebugger(input, evalOptions);
let evalResult = evalInfo.result;
@ -796,6 +797,8 @@ WebConsoleActor.prototype =
* evaluated.
* - result: the result of the evaluation.
* - helperResult: any result coming from a JSTerm helper function.
* - url: the url to evaluate the script as. Defaults to
* "debugger eval code".
*/
evalWithDebugger: function WCA_evalWithDebugger(aString, aOptions = {})
{
@ -894,12 +897,17 @@ WebConsoleActor.prototype =
// Ready to evaluate the string.
helpers.evalInput = aString;
let evalOptions;
if (typeof aOptions.url == "string") {
evalOptions = { url: aOptions.url };
}
let result;
if (frame) {
result = frame.evalWithBindings(aString, bindings);
result = frame.evalWithBindings(aString, bindings, evalOptions);
}
else {
result = dbgWindow.evalInGlobalWithBindings(aString, bindings);
result = dbgWindow.evalInGlobalWithBindings(aString, bindings, evalOptions);
}
let helperResult = helpers.helperResult;

View File

@ -23,6 +23,7 @@ MOCHITEST_CHROME_FILES = \
test_inspector-mutations-value.html \
test_inspector-release.html \
test_inspector-remove.html \
test_inspector-reload.html \
test_inspector-retain.html \
test_inspector-pseudoclass-lock.html \
test_inspector-traversal.html \

View File

@ -235,6 +235,10 @@ function isChildList(change) {
return change.type === "childList";
}
function isNewRoot(change) {
return change.type === "newRoot";
}
// Make sure an iframe's src attribute changed and then
// strip that mutation out of the list.
function assertSrcChange(mutations) {

View File

@ -0,0 +1,83 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
const Ci = Components.interfaces;
const promise = devtools.require("sdk/core/promise");
const inspector = devtools.require("devtools/server/actors/inspector");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gInspectee = null;
var gClient = null;
var gWalker = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
gInspectee = doc;
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
ok(walker, "getWalker() should return an actor.");
gClient = client;
gWalker = walker;
return inspector.getWalker();
}).then(walker => {
dump(walker.actorID + "\n");
ok(walker === gWalker, "getWalker() twice should return the same walker.");
}).then(runNextTest));
});
});
addTest(function testReload() {
let nodeFront;
let oldRootID = gWalker.rootNode.actorID;
// Load a node to populate the tree a bit.
promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
gInspectee.defaultView.location.reload();
return waitForMutation(gWalker, isNewRoot);
}).then(() => {
ok(gWalker.rootNode.actorID != oldRootID, "Root node should have changed.");
}).then(() => {
// Make sure we can still access the document
return gWalker.querySelector(gWalker.rootNode, "#a");
}).then(front => {
ok(front.actorID, "Got a new actor ID");
}).then(runNextTest));
});
addTest(function cleanup() {
delete gWalker;
delete gInspectee;
delete gClient;
runNextTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -101,6 +101,9 @@ WebConsoleClient.prototype = {
* - frameActor: a FrameActor ID. The FA holds a reference to
* a Debugger.Frame. This option allows you to evaluate the string in
* the frame of the given FA.
*
* - url: the url to evaluate the script as. Defaults to
* "debugger eval code".
*/
evaluateJS: function WCC_evaluateJS(aString, aOnResponse, aOptions = {})
{
@ -110,6 +113,7 @@ WebConsoleClient.prototype = {
text: aString,
bindObjectActor: aOptions.bindObjectActor,
frameActor: aOptions.frameActor,
url: aOptions.url,
};
this._client.request(packet, aOnResponse);
},

View File

@ -6219,8 +6219,34 @@ nsWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
}
GdkDisplay* display = gdk_window_get_display(mGdkWindow);
GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
gdk_display_warp_pointer(display, screen, aPoint.x, aPoint.y);
// When a button-release event is requested, create it here and put it in the
// event queue. This will not emit a motion event - this needs to be done
// explicitly *before* requesting a button-release. You will also need to wait
// for the motion event to be dispatched before requesting a button-release
// event to maintain the desired event order.
if (aNativeMessage == GDK_BUTTON_RELEASE) {
GdkEvent event;
memset(&event, 0, sizeof(GdkEvent));
event.type = (GdkEventType)aNativeMessage;
event.button.button = 1;
event.button.window = mGdkWindow;
event.button.time = GDK_CURRENT_TIME;
#if (MOZ_WIDGET_GTK == 3)
// Get device for event source
GdkDeviceManager *device_manager = gdk_display_get_device_manager(display);
event.button.device = gdk_device_manager_get_client_pointer(device_manager);
#endif
gdk_event_put(&event);
} else {
// We don't support specific events other than button-release. In case
// aNativeMessage != GDK_BUTTON_RELEASE we'll synthesize a motion event
// that will be emitted by gdk_display_warp_pointer().
GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
gdk_display_warp_pointer(display, screen, aPoint.x, aPoint.y);
}
return NS_OK;
}