Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-12-02 10:34:41 -05:00
commit 2866421e0f
152 changed files with 5093 additions and 734 deletions

View File

@ -1,4 +1,4 @@
{
"revision": "121f70034b7ef01836aa345f8ff37b61b96ac88e",
"revision": "cf23b263b4bbf1024210350f24c7e6eb5872a4be",
"repo_path": "/integration/gaia-central"
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1384551293000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1385765544000">
<emItems>
<emItem blockID="i454" id="sqlmoz@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -69,8 +69,8 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i348" id="{13c9f1f9-2322-4d5c-81df-6d4bf8476ba4}">
<versionRange minVersion="0" maxVersion="*" severity="1">
<emItem blockID="i488" id="jid1-4P0kohSJxU1qGg@jetpack">
<versionRange minVersion="1.2.50" maxVersion="1.2.50" severity="1">
</versionRange>
</emItem>
<emItem blockID="i486" id="xz123@ya456.com">
@ -102,14 +102,18 @@
<versionRange minVersion="3.4.1" maxVersion="3.4.1.194" severity="1">
</versionRange>
</emItem>
<emItem blockID="i474" id="{906000a4-88d9-4d52-b209-7a772970d91f}">
<versionRange minVersion="0" maxVersion="*" severity="3">
<emItem blockID="i100" id="{394DCBA4-1F92-4f8e-8EC9-8D2CB90CB69B}">
<versionRange minVersion="2.5.0" maxVersion="2.5.0" severity="1">
</versionRange>
</emItem>
<emItem blockID="i40" id="{28387537-e3f9-4ed7-860c-11e69af4a8a0}">
<versionRange minVersion="0.1" maxVersion="4.3.1.00" severity="1">
</versionRange>
</emItem>
<emItem blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
<versionRange minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
</versionRange>
</emItem>
<emItem blockID="i430" id="1chtw@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
@ -181,10 +185,6 @@
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i475" id="{B21F5E31-B8E8-41CD-B74C-168A71A10E49}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i11" id="yslow@yahoo-inc.com">
<versionRange minVersion="2.0.5" maxVersion="2.0.5">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
@ -256,6 +256,10 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i487" id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i142" id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
<versionRange minVersion="2.0.3" maxVersion="2.0.3">
</versionRange>
@ -311,8 +315,8 @@
<versionRange minVersion="0" maxVersion="*">
</versionRange>
</emItem>
<emItem blockID="i100" id="{394DCBA4-1F92-4f8e-8EC9-8D2CB90CB69B}">
<versionRange minVersion="2.5.0" maxVersion="2.5.0" severity="1">
<emItem blockID="i348" id="{13c9f1f9-2322-4d5c-81df-6d4bf8476ba4}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i338" id="{1FD91A9C-410C-4090-BBCC-55D3450EF433}">
@ -455,6 +459,10 @@
<versionRange minVersion="0" maxVersion="*">
</versionRange>
</emItem>
<emItem blockID="i474" id="{906000a4-88d9-4d52-b209-7a772970d91f}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i453" id="/^brasilescape.*\@facebook\.com$/">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
@ -582,8 +590,8 @@
<versionRange severity="3">
</versionRange>
</emItem>
<emItem blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
<versionRange minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
<emItem blockID="i491" id="{515b2424-5911-40bd-8a2c-bdb20286d8f5}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i444" id="fplayer@adobe.flash">
@ -662,6 +670,10 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i490" id="now.msn.com@services.mozilla.org">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i312" id="extension21804@extension21804.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
@ -775,6 +787,10 @@
<versionRange minVersion="0" maxVersion="*">
</versionRange>
</emItem>
<emItem blockID="i489" id="astrovia@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i68" id="flashupdate@adobe.com">
<versionRange minVersion="0" maxVersion="*">
</versionRange>

View File

@ -1447,11 +1447,6 @@
var uriIsAboutBlank = !aURI || aURI == "about:blank";
if (!aURI || isBlankPageURL(aURI))
t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
else
t.setAttribute("label", aURI);
t.setAttribute("crop", "end");
t.setAttribute("onerror", "this.removeAttribute('image');");
t.className = "tabbrowser-tab";
@ -1558,6 +1553,14 @@
// initialized by this point.
this.mPanelContainer.appendChild(notificationbox);
// We've waited until the tab is in the DOM to set the label. This
// allows the TabLabelModified event to be properly dispatched.
if (!aURI || isBlankPageURL(aURI)) {
t.label = this.mStringBundle.getString("tabs.emptyTabTitle");
} else {
t.label = aURI;
}
this.tabContainer.updateVisibility();
// wire up a progress listener for the new browser object.
@ -3345,8 +3348,7 @@
this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
var tab = this.firstChild;
tab.setAttribute("label",
this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
tab.setAttribute("crop", "end");
tab.setAttribute("onerror", "this.removeAttribute('image');");
this.adjustTabstrip();
@ -4652,7 +4654,8 @@
validate="never"
role="presentation"/>
<xul:label flex="1"
xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
anonid="tab-label"
xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected"
class="tab-text tab-label"
role="presentation"/>
<xul:toolbarbutton anonid="close-button"
@ -4663,6 +4666,32 @@
</content>
<implementation>
<property name="label">
<getter>
return this.getAttribute("label");
</getter>
<setter>
this.setAttribute("label", val);
let event = new CustomEvent("TabLabelModified", {
bubbles: true,
cancelable: true
});
this.dispatchEvent(event);
// Let listeners prevent synchronizing the actual label to the
// visible label (allowing them to override the visible label).
if (!event.defaultPrevented)
this.visibleLabel = val;
</setter>
</property>
<property name="visibleLabel">
<getter>
return this.getAttribute("visibleLabel");
</getter>
<setter>
this.setAttribute("visibleLabel", val);
</setter>
</property>
<property name="pinned" readonly="true">
<getter>
return this.getAttribute("pinned") == "true";

View File

@ -109,6 +109,7 @@ run-if = crashreporter
[browser_CTP_resize.js]
[browser_URLBarSetURI.js]
[browser_aboutHealthReport.js]
skip-if = os == "linux" # Bug 924307
[browser_aboutHome.js]
[browser_aboutSyncProgress.js]
[browser_addKeywordSearch.js]
@ -317,6 +318,7 @@ skip-if = os == "linux" # No tabs in titlebar on linux
[browser_urlbar_search_healthreport.js]
[browser_utilityOverlay.js]
[browser_visibleFindSelection.js]
[browser_visibleLabel.js]
[browser_visibleTabs.js]
[browser_visibleTabs_bookmarkAllPages.js]
[browser_visibleTabs_bookmarkAllTabs.js]

View File

@ -0,0 +1,94 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* Tests:
* verify that the visibleLabel attribute works
* verify the TabLabelModified event works for both existing and new tabs
*/
function test() {
waitForExplicitFinish();
registerCleanupFunction(function() {
gBrowser.removeCurrentTab({animate: false});
});
let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank",
{skipAnimation: true});
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
event.currentTarget.removeEventListener("load", onLoad, true);
executeSoon(afterLoad);
}, true);
}
function afterLoad() {
let tab = gBrowser.selectedTab;
let xulLabel = document.getAnonymousElementByAttribute(tab, "anonid",
"tab-label");
// Verify we're starting out on the right foot
is(tab.label, "New Tab", "Initial tab label is default");
is(xulLabel.value, "New Tab", "Label element is default");
is(tab.visibleLabel, "New Tab", "visibleLabel is default");
// Check that a normal label setting works correctly
tab.label = "Hello, world!";
is(tab.label, "Hello, world!", "tab label attribute set via tab.label");
is(xulLabel.value, "Hello, world!", "xul:label set via tab.label");
is(tab.visibleLabel, "Hello, world!", "visibleLabel set via tab.label");
// Check that setting visibleLabel only affects the label element
tab.visibleLabel = "Goodnight, Irene";
is(tab.label, "Hello, world!", "Tab.label unaffected by visibleLabel setter");
is(xulLabel.value, "Goodnight, Irene",
"xul:label set by visibleLabel setter");
is(tab.visibleLabel, "Goodnight, Irene",
"visibleLabel attribute set by visibleLabel setter");
// Check that setting the label property hits everything
tab.label = "One more label";
is(tab.label, "One more label",
"Tab label set via label property after diverging from visibleLabel");
is(xulLabel.value, "One more label",
"xul:label set via label property after diverging from visibleLabel");
is(tab.visibleLabel, "One more label",
"visibleLabel set from label property after diverging from visibleLabel");
tab.addEventListener("TabLabelModified", overrideTabLabel, true);
tab.label = "This won't be the visibleLabel";
}
function overrideTabLabel(aEvent) {
aEvent.target.removeEventListener("TabLabelModified", overrideTabLabel, true);
aEvent.preventDefault();
aEvent.stopPropagation();
aEvent.target.visibleLabel = "Handler set this as the visible label";
executeSoon(checkTabLabelModified);
}
function checkTabLabelModified() {
let tab = gBrowser.selectedTab;
let xulLabel = document.getAnonymousElementByAttribute(tab, "anonid",
"tab-label");
is(tab.label, "This won't be the visibleLabel",
"Tab label set via label property that triggered event");
is(xulLabel.value, "Handler set this as the visible label",
"xul:label set by TabLabelModified handler");
is(tab.visibleLabel, "Handler set this as the visible label",
"visibleLabel set by TabLabelModified handler");
gBrowser.removeCurrentTab({animate: false});
executeSoon(checkTabLabelModifiedOnNewTab);
}
function checkTabLabelModifiedOnNewTab() {
gBrowser.tabContainer.addEventListener("TabLabelModified",
handleTabLabelModifiedOnNewTab, true);
let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank",
{skipAnimation: true});
}
function handleTabLabelModifiedOnNewTab(aEvent) {
gBrowser.tabContainer.removeEventListener("TabLabelModified",
handleTabLabelModifiedOnNewTab, true);
ok(true, "Event received from new tab default being set");
executeSoon(finish);
}

View File

@ -77,10 +77,10 @@ browser.jar:
content/browser/sync/aboutSyncTabs.js (content/sync/aboutSyncTabs.js)
content/browser/sync/aboutSyncTabs.css (content/sync/aboutSyncTabs.css)
content/browser/sync/aboutSyncTabs-bindings.xml (content/sync/aboutSyncTabs-bindings.xml)
* content/browser/sync/setup.xul (content/sync/setup.xul)
content/browser/sync/setup.xul (content/sync/setup.xul)
content/browser/sync/addDevice.js (content/sync/addDevice.js)
content/browser/sync/addDevice.xul (content/sync/addDevice.xul)
* content/browser/sync/setup.js (content/sync/setup.js)
content/browser/sync/setup.js (content/sync/setup.js)
content/browser/sync/genericChange.xul (content/sync/genericChange.xul)
content/browser/sync/genericChange.js (content/sync/genericChange.js)
content/browser/sync/key.xhtml (content/sync/key.xhtml)

View File

@ -17,10 +17,11 @@
<footer id="PanelUI-footer">
<!-- The parentNode is used so that the footer is presented as the anchor
instead of just the button being the anchor. -->
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
exitLabel="&appMenuCustomizeExit.label;" tabindex="0"
oncommand="gCustomizeMode.toggle();"/>
<toolbarbutton id="PanelUI-help" label="&helpMenu.label;" tabindex="0"
oncommand="PanelUI.showHelpView(this.parentNode);"/>
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;" tabindex="0"
oncommand="gCustomizeMode.toggle();"/>
<toolbarbutton id="PanelUI-quit" tabindex="0"
#ifdef XP_WIN
label="&quitApplicationCmdWin.label;"

View File

@ -135,6 +135,10 @@ CustomizeMode.prototype = {
let mainView = window.PanelUI.mainView;
panelHolder.appendChild(mainView);
let customizeButton = document.getElementById("PanelUI-customize");
customizeButton.setAttribute("enterLabel", customizeButton.getAttribute("label"));
customizeButton.setAttribute("label", customizeButton.getAttribute("exitLabel"));
this._transitioning = true;
let customizer = document.getElementById("customization-container");
@ -255,6 +259,10 @@ CustomizeMode.prototype = {
window.PanelUI.setMainView(window.PanelUI.mainView);
window.PanelUI.menuButton.disabled = false;
let customizeButton = document.getElementById("PanelUI-customize");
customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label"));
customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel"));
// We have to use setAttribute/removeAttribute here instead of the
// property because the XBL property will be set later, and right
// now we'd be setting an expando, which breaks the XBL property.

View File

@ -34,6 +34,7 @@ const EVENTS = {
FETCHED_SCOPES: "Debugger:FetchedScopes",
FETCHED_VARIABLES: "Debugger:FetchedVariables",
FETCHED_PROPERTIES: "Debugger:FetchedProperties",
FETCHED_BUBBLE_PROPERTIES: "Debugger:FetchedBubbleProperties",
FETCHED_WATCH_EXPRESSIONS: "Debugger:FetchedWatchExpressions",
// When a breakpoint has been added or removed on the debugger server.
@ -75,6 +76,14 @@ const EVENTS = {
LAYOUT_CHANGED: "Debugger:LayoutChanged"
};
// Descriptions for what a stack frame represents after the debugger pauses.
const FRAME_TYPE = {
NORMAL: 0,
CONDITIONAL_BREAKPOINT_EVAL: 1,
WATCH_EXPRESSIONS_EVAL: 2,
PUBLIC_CLIENT_EVAL: 3
};
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
@ -86,9 +95,10 @@ Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
const Editor = require("devtools/sourceeditor/editor");
const promise = require("sdk/core/promise");
const Editor = require("devtools/sourceeditor/editor");
const DebuggerEditor = require("devtools/sourceeditor/debugger.js");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
XPCOMUtils.defineLazyModuleGetter(this, "Parser",
"resource:///modules/devtools/Parser.jsm");
@ -521,8 +531,7 @@ function StackFrames() {
StackFrames.prototype = {
get activeThread() DebuggerController.activeThread,
currentFrameDepth: -1,
_isWatchExpressionsEvaluation: false,
_isConditionalBreakpointEvaluation: false,
_currentFrameDescription: FRAME_TYPE.NORMAL,
_syncedWatchExpressions: null,
_currentWatchExpressions: null,
_currentBreakpointLocation: null,
@ -558,6 +567,7 @@ StackFrames.prototype = {
this.activeThread.removeListener("framescleared", this._onFramesCleared);
this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange);
this.activeThread.removeListener("prettyprintchange", this._onPrettyPrintChange);
clearNamedTimeout("frames-cleared");
},
/**
@ -611,10 +621,8 @@ StackFrames.prototype = {
* Handler for the thread client's resumed notification.
*/
_onResumed: function() {
DebuggerView.editor.clearDebugLocation();
// Prepare the watch expression evaluation string for the next pause.
if (!this._isWatchExpressionsEvaluation) {
if (this._currentFrameDescription != FRAME_TYPE.WATCH_EXPRESSIONS_EVAL) {
this._currentWatchExpressions = this._syncedWatchExpressions;
}
},
@ -640,16 +648,13 @@ StackFrames.prototype = {
// Make sure a breakpoint actually exists at the specified url and line.
let breakpointPromise = DebuggerController.Breakpoints._getAdded(breakLocation);
if (breakpointPromise) {
breakpointPromise.then(aBreakpointClient => {
if ("conditionalExpression" in aBreakpointClient) {
// Evaluating the current breakpoint's conditional expression will
// cause the stack frames to be cleared and active thread to pause,
// sending a 'clientEvaluated' packed and adding the frames again.
this.evaluate(aBreakpointClient.conditionalExpression, 0);
this._isConditionalBreakpointEvaluation = true;
waitForNextPause = true;
}
});
breakpointPromise.then(({ conditionalExpression: e }) => { if (e) {
// Evaluating the current breakpoint's conditional expression will
// cause the stack frames to be cleared and active thread to pause,
// sending a 'clientEvaluated' packed and adding the frames again.
this.evaluate(e, { depth: 0, meta: FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL });
waitForNextPause = true;
}});
}
}
// We'll get our evaluation of the current breakpoint's conditional
@ -657,8 +662,8 @@ StackFrames.prototype = {
if (waitForNextPause) {
return;
}
if (this._isConditionalBreakpointEvaluation) {
this._isConditionalBreakpointEvaluation = false;
if (this._currentFrameDescription == FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL) {
this._currentFrameDescription = FRAME_TYPE.NORMAL;
// If the breakpoint's conditional expression evaluation is falsy,
// automatically resume execution.
if (VariablesView.isFalsy({ value: this._currentEvaluation.return })) {
@ -673,8 +678,7 @@ StackFrames.prototype = {
if (watchExpressions) {
// Evaluation causes the stack frames to be cleared and active thread to
// pause, sending a 'clientEvaluated' packet and adding the frames again.
this.evaluate(watchExpressions, 0);
this._isWatchExpressionsEvaluation = true;
this.evaluate(watchExpressions, { depth: 0, meta: FRAME_TYPE.WATCH_EXPRESSIONS_EVAL });
waitForNextPause = true;
}
// We'll get our evaluation of the current watch expressions the next time
@ -682,8 +686,8 @@ StackFrames.prototype = {
if (waitForNextPause) {
return;
}
if (this._isWatchExpressionsEvaluation) {
this._isWatchExpressionsEvaluation = false;
if (this._currentFrameDescription == FRAME_TYPE.WATCH_EXPRESSIONS_EVAL) {
this._currentFrameDescription = FRAME_TYPE.NORMAL;
// If an error was thrown during the evaluation of the watch expressions,
// then at least one expression evaluation could not be performed. So
// remove the most recent watch expression and try again.
@ -697,6 +701,11 @@ StackFrames.prototype = {
// Make sure the debugger view panes are visible, then refill the frames.
DebuggerView.showInstrumentsPane();
this._refillFrames();
// No additional processing is necessary for this stack frame.
if (this._currentFrameDescription != FRAME_TYPE.NORMAL) {
this._currentFrameDescription = FRAME_TYPE.NORMAL;
}
},
/**
@ -714,29 +723,34 @@ StackFrames.prototype = {
let title = StackFrameUtils.getFrameTitle(frame);
DebuggerView.StackFrames.addFrame(title, location, line, depth, isBlackBoxed);
}
if (this.currentFrameDepth == -1) {
DebuggerView.StackFrames.selectedDepth = 0;
}
if (this.activeThread.moreFrames) {
DebuggerView.StackFrames.dirty = true;
}
DebuggerView.StackFrames.selectedDepth = Math.max(this.currentFrameDepth, 0);
DebuggerView.StackFrames.dirty = this.activeThread.moreFrames;
},
/**
* Handler for the thread client's framescleared notification.
*/
_onFramesCleared: function() {
this.currentFrameDepth = -1;
this._currentWatchExpressions = null;
this._currentBreakpointLocation = null;
this._currentEvaluation = null;
this._currentException = null;
this._currentReturnedValue = null;
switch (this._currentFrameDescription) {
case FRAME_TYPE.NORMAL:
this._currentEvaluation = null;
this._currentException = null;
this._currentReturnedValue = null;
break;
case FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL:
this._currentBreakpointLocation = null;
break;
case FRAME_TYPE.WATCH_EXPRESSIONS_EVAL:
this._currentWatchExpressions = null;
break;
}
// After each frame step (in, over, out), framescleared is fired, which
// forces the UI to be emptied and rebuilt on framesadded. Most of the times
// this is not necessary, and will result in a brief redraw flicker.
// To avoid it, invalidate the UI only after a short time if necessary.
window.setTimeout(this._afterFramesCleared, FRAME_STEP_CLEAR_DELAY);
setNamedTimeout("frames-cleared", FRAME_STEP_CLEAR_DELAY, this._afterFramesCleared);
},
/**
@ -744,6 +758,8 @@ StackFrames.prototype = {
*/
_onBlackBoxChange: function() {
if (this.activeThread.state == "paused") {
// Hack to avoid selecting the topmost frame after blackboxing a source.
this.currentFrameDepth = NaN;
this._refillFrames();
}
},
@ -765,6 +781,7 @@ StackFrames.prototype = {
if (this.activeThread.cachedFrames.length) {
return;
}
DebuggerView.editor.clearDebugLocation();
DebuggerView.StackFrames.empty();
DebuggerView.Sources.unhighlightBreakpoint();
DebuggerView.WatchExpressions.toggleContents(true);
@ -779,10 +796,8 @@ StackFrames.prototype = {
*
* @param number aDepth
* The depth of the frame in the stack.
* @param boolean aDontSwitchSources
* Flag on whether or not we want to switch the selected source.
*/
selectFrame: function(aDepth, aDontSwitchSources) {
selectFrame: function(aDepth) {
// Make sure the frame at the specified depth exists first.
let frame = this.activeThread.cachedFrames[this.currentFrameDepth = aDepth];
if (!frame) {
@ -795,15 +810,22 @@ StackFrames.prototype = {
return;
}
// Move the editor's caret to the proper url and line.
DebuggerView.setEditorLocation(where.url, where.line);
// Highlight the breakpoint at the specified url and line if it exists.
DebuggerView.Sources.highlightBreakpoint(where, { noEditorUpdate: true });
// Don't change the editor's location if the execution was paused by a
// public client evaluation. This is useful for adding overlays on
// top of the editor, like a variable inspection popup.
if (this._currentFrameDescription != FRAME_TYPE.PUBLIC_CLIENT_EVAL) {
// Move the editor's caret to the proper url and line.
DebuggerView.setEditorLocation(where.url, where.line);
// Highlight the breakpoint at the specified url and line if it exists.
DebuggerView.Sources.highlightBreakpoint(where, { noEditorUpdate: true });
}
// Don't display the watch expressions textbox inputs in the pane.
DebuggerView.WatchExpressions.toggleContents(false);
// Start recording any added variables or properties in any scope.
// Start recording any added variables or properties in any scope and
// clear existing scopes to create each one dynamically.
DebuggerView.Variables.createHierarchy();
// Clear existing scopes and create each one dynamically.
DebuggerView.Variables.empty();
// If watch expressions evaluation results are available, create a scope
@ -870,14 +892,42 @@ StackFrames.prototype = {
*
* @param string aExpression
* The expression to evaluate.
* @param number aFrame [optional]
* The frame depth used for evaluation.
* @param object aOptions [optional]
* Additional options for this client evaluation:
* - depth: the frame depth used for evaluation, 0 being the topmost.
* - meta: some meta-description for what this evaluation represents.
* @return object
* A promise that is resolved when the evaluation finishes,
* or rejected if there was no stack frame available or some
* other error occurred.
*/
evaluate: function(aExpression, aFrame = this.currentFrameDepth) {
let frame = this.activeThread.cachedFrames[aFrame];
if (frame) {
this.activeThread.eval(frame.actor, aExpression);
evaluate: function(aExpression, aOptions = {}) {
let depth = "depth" in aOptions ? aOptions.depth : this.currentFrameDepth;
let frame = this.activeThread.cachedFrames[depth];
if (frame == null) {
return promise.reject(new Error("No stack frame available."));
}
let deferred = promise.defer();
this.activeThread.addOneTimeListener("paused", (aEvent, aPacket) => {
let { type, frameFinished } = aPacket.why;
if (type == "clientEvaluated") {
if (!("terminated" in frameFinished)) {
deferred.resolve(frameFinished);
} else {
deferred.reject(new Error("The execution was abruptly terminated."));
}
} else {
deferred.reject(new Error("Active thread paused unexpectedly."));
}
});
let meta = "meta" in aOptions ? aOptions.meta : FRAME_TYPE.PUBLIC_CLIENT_EVAL;
this._currentFrameDescription = meta;
this.activeThread.eval(frame.actor, aExpression);
return deferred.promise;
},
/**
@ -990,6 +1040,7 @@ StackFrames.prototype = {
this._syncedWatchExpressions =
this._currentWatchExpressions = null;
}
this.currentFrameDepth = -1;
this._onFrames();
}

View File

@ -1265,6 +1265,259 @@ let SourceUtils = {
}
};
/**
* Functions handling the variables bubble UI.
*/
function VariableBubbleView() {
dumpn("VariableBubbleView was instantiated");
this._onMouseMove = this._onMouseMove.bind(this);
this._onMouseLeave = this._onMouseLeave.bind(this);
this._onMouseScroll = this._onMouseScroll.bind(this);
this._onPopupHiding = this._onPopupHiding.bind(this);
}
VariableBubbleView.prototype = {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function() {
dumpn("Initializing the VariableBubbleView");
this._tooltip = new Tooltip(document);
this._editorContainer = document.getElementById("editor");
this._tooltip.defaultPosition = EDITOR_VARIABLE_POPUP_POSITION;
this._tooltip.defaultShowDelay = EDITOR_VARIABLE_HOVER_DELAY;
this._tooltip.panel.addEventListener("popuphiding", this._onPopupHiding);
this._editorContainer.addEventListener("mousemove", this._onMouseMove, false);
this._editorContainer.addEventListener("mouseleave", this._onMouseLeave, false);
this._editorContainer.addEventListener("scroll", this._onMouseScroll, true);
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function() {
dumpn("Destroying the VariableBubbleView");
this._tooltip.panel.removeEventListener("popuphiding", this._onPopupHiding);
this._editorContainer.removeEventListener("mousemove", this._onMouseMove, false);
this._editorContainer.removeEventListener("mouseleave", this._onMouseLeave, false);
this._editorContainer.removeEventListener("scroll", this._onMouseScroll, true);
},
/**
* Searches for an identifier underneath the specified position in the
* source editor, and if found, opens a VariablesView inspection popup.
*
* @param number x, y
* The left/top coordinates where to look for an identifier.
*/
_findIdentifier: function(x, y) {
let editor = DebuggerView.editor;
// Calculate the editor's line and column at the current x and y coords.
let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
let hoveredOffset = editor.getOffset(hoveredPos);
let hoveredLine = hoveredPos.line;
let hoveredColumn = hoveredPos.ch;
// A source contains multiple scripts. Find the start index of the script
// containing the specified offset relative to its parent source.
let contents = editor.getText();
let location = DebuggerView.Sources.selectedValue;
let parsedSource = DebuggerController.Parser.get(contents, location);
let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
// If the script length is negative, we're not hovering JS source code.
if (scriptInfo.length == -1) {
return;
}
// Using the script offset, determine the actual line and column inside the
// script, to use when finding identifiers.
let scriptStart = editor.getPosition(scriptInfo.start);
let scriptLineOffset = scriptStart.line;
let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
let scriptLine = hoveredLine - scriptLineOffset;
let scriptColumn = hoveredColumn - scriptColumnOffset;
let identifierInfo = parsedSource.getIdentifierAt(scriptLine + 1, scriptColumn);
// If the info is null, we're not hovering any identifier.
if (!identifierInfo) {
return;
}
// Transform the line and column relative to the parsed script back
// to the context of the parent source.
let { start: identifierStart, end: identifierEnd } = identifierInfo.location;
let identifierCoords = {
line: identifierStart.line + scriptLineOffset,
column: identifierStart.column + scriptColumnOffset,
length: identifierEnd.column - identifierStart.column
};
// Evaluate the identifier in the current stack frame and show the
// results in a VariablesView inspection popup.
DebuggerController.StackFrames.evaluate(identifierInfo.evalString)
.then(frameFinished => {
if ("return" in frameFinished) {
this.showContents({
coords: identifierCoords,
evalPrefix: identifierInfo.evalString,
objectActor: frameFinished.return
});
} else {
let msg = "Evaluation has thrown for: " + identifierInfo.evalString;
console.warn(msg);
dumpn(msg);
}
})
.then(null, err => {
let msg = "Couldn't evaluate: " + err.message;
console.error(msg);
dumpn(msg);
});
},
/**
* Shows an inspection popup for a specified object actor grip.
*
* @param string object
* An object containing the following properties:
* - coords: the inspected identifier coordinates in the editor,
* containing the { line, column, length } properties.
* - evalPrefix: a prefix for the variables view evaluation macros.
* - objectActor: the value grip for the object actor.
*/
showContents: function({ coords, evalPrefix, objectActor }) {
let editor = DebuggerView.editor;
let { line, column, length } = coords;
// Highlight the function found at the mouse position.
this._markedText = editor.markText(
{ line: line - 1, ch: column },
{ line: line - 1, ch: column + length });
// If the grip represents a primitive value, use a more lightweight
// machinery to display it.
if (VariablesView.isPrimitive({ value: objectActor })) {
let className = VariablesView.getClass(objectActor);
let textContent = VariablesView.getString(objectActor);
this._tooltip.setTextContent([textContent], className, "plain");
} else {
this._tooltip.setVariableContent(objectActor, {
searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
searchEnabled: Prefs.variablesSearchboxVisible,
eval: aString => {
DebuggerController.StackFrames.evaluate(aString);
DebuggerView.VariableBubble.hideContents();
}
}, {
getEnvironmentClient: aObject => gThreadClient.environment(aObject),
getObjectClient: aObject => gThreadClient.pauseGrip(aObject),
simpleValueEvalMacro: this._getSimpleValueEvalMacro(evalPrefix),
getterOrSetterEvalMacro: this._getGetterOrSetterEvalMacro(evalPrefix),
overrideValueEvalMacro: this._getOverrideValueEvalMacro(evalPrefix)
}, {
fetched: (aEvent, aType) => {
if (aType == "properties") {
window.emit(EVENTS.FETCHED_BUBBLE_PROPERTIES);
}
}
});
}
// Calculate the x, y coordinates for the variable bubble anchor.
let identifierCenter = { line: line - 1, ch: column + length / 2 };
let anchor = editor.getCoordsFromPosition(identifierCenter);
this._tooltip.defaultOffsetX = anchor.left + EDITOR_VARIABLE_POPUP_OFFSET_X;
this._tooltip.defaultOffsetY = anchor.top + EDITOR_VARIABLE_POPUP_OFFSET_Y;
this._tooltip.show(this._editorContainer);
},
/**
* Hides the inspection popup.
*/
hideContents: function() {
clearNamedTimeout("editor-mouse-move");
this._tooltip.hide();
},
/**
* Functions for getting customized variables view evaluation macros.
*
* @param string aPrefix
* See the corresponding VariablesView.* functions.
*/
_getSimpleValueEvalMacro: function(aPrefix) {
return (item, string) =>
VariablesView.simpleValueEvalMacro(item, string, aPrefix);
},
_getGetterOrSetterEvalMacro: function(aPrefix) {
return (item, string) =>
VariablesView.getterOrSetterEvalMacro(item, string, aPrefix);
},
_getOverrideValueEvalMacro: function(aPrefix) {
return (item, string) =>
VariablesView.overrideValueEvalMacro(item, string, aPrefix);
},
/**
* The mousemove listener for the source editor.
*/
_onMouseMove: function({ clientX: x, clientY: y }) {
// Prevent the variable inspection popup from showing when the thread client
// is not paused, or while a popup is already visible.
if (gThreadClient && gThreadClient.state != "paused" || !this._tooltip.isHidden()) {
clearNamedTimeout("editor-mouse-move");
return;
}
// Allow events to settle down first. If the mouse hovers over
// a certain point in the editor long enough, try showing a variable bubble.
setNamedTimeout("editor-mouse-move",
EDITOR_VARIABLE_HOVER_DELAY, () => this._findIdentifier(x, y));
},
/**
* The mouseleave listener for the source editor container node.
*/
_onMouseLeave: function() {
clearNamedTimeout("editor-mouse-move");
},
/**
* The mousescroll listener for the source editor container node.
*/
_onMouseScroll: function() {
this.hideContents();
},
/**
* Listener handling the popup hiding event.
*/
_onPopupHiding: function({ target }) {
if (this._tooltip.panel != target) {
return;
}
if (this._markedText) {
this._markedText.clear();
this._markedText = null;
}
if (!this._tooltip.isEmpty()) {
this._tooltip.empty();
}
},
_editorContainer: null,
_markedText: null,
_tooltip: null
};
/**
* Functions handling the watch expressions UI.
*/
@ -2518,6 +2771,7 @@ LineResults.size = function() {
* Preliminary setup for the DebuggerView object.
*/
DebuggerView.Sources = new SourcesView();
DebuggerView.VariableBubble = new VariableBubbleView();
DebuggerView.WatchExpressions = new WatchExpressionsView();
DebuggerView.EventListeners = new EventListenersView();
DebuggerView.GlobalSearch = new GlobalSearchView();

View File

@ -136,6 +136,7 @@ ToolbarView.prototype = {
_onResumePressed: function() {
if (DebuggerController.activeThread.paused) {
let warn = DebuggerController._ensureResumptionOrder;
DebuggerController.StackFrames.currentFrameDepth = -1;
DebuggerController.activeThread.resume(warn);
} else {
DebuggerController.activeThread.interrupt();
@ -147,6 +148,7 @@ ToolbarView.prototype = {
*/
_onStepOverPressed: function() {
if (DebuggerController.activeThread.paused) {
DebuggerController.StackFrames.currentFrameDepth = -1;
DebuggerController.activeThread.stepOver();
}
},
@ -156,6 +158,7 @@ ToolbarView.prototype = {
*/
_onStepInPressed: function() {
if (DebuggerController.activeThread.paused) {
DebuggerController.StackFrames.currentFrameDepth = -1;
DebuggerController.activeThread.stepIn();
}
},
@ -165,6 +168,7 @@ ToolbarView.prototype = {
*/
_onStepOutPressed: function() {
if (DebuggerController.activeThread.paused) {
DebuggerController.StackFrames.currentFrameDepth = -1;
DebuggerController.activeThread.stepOut();
}
},
@ -477,6 +481,17 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
this.selectedItem = aItem => aItem.attachment.depth == aDepth;
},
/**
* Gets the currently selected stack frame's depth in this container.
* This will essentially be the opposite of |selectedIndex|, which deals
* with the position in the view, where the last item added is actually
* the bottommost, not topmost.
* @return number
*/
get selectedDepth() {
return this.selectedItem.attachment.depth;
},
/**
* Specifies if the active thread has more frames that need to be loaded.
*/
@ -1384,8 +1399,8 @@ FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototyp
}
for (let [location, contents] of aSources) {
let parserMethods = DebuggerController.Parser.get(location, contents);
let sourceResults = parserMethods.getNamedFunctionDefinitions(aToken);
let parsedSource = DebuggerController.Parser.get(contents, location);
let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken);
for (let scriptResult of sourceResults) {
for (let parseResult of scriptResult.parseResults) {

View File

@ -28,6 +28,10 @@ const SEARCH_FUNCTION_FLAG = "@";
const SEARCH_TOKEN_FLAG = "#";
const SEARCH_LINE_FLAG = ":";
const SEARCH_VARIABLE_FLAG = "*";
const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms
const EDITOR_VARIABLE_POPUP_OFFSET_X = 5; // px
const EDITOR_VARIABLE_POPUP_OFFSET_Y = 0; // px
const EDITOR_VARIABLE_POPUP_POSITION = "before_start";
/**
* Object defining the debugger view components.
@ -56,6 +60,7 @@ let DebuggerView = {
this.ChromeGlobals.initialize();
this.StackFrames.initialize();
this.Sources.initialize();
this.VariableBubble.initialize();
this.WatchExpressions.initialize();
this.EventListeners.initialize();
this.GlobalSearch.initialize();
@ -89,6 +94,7 @@ let DebuggerView = {
this.ChromeGlobals.destroy();
this.StackFrames.destroy();
this.Sources.destroy();
this.VariableBubble.destroy();
this.WatchExpressions.destroy();
this.EventListeners.destroy();
this.GlobalSearch.destroy();
@ -301,6 +307,7 @@ let DebuggerView = {
_setEditorText: function(aTextContent = "") {
this.editor.setMode(Editor.modes.text);
this.editor.setText(aTextContent);
this.editor.clearDebugLocation();
this.editor.clearHistory();
},
@ -416,8 +423,10 @@ let DebuggerView = {
* - columnOffset: column offset for the caret or debug location
* - noCaret: don't set the caret location at the specified line
* - noDebug: don't set the debug location at the specified line
* - align: string specifying whether to align the specified line
* at the "top", "center" or "bottom" of the editor
* - force: boolean allowing whether we can get the selected url's
* text again.
* text again
* @return object
* A promise that is resolved after the source text has been set.
*/
@ -426,6 +435,7 @@ let DebuggerView = {
if (!this.Sources.containsValue(aUrl)) {
return promise.reject(new Error("Unknown source for the specified URL."));
}
// If the line is not specified, default to the current frame's position,
// if available and the frame's url corresponds to the requested url.
if (!aLine) {
@ -440,11 +450,6 @@ let DebuggerView = {
let sourceItem = this.Sources.getItemByValue(aUrl);
let sourceForm = sourceItem.attachment.source;
// Once we change the editor location, it replaces editor's contents.
// This means that the debug location information is now obsolete, so
// we need to clear it. We set a new location below, in this function.
this.editor.clearDebugLocation();
// Make sure the requested source client is shown in the editor, then
// update the source editor's caret position and debug location.
return this._setEditorSource(sourceForm, aFlags).then(() => {
@ -453,20 +458,16 @@ let DebuggerView = {
if (aLine < 1) {
return;
}
if (aFlags.charOffset) {
aLine += this.editor.getPosition(aFlags.charOffset).line;
}
if (aFlags.lineOffset) {
aLine += aFlags.lineOffset;
}
if (!aFlags.noCaret) {
this.editor.setCursor({ line: aLine -1, ch: aFlags.columnOffset || 0 },
aFlags.align);
let location = { line: aLine -1, ch: aFlags.columnOffset || 0 };
this.editor.setCursor(location, aFlags.align);
}
if (!aFlags.noDebug) {
this.editor.setDebugLocation(aLine - 1);
}
@ -637,6 +638,7 @@ let DebuggerView = {
StackFrames: null,
Sources: null,
Variables: null,
VariableBubble: null,
WatchExpressions: null,
EventListeners: null,
editor: null,

View File

@ -50,6 +50,7 @@ support-files =
doc_pretty-print-2.html
doc_random-javascript.html
doc_recursion-stack.html
doc_scope-variable.html
doc_script-switching-01.html
doc_script-switching-02.html
doc_step-out.html
@ -67,7 +68,6 @@ support-files =
[browser_dbg_blackboxing-04.js]
[browser_dbg_blackboxing-05.js]
[browser_dbg_blackboxing-06.js]
[browser_dbg_file-reload.js]
[browser_dbg_breadcrumbs-access.js]
[browser_dbg_break-on-dom-01.js]
[browser_dbg_break-on-dom-02.js]
@ -94,10 +94,13 @@ skip-if = true
[browser_dbg_cmd-dbg.js]
[browser_dbg_conditional-breakpoints-01.js]
[browser_dbg_conditional-breakpoints-02.js]
[browser_dbg_controller-evaluate-01.js]
[browser_dbg_controller-evaluate-02.js]
[browser_dbg_debugger-statement.js]
[browser_dbg_editor-contextmenu.js]
[browser_dbg_editor-mode.js]
[browser_dbg_event-listeners.js]
[browser_dbg_file-reload.js]
[browser_dbg_function-display-name.js]
[browser_dbg_globalactor.js]
[browser_dbg_host-layout.js]
@ -114,6 +117,16 @@ skip-if = true
[browser_dbg_navigation.js]
[browser_dbg_on-pause-highlight.js]
[browser_dbg_panel-size.js]
[browser_dbg_parser-01.js]
[browser_dbg_parser-02.js]
[browser_dbg_parser-03.js]
[browser_dbg_parser-04.js]
[browser_dbg_parser-05.js]
[browser_dbg_parser-06.js]
[browser_dbg_parser-07.js]
[browser_dbg_parser-08.js]
[browser_dbg_parser-09.js]
[browser_dbg_parser-10.js]
[browser_dbg_pause-exceptions-01.js]
[browser_dbg_pause-exceptions-02.js]
[browser_dbg_pause-resume.js]
@ -168,6 +181,7 @@ skip-if = true
[browser_dbg_stack-04.js]
[browser_dbg_stack-05.js]
[browser_dbg_stack-06.js]
[browser_dbg_stack-07.js]
[browser_dbg_step-out.js]
[browser_dbg_tabactor-01.js]
[browser_dbg_tabactor-02.js]
@ -196,6 +210,14 @@ skip-if = true
[browser_dbg_variables-view-frozen-sealed-nonext.js]
[browser_dbg_variables-view-hide-non-enums.js]
[browser_dbg_variables-view-large-array-buffer.js]
[browser_dbg_variables-view-popup-01.js]
[browser_dbg_variables-view-popup-02.js]
[browser_dbg_variables-view-popup-03.js]
[browser_dbg_variables-view-popup-04.js]
[browser_dbg_variables-view-popup-05.js]
[browser_dbg_variables-view-popup-06.js]
[browser_dbg_variables-view-popup-07.js]
[browser_dbg_variables-view-popup-08.js]
[browser_dbg_variables-view-reexpand-01.js]
[browser_dbg_variables-view-reexpand-02.js]
[browser_dbg_variables-view-webidl.js]

View File

@ -242,7 +242,7 @@ function test() {
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let coords = gEditor.cursorCoords({ line: 4, ch: 0 });
let coords = gEditor.getCoordsFromPosition({ line: 4, ch: 0 });
let rect = iframe.getBoundingClientRect();
let left = rect.left + 10;
let top = rect.top + coords.top + 4;

View File

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the public evaluation API from the debugger controller.
*/
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let frames = win.DebuggerController.StackFrames;
let framesView = win.DebuggerView.StackFrames;
let sources = win.DebuggerController.SourceScripts;
let sourcesView = win.DebuggerView.Sources;
let editorView = win.DebuggerView.editor;
let events = win.EVENTS;
function checkView(frameDepth, selectedSource, caretLine, editorText) {
is(win.gThreadClient.state, "paused",
"Should only be getting stack frames while paused.");
is(framesView.itemCount, 4,
"Should have four frames.");
is(framesView.selectedDepth, frameDepth,
"The correct frame is selected in the widget.");
is(sourcesView.selectedIndex, selectedSource,
"The correct source is selected in the widget.");
ok(isCaretPos(panel, caretLine),
"Editor caret location is correct.");
is(editorView.getText().search(editorText[0]), editorText[1],
"The correct source is not displayed.");
}
// Cache the sources text to avoid having to wait for their retrieval.
yield promise.all(sourcesView.attachments.map(e => sources.getText(e.source)));
is(sources._cache.size, 2, "There should be two cached sources in the cache.");
// Eval while not paused.
try {
yield frames.evaluate("foo");
} catch (error) {
is(error.message, "No stack frame available.",
"Evaluating shouldn't work while the debuggee isn't paused.");
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.firstCall());
yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6);
checkView(0, 1, 6, [/secondCall/, 118]);
// Eval in the topmost frame, while paused.
let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
let result = yield frames.evaluate("foo");
ok(!result.throw, "The evaluation hasn't thrown.");
is(result.return.type, "object", "The evaluation return type is correct.");
is(result.return.class, "Function", "The evaluation return class is correct.");
yield updatedView;
checkView(0, 1, 6, [/secondCall/, 118]);
ok(true, "Evaluating in the topmost frame works properly.");
// Eval in a different frame, while paused.
let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
try {
yield frames.evaluate("foo", { depth: 3 }); // oldest frame
} catch (result) {
is(result.return.type, "object", "The evaluation thrown type is correct.");
is(result.return.class, "Error", "The evaluation thrown class is correct.");
ok(!result.return, "The evaluation hasn't returned.");
}
yield updatedView;
checkView(0, 1, 6, [/secondCall/, 118]);
ok(true, "Evaluating in a custom frame works properly.");
// Eval in a non-existent frame, while paused.
waitForDebuggerEvents(panel, events.FETCHED_SCOPES).then(() => {
ok(false, "Shouldn't have updated the view when trying to evaluate " +
"an expression in a non-existent stack frame.");
});
try {
yield frames.evaluate("foo", { depth: 4 }); // non-existent frame
} catch (error) {
is(error.message, "No stack frame available.",
"Evaluating shouldn't work if the specified frame doesn't exist.");
}
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,66 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the public evaluation API from the debugger controller.
*/
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let frames = win.DebuggerController.StackFrames;
let framesView = win.DebuggerView.StackFrames;
let sources = win.DebuggerController.SourceScripts;
let sourcesView = win.DebuggerView.Sources;
let editorView = win.DebuggerView.editor;
let events = win.EVENTS;
function checkView(selectedFrame, selectedSource, caretLine, editorText) {
is(win.gThreadClient.state, "paused",
"Should only be getting stack frames while paused.");
is(framesView.itemCount, 4,
"Should have four frames.");
is(framesView.selectedDepth, selectedFrame,
"The correct frame is selected in the widget.");
is(sourcesView.selectedIndex, selectedSource,
"The correct source is selected in the widget.");
ok(isCaretPos(panel, caretLine),
"Editor caret location is correct.");
is(editorView.getText().search(editorText[0]), editorText[1],
"The correct source is not displayed.");
}
// Cache the sources text to avoid having to wait for their retrieval.
yield promise.all(sourcesView.attachments.map(e => sources.getText(e.source)));
is(sources._cache.size, 2, "There should be two cached sources in the cache.");
// Allow this generator function to yield first.
executeSoon(() => debuggee.firstCall());
yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6);
checkView(0, 1, 6, [/secondCall/, 118]);
// Change the selected frame and eval inside it.
let updatedFrame = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
framesView.selectedDepth = 3; // oldest frame
yield updatedFrame;
checkView(3, 0, 5, [/firstCall/, 118]);
let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
try {
yield frames.evaluate("foo");
} catch (result) {
is(result.return.type, "object", "The evaluation thrown type is correct.");
is(result.return.class, "Error", "The evaluation thrown class is correct.");
ok(!result.return, "The evaluation hasn't returned.");
}
yield updatedView;
checkView(3, 0, 5, [/firstCall/, 118]);
ok(true, "Evaluating while in a user-selected frame works properly.");
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that simple JS can be parsed and cached with the reflection API.
*/
function test() {
let { Parser } = Cu.import("resource:///modules/devtools/Parser.jsm", {});
let source = "let x = 42;";
let parser = new Parser();
let first = parser.get(source);
let second = parser.get(source);
isnot(first, second,
"The two syntax trees should be different.");
let third = parser.get(source, "url");
let fourth = parser.get(source, "url");
isnot(first, third,
"The new syntax trees should be different than the old ones.");
is(third, fourth,
"The new syntax trees were cached once an identifier was specified.");
is(parser.errors.length, 0,
"There should be no errors logged when parsing.");
finish();
}

View File

@ -0,0 +1,26 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that syntax errors are reported correctly.
*/
function test() {
let { Parser } = Cu.import("resource:///modules/devtools/Parser.jsm", {});
let source = "let x + 42;";
let parser = new Parser();
let parsed = parser.get(source);
ok(parsed,
"An object should be returned even though the source had a syntax error.");
is(parser.errors.length, 1,
"There should be one error logged when parsing.");
is(parser.errors[0].name, "SyntaxError",
"The correct exception was caught.");
is(parser.errors[0].message, "missing ; before statement",
"The correct exception was caught.");
finish();
}

View File

@ -0,0 +1,77 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that JS inside HTML can be separated and parsed correctly.
*/
function test() {
let { Parser } = Cu.import("resource:///modules/devtools/Parser.jsm", {});
let source = [
"<!doctype html>",
"<head>",
"<script>",
"let a = 42;",
"</script>",
"<script type='text/javascript'>",
"let b = 42;",
"</script>",
"<script type='text/javascript;version=1.8'>",
"let c = 42;",
"</script>",
"</head>"
].join("\n");
let parser = new Parser();
let parsed = parser.get(source);
ok(parsed,
"HTML code should be parsed correctly.");
is(parser.errors.length, 0,
"There should be no errors logged when parsing.");
is(parsed.scriptCount, 3,
"There should be 3 scripts parsed in the parent HTML source.");
is(parsed.getScriptInfo(0).toSource(), "({start:-1, length:-1})",
"There is no script at the beginning of the parent source.");
is(parsed.getScriptInfo(source.length - 1).toSource(), "({start:-1, length:-1})",
"There is no script at the end of the parent source.");
is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:31, length:13})",
"The first script was located correctly.");
is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13})",
"The second script was located correctly.");
is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:151, length:13})",
"The third script was located correctly.");
is(parsed.getScriptInfo(source.indexOf("let a") - 1).toSource(), "({start:31, length:13})",
"The left edge of the first script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let b") - 1).toSource(), "({start:85, length:13})",
"The left edge of the second script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let c") - 1).toSource(), "({start:151, length:13})",
"The left edge of the third script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let a") - 2).toSource(), "({start:-1, length:-1})",
"The left outside of the first script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let b") - 2).toSource(), "({start:-1, length:-1})",
"The left outside of the second script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let c") - 2).toSource(), "({start:-1, length:-1})",
"The left outside of the third script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let a") + 12).toSource(), "({start:31, length:13})",
"The right edge of the first script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let b") + 12).toSource(), "({start:85, length:13})",
"The right edge of the second script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let c") + 12).toSource(), "({start:151, length:13})",
"The right edge of the third script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let a") + 13).toSource(), "({start:-1, length:-1})",
"The right outside of the first script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let b") + 13).toSource(), "({start:-1, length:-1})",
"The right outside of the second script was interpreted correctly.");
is(parsed.getScriptInfo(source.indexOf("let c") + 13).toSource(), "({start:-1, length:-1})",
"The right outside of the third script was interpreted correctly.");
finish();
}

View File

@ -0,0 +1,54 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that faulty JS inside HTML can be separated and identified correctly.
*/
function test() {
let { Parser } = Cu.import("resource:///modules/devtools/Parser.jsm", {});
let source = [
"<!doctype html>",
"<head>",
"<SCRIPT>",
"let a + 42;",
"</SCRIPT>",
"<script type='text/javascript'>",
"let b = 42;",
"</SCRIPT>",
"<script type='text/javascript;version=1.8'>",
"let c + 42;",
"</SCRIPT>",
"</head>"
].join("\n");
let parser = new Parser();
let parsed = parser.get(source);
ok(parsed,
"HTML code should be parsed correctly.");
is(parser.errors.length, 2,
"There should be two errors logged when parsing.");
is(parser.errors[0].name, "SyntaxError",
"The correct first exception was caught.");
is(parser.errors[0].message, "missing ; before statement",
"The correct first exception was caught.");
is(parser.errors[1].name, "SyntaxError",
"The correct second exception was caught.");
is(parser.errors[1].message, "missing ; before statement",
"The correct second exception was caught.");
is(parsed.scriptCount, 1,
"There should be 1 script parsed in the parent HTML source.");
is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:-1, length:-1})",
"The first script shouldn't be considered valid.");
is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13})",
"The second script was located correctly.");
is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:-1, length:-1})",
"The third script shouldn't be considered valid.");
finish();
}

View File

@ -0,0 +1,43 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that JS code containing strings that might look like <script> tags
* inside an HTML source is parsed correctly.
*/
function test() {
let { Parser } = Cu.import("resource:///modules/devtools/Parser.jsm", {});
let source = [
"let a = [];",
"a.push('<script>');",
"a.push('var a = 42;');",
"a.push('</script>');",
"a.push('<script type=\"text/javascript\">');",
"a.push('var b = 42;');",
"a.push('</script>');",
"a.push('<script type=\"text/javascript;version=1.8\">');",
"a.push('var c = 42;');",
"a.push('</script>');"
].join("\n");
let parser = new Parser();
let parsed = parser.get(source);
ok(parsed,
"The javascript code should be parsed correctly.");
is(parser.errors.length, 0,
"There should be no errors logged when parsing.");
is(parsed.scriptCount, 1,
"There should be 1 script parsed in the parent source.");
is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:0, length:261})",
"The script location is correct (1).");
is(parsed.getScriptInfo(source.indexOf("<script>")).toSource(), "({start:0, length:261})",
"The script location is correct (2).");
is(parsed.getScriptInfo(source.indexOf("</script>")).toSource(), "({start:0, length:261})",
"The script location is correct (3).");
finish();
}

View File

@ -0,0 +1,64 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that some potentially problematic identifier nodes have the
* right location information attached.
*/
function test() {
let { Parser, ParserHelpers, SyntaxTreeVisitor } =
Cu.import("resource:///modules/devtools/Parser.jsm", {});
function verify(source, predicate, [sline, scol], [eline, ecol]) {
let ast = Parser.reflectionAPI.parse(source);
let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
let loc = ParserHelpers.getNodeLocation(node);
is(loc.start.toSource(), { line: sline, column: scol }.toSource(),
"The start location was correct for the identifier in: '" + source + "'.");
is(loc.end.toSource(), { line: eline, column: ecol }.toSource(),
"The end location was correct for the identifier in: '" + source + "'.");
}
// FunctionDeclarations and FunctionExpressions.
// The location is unavailable for the identifier node "foo".
verify("function foo(){}", e => e.name == "foo", [1, 9], [1, 12]);
verify("\nfunction\nfoo\n(\n)\n{\n}\n", e => e.name == "foo", [3, 0], [3, 3]);
verify("({bar:function foo(){}})", e => e.name == "foo", [1, 15], [1, 18]);
verify("(\n{\nbar\n:\nfunction\nfoo\n(\n)\n{\n}\n}\n)", e => e.name == "foo", [6, 0], [6, 3]);
// Just to be sure, check the identifier node "bar" as well.
verify("({bar:function foo(){}})", e => e.name == "bar", [1, 2], [1, 5]);
verify("(\n{\nbar\n:\nfunction\nfoo\n(\n)\n{\n}\n}\n)", e => e.name == "bar", [3, 0], [3, 3]);
// MemberExpressions.
// The location is unavailable for the identifier node "bar".
verify("foo.bar", e => e.name == "bar", [1, 4], [1, 7]);
verify("\nfoo\n.\nbar\n", e => e.name == "bar", [4, 0], [4, 3]);
// Just to be sure, check the identifier node "foo" as well.
verify("foo.bar", e => e.name == "foo", [1, 0], [1, 3]);
verify("\nfoo\n.\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);
// VariableDeclarator
// The location is incorrect for the identifier node "foo".
verify("let foo = bar", e => e.name == "foo", [1, 4], [1, 7]);
verify("\nlet\nfoo\n=\nbar\n", e => e.name == "foo", [3, 0], [3, 3]);
// Just to be sure, check the identifier node "bar" as well.
verify("let foo = bar", e => e.name == "bar", [1, 10], [1, 13]);
verify("\nlet\nfoo\n=\nbar\n", e => e.name == "bar", [5, 0], [5, 3]);
// Just to be sure, check AssignmentExpreesions as well.
verify("foo = bar", e => e.name == "foo", [1, 0], [1, 3]);
verify("\nfoo\n=\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);
verify("foo = bar", e => e.name == "bar", [1, 6], [1, 9]);
verify("\nfoo\n=\nbar\n", e => e.name == "bar", [4, 0], [4, 3]);
finish();
}

View File

@ -0,0 +1,55 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that nodes with locaiton information attached can be properly
* verified for containing lines and columns.
*/
function test() {
let { ParserHelpers } = Cu.import("resource:///modules/devtools/Parser.jsm", {});
let node1 = { loc: {
start: { line: 1, column: 10 },
end: { line: 10, column: 1 }
}};
let node2 = { loc: {
start: { line: 1, column: 10 },
end: { line: 1, column: 20 }
}};
ok(ParserHelpers.nodeContainsLine(node1, 1), "1st check.");
ok(ParserHelpers.nodeContainsLine(node1, 5), "2nd check.");
ok(ParserHelpers.nodeContainsLine(node1, 10), "3rd check.");
ok(!ParserHelpers.nodeContainsLine(node1, 0), "4th check.");
ok(!ParserHelpers.nodeContainsLine(node1, 11), "5th check.");
ok(ParserHelpers.nodeContainsLine(node2, 1), "6th check.");
ok(!ParserHelpers.nodeContainsLine(node2, 0), "7th check.");
ok(!ParserHelpers.nodeContainsLine(node2, 2), "8th check.");
ok(!ParserHelpers.nodeContainsPoint(node1, 1, 10), "9th check.");
ok(!ParserHelpers.nodeContainsPoint(node1, 10, 1), "10th check.");
ok(!ParserHelpers.nodeContainsPoint(node1, 0, 10), "11th check.");
ok(!ParserHelpers.nodeContainsPoint(node1, 11, 1), "12th check.");
ok(!ParserHelpers.nodeContainsPoint(node1, 1, 9), "13th check.");
ok(!ParserHelpers.nodeContainsPoint(node1, 10, 2), "14th check.");
ok(ParserHelpers.nodeContainsPoint(node2, 1, 10), "15th check.");
ok(ParserHelpers.nodeContainsPoint(node2, 1, 15), "16th check.");
ok(ParserHelpers.nodeContainsPoint(node2, 1, 20), "17th check.");
ok(!ParserHelpers.nodeContainsPoint(node2, 0, 10), "18th check.");
ok(!ParserHelpers.nodeContainsPoint(node2, 2, 20), "19th check.");
ok(!ParserHelpers.nodeContainsPoint(node2, 0, 9), "20th check.");
ok(!ParserHelpers.nodeContainsPoint(node2, 2, 21), "21th check.");
ok(!ParserHelpers.nodeContainsPoint(node2, 1, 9), "22th check.");
ok(!ParserHelpers.nodeContainsPoint(node2, 1, 21), "23th check.");
finish();
}

View File

@ -0,0 +1,289 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that inferring anonymous function information is done correctly.
*/
function test() {
let { Parser, ParserHelpers, SyntaxTreeVisitor } =
Cu.import("resource:///modules/devtools/Parser.jsm", {});
function verify(source, predicate, details) {
let { name, chain } = details;
let [[sline, scol], [eline, ecol]] = details.loc;
let ast = Parser.reflectionAPI.parse(source);
let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
let info = ParserHelpers.inferFunctionExpressionInfo(node);
is(info.name, name,
"The function expression assignment property name is correct.");
is(chain ? info.chain.toSource() : info.chain, chain ? chain.toSource() : chain,
"The function expression assignment property chain is correct.");
is(info.loc.start.toSource(), { line: sline, column: scol }.toSource(),
"The start location was correct for the identifier in: '" + source + "'.");
is(info.loc.end.toSource(), { line: eline, column: ecol }.toSource(),
"The end location was correct for the identifier in: '" + source + "'.");
}
// VariableDeclarator
verify("var foo=function(){}", e => e.type == "FunctionExpression", {
name: "foo",
chain: null,
loc: [[1, 4], [1, 7]]
});
verify("\nvar\nfoo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", {
name: "foo",
chain: null,
loc: [[3, 0], [3, 3]]
});
// AssignmentExpression
verify("foo=function(){}", e => e.type == "FunctionExpression",
{ name: "foo", chain: [], loc: [[1, 0], [1, 3]] });
verify("\nfoo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
{ name: "foo", chain: [], loc: [[2, 0], [2, 3]] });
verify("foo.bar=function(){}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] });
verify("\nfoo.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] });
verify("this.foo=function(){}", e => e.type == "FunctionExpression",
{ name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] });
verify("\nthis.foo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
{ name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] });
verify("this.foo.bar=function(){}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] });
verify("\nthis.foo.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] });
verify("foo.this.bar=function(){}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] });
verify("\nfoo.this.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] });
// ObjectExpression
verify("({foo:function(){}})", e => e.type == "FunctionExpression",
{ name: "foo", chain: [], loc: [[1, 2], [1, 5]] });
verify("(\n{\nfoo\n:\nfunction\n(\n)\n{\n}\n}\n)", e => e.type == "FunctionExpression",
{ name: "foo", chain: [], loc: [[3, 0], [3, 3]] });
verify("({foo:{bar:function(){}}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] });
verify("(\n{\nfoo\n:\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n}\n)", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
// AssignmentExpression + ObjectExpression
verify("foo={bar:function(){}}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] });
verify("\nfoo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] });
verify("foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] });
verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("nested.foo={bar:function(){}}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] });
verify("\nnested.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] });
verify("nested.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] });
verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("this.foo={bar:function(){}}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] });
verify("\nthis.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] });
verify("this.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] });
verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("this.nested.foo={bar:function(){}}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] });
verify("\nthis.nested.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] });
verify("this.nested.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] });
verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("nested.this.foo={bar:function(){}}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] });
verify("\nnested.this.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] });
verify("nested.this.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] });
verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
// VariableDeclarator + AssignmentExpression + ObjectExpression
verify("let foo={bar:function(){}}", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] });
verify("\nlet\nfoo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
verify("let foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] });
verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] });
// New/CallExpression + AssignmentExpression + ObjectExpression
verify("foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[1, 5], [1, 8]] });
verify("\nfoo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] });
verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[1, 12], [1, 15]] });
verify("\nnested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] });
verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[1, 10], [1, 13]] });
verify("\nthis.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] });
verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("this.nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
verify("\nthis.nested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("this.nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("nested.this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
verify("\nnested.this.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("nested.this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
// New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression
verify("let target=foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] });
verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] });
verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] });
verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] });
verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] });
verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] });
verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=this.nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=this.nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=nested.this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=nested.this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
finish();
}

View File

@ -0,0 +1,290 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that inferring anonymous function information is done correctly
* from arrow expressions.
*/
function test() {
let { Parser, ParserHelpers, SyntaxTreeVisitor } =
Cu.import("resource:///modules/devtools/Parser.jsm", {});
function verify(source, predicate, details) {
let { name, chain } = details;
let [[sline, scol], [eline, ecol]] = details.loc;
let ast = Parser.reflectionAPI.parse(source);
let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
let info = ParserHelpers.inferFunctionExpressionInfo(node);
is(info.name, name,
"The function expression assignment property name is correct.");
is(chain ? info.chain.toSource() : info.chain, chain ? chain.toSource() : chain,
"The function expression assignment property chain is correct.");
is(info.loc.start.toSource(), { line: sline, column: scol }.toSource(),
"The start location was correct for the identifier in: '" + source + "'.");
is(info.loc.end.toSource(), { line: eline, column: ecol }.toSource(),
"The end location was correct for the identifier in: '" + source + "'.");
}
// VariableDeclarator
verify("var foo=()=>{}", e => e.type == "ArrowExpression", {
name: "foo",
chain: null,
loc: [[1, 4], [1, 7]]
});
verify("\nvar\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression", {
name: "foo",
chain: null,
loc: [[3, 0], [3, 3]]
});
// AssignmentExpression
verify("foo=()=>{}", e => e.type == "ArrowExpression",
{ name: "foo", chain: [], loc: [[1, 0], [1, 3]] });
verify("\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
{ name: "foo", chain: [], loc: [[2, 0], [2, 3]] });
verify("foo.bar=()=>{}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] });
verify("\nfoo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] });
verify("this.foo=()=>{}", e => e.type == "ArrowExpression",
{ name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] });
verify("\nthis.foo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
{ name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] });
verify("this.foo.bar=()=>{}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] });
verify("\nthis.foo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] });
verify("foo.this.bar=()=>{}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] });
verify("\nfoo.this.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] });
// ObjectExpression
verify("({foo:()=>{}})", e => e.type == "ArrowExpression",
{ name: "foo", chain: [], loc: [[1, 2], [1, 5]] });
verify("(\n{\nfoo\n:\n(\n)\n=>\n{\n}\n}\n)", e => e.type == "ArrowExpression",
{ name: "foo", chain: [], loc: [[3, 0], [3, 3]] });
verify("({foo:{bar:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] });
verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n}\n)", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
// AssignmentExpression + ObjectExpression
verify("foo={bar:()=>{}}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] });
verify("\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] });
verify("foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] });
verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("nested.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] });
verify("\nnested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] });
verify("nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] });
verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("this.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] });
verify("\nthis.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] });
verify("this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] });
verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("this.nested.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] });
verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] });
verify("this.nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] });
verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
verify("nested.this.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] });
verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] });
verify("nested.this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] });
verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
// VariableDeclarator + AssignmentExpression + ObjectExpression
verify("let foo={bar:()=>{}}", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] });
verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
verify("let foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] });
verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] });
// New/CallExpression + AssignmentExpression + ObjectExpression
verify("foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[1, 5], [1, 8]] });
verify("\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] });
verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[1, 12], [1, 15]] });
verify("\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] });
verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[1, 10], [1, 13]] });
verify("\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] });
verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("this.nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
verify("nested.this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
verify("nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
// New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression
verify("let target=foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] });
verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] });
verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] });
verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] });
verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] });
verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] });
verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=this.nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
verify("let target=nested.this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
verify("let target=nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowExpression",
{ name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
finish();
}

View File

@ -0,0 +1,127 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that creating an evaluation string for certain nodes works properly.
*/
function test() {
let { Parser, ParserHelpers, SyntaxTreeVisitor } =
Cu.import("resource:///modules/devtools/Parser.jsm", {});
function verify(source, predicate, string) {
let ast = Parser.reflectionAPI.parse(source);
let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
let info = ParserHelpers.getIdentifierEvalString(node);
is(info, string, "The identifier evaluation string is correct.");
}
// Indentifier or Literal
verify("foo", e => e.type == "Identifier", "foo");
verify("undefined", e => e.type == "Identifier", "undefined");
verify("null", e => e.type == "Literal", "null");
verify("42", e => e.type == "Literal", "42");
verify("true", e => e.type == "Literal", "true");
verify("\"nasu\"", e => e.type == "Literal", "\"nasu\"");
// MemberExpression or ThisExpression
verify("this", e => e.type == "ThisExpression", "this");
verify("foo.bar", e => e.name == "foo", "foo");
verify("foo.bar", e => e.name == "bar", "foo.bar");
// MemberExpression + ThisExpression
verify("this.foo.bar", e => e.type == "ThisExpression", "this");
verify("this.foo.bar", e => e.name == "foo", "this.foo");
verify("this.foo.bar", e => e.name == "bar", "this.foo.bar");
verify("foo.this.bar", e => e.name == "foo", "foo");
verify("foo.this.bar", e => e.name == "this", "foo.this");
verify("foo.this.bar", e => e.name == "bar", "foo.this.bar");
// ObjectExpression + VariableDeclarator
verify("let foo={bar:baz}", e => e.name == "baz", "baz");
verify("let foo={bar:undefined}", e => e.name == "undefined", "undefined");
verify("let foo={bar:null}", e => e.type == "Literal", "null");
verify("let foo={bar:42}", e => e.type == "Literal", "42");
verify("let foo={bar:true}", e => e.type == "Literal", "true");
verify("let foo={bar:\"nasu\"}", e => e.type == "Literal", "\"nasu\"");
verify("let foo={bar:this}", e => e.type == "ThisExpression", "this");
verify("let foo={bar:{nested:baz}}", e => e.name == "baz", "baz");
verify("let foo={bar:{nested:undefined}}", e => e.name == "undefined", "undefined");
verify("let foo={bar:{nested:null}}", e => e.type == "Literal", "null");
verify("let foo={bar:{nested:42}}", e => e.type == "Literal", "42");
verify("let foo={bar:{nested:true}}", e => e.type == "Literal", "true");
verify("let foo={bar:{nested:\"nasu\"}}", e => e.type == "Literal", "\"nasu\"");
verify("let foo={bar:{nested:this}}", e => e.type == "ThisExpression", "this");
verify("let foo={bar:baz}", e => e.name == "bar", "foo.bar");
verify("let foo={bar:baz}", e => e.name == "foo", "foo");
verify("let foo={bar:{nested:baz}}", e => e.name == "nested", "foo.bar.nested");
verify("let foo={bar:{nested:baz}}", e => e.name == "bar", "foo.bar");
verify("let foo={bar:{nested:baz}}", e => e.name == "foo", "foo");
// ObjectExpression + MemberExpression
verify("parent.foo={bar:baz}", e => e.name == "bar", "parent.foo.bar");
verify("parent.foo={bar:baz}", e => e.name == "foo", "parent.foo");
verify("parent.foo={bar:baz}", e => e.name == "parent", "parent");
verify("parent.foo={bar:{nested:baz}}", e => e.name == "nested", "parent.foo.bar.nested");
verify("parent.foo={bar:{nested:baz}}", e => e.name == "bar", "parent.foo.bar");
verify("parent.foo={bar:{nested:baz}}", e => e.name == "foo", "parent.foo");
verify("parent.foo={bar:{nested:baz}}", e => e.name == "parent", "parent");
verify("this.foo={bar:{nested:baz}}", e => e.name == "nested", "this.foo.bar.nested");
verify("this.foo={bar:{nested:baz}}", e => e.name == "bar", "this.foo.bar");
verify("this.foo={bar:{nested:baz}}", e => e.name == "foo", "this.foo");
verify("this.foo={bar:{nested:baz}}", e => e.type == "ThisExpression", "this");
verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "nested", "this.parent.foo.bar.nested");
verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "bar", "this.parent.foo.bar");
verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "foo", "this.parent.foo");
verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "parent", "this.parent");
verify("this.parent.foo={bar:{nested:baz}}", e => e.type == "ThisExpression", "this");
verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "nested", "parent.this.foo.bar.nested");
verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "bar", "parent.this.foo.bar");
verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "foo", "parent.this.foo");
verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "this", "parent.this");
verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "parent", "parent");
// FunctionExpression
verify("function foo(){}", e => e.name == "foo", "foo");
verify("var foo=function(){}", e => e.name == "foo", "foo");
verify("var foo=function bar(){}", e => e.name == "bar", "bar");
// New/CallExpression
verify("foo()", e => e.name == "foo", "foo");
verify("new foo()", e => e.name == "foo", "foo");
verify("foo(bar)", e => e.name == "bar", "bar");
verify("foo(bar, baz)", e => e.name == "baz", "baz");
verify("foo(undefined)", e => e.name == "undefined", "undefined");
verify("foo(null)", e => e.type == "Literal", "null");
verify("foo(42)", e => e.type == "Literal", "42");
verify("foo(true)", e => e.type == "Literal", "true");
verify("foo(\"nasu\")", e => e.type == "Literal", "\"nasu\"");
verify("foo(this)", e => e.type == "ThisExpression", "this");
// New/CallExpression + ObjectExpression + MemberExpression
verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "nested", "this.parent.foo.bar.nested");
verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "bar", "this.parent.foo.bar");
verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "foo", "this.parent.foo");
verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "parent", "this.parent");
verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.type == "ThisExpression", "this");
verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "fun", "fun");
finish();
}

View File

@ -77,6 +77,8 @@ function testSourcesDisplay() {
executeSoon(() => {
is(gEditor.getDebugLocation(), 5,
"Editor debugger location is correct.");
ok(gEditor.hasLineClass(5, "debug-line"),
"The debugged line is highlighted appropriately.");
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
gSources.selectedIndex = 0;
@ -106,6 +108,8 @@ function testSwitchPaused1() {
"Editor caret location is correct.");
is(gEditor.getDebugLocation(), null,
"Editor debugger location is correct.");
ok(!gEditor.hasLineClass(5, "debug-line"),
"The debugged line highlight was removed.");
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
gSources.selectedIndex = 1;
@ -135,6 +139,8 @@ function testSwitchPaused2() {
"Editor caret location is correct.");
is(gEditor.getDebugLocation(), 5,
"Editor debugger location is correct.");
ok(gEditor.hasLineClass(5, "debug-line"),
"The debugged line is highlighted appropriately.");
// Step out three times.
waitForThreadEvents(gPanel, "paused").then(() => {
@ -171,6 +177,8 @@ function testSwitchRunning() {
"Editor caret location is correct.");
is(gEditor.getDebugLocation(), 4,
"Editor debugger location is correct.");
ok(gEditor.hasLineClass(4, "debug-line"),
"The debugged line is highlighted appropriately.");
deferred.resolve();
});

View File

@ -72,6 +72,8 @@ function testSourcesDisplay() {
executeSoon(() => {
is(gEditor.getDebugLocation(), 5,
"Editor debugger location is correct.");
ok(gEditor.hasLineClass(5, "debug-line"),
"The debugged line is highlighted appropriately.");
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
gSources.selectedLabel = gLabel1;
@ -102,6 +104,8 @@ function testSwitchPaused1() {
is(gEditor.getDebugLocation(), null,
"Editor debugger location is correct.");
ok(!gEditor.hasLineClass(5, "debug-line"),
"The debugged line highlight was removed.");
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
gSources.selectedLabel = gLabel2;
@ -131,6 +135,8 @@ function testSwitchPaused2() {
"Editor caret location is correct.");
is(gEditor.getDebugLocation(), 5,
"Editor debugger location is correct.");
ok(gEditor.hasLineClass(5, "debug-line"),
"The debugged line is highlighted appropriately.");
// Step out three times.
waitForThreadEvents(gPanel, "paused").then(() => {
@ -167,6 +173,8 @@ function testSwitchRunning() {
"Editor caret location is correct.");
is(gEditor.getDebugLocation(), 4,
"Editor debugger location is correct.");
ok(gEditor.hasLineClass(4, "debug-line"),
"The debugged line is highlighted appropriately.");
deferred.resolve();
});

View File

@ -175,9 +175,9 @@ function secondJsSearch() {
["y" + s + "Y", "-02.js", "test.prototype.sub", 16, 1],
["z" + s + "Z", "-02.js", "test.prototype.sub.sub", 18, 1],
["t", "-02.js", "test.prototype.sub.sub.sub", 20, 1],
["x", "-02.js", "", 20, 32],
["y", "-02.js", "", 20, 41],
["z", "-02.js", "", 20, 50]
["x", "-02.js", "this", 20, 32],
["y", "-02.js", "this", 20, 41],
["z", "-02.js", "this", 20, 50]
];
for (let [label, value, description, line, column] of expectedResults) {
@ -231,11 +231,11 @@ function thirdJsSearch() {
["a" + s + "A", "-03.js", "bar", 10, 5],
["b" + s + "B", "-03.js", "bar.alpha", 15, 5],
["c" + s + "C", "-03.js", "bar.alpha.beta", 20, 5],
["d" + s + "D", "-03.js", "theta", 25, 5],
["d" + s + "D", "-03.js", "this.theta", 25, 5],
["fun", "-03.js", "", 29, 7],
["foo", "-03.js", "", 29, 13],
["bar", "-03.js", "", 29, 19],
["t_foo", "-03.js", "", 29, 25],
["t_foo", "-03.js", "this", 29, 25],
["w_bar" + s + "baz", "-03.js", "window", 29, 38]
];

View File

@ -0,0 +1,103 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that after selecting a different stack frame, resuming reselects
* the topmost stackframe, loads the right source in the editor pane and
* highlights the proper line.
*/
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gFrames, gToolbar;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gFrames = gDebugger.DebuggerView.StackFrames;
gToolbar = gDebugger.DebuggerView.Toolbar;
waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest);
gDebuggee.firstCall();
});
}
function performTest() {
return Task.spawn(function() {
yield selectBottomFrame();
testBottomFrame(5);
yield performStep("StepOver");
testTopFrame(3);
yield selectBottomFrame();
testBottomFrame(4);
yield performStep("StepIn");
testTopFrame(2);
yield selectBottomFrame();
testBottomFrame(4);
yield performStep("StepOut");
testTopFrame(2);
yield resumeDebuggerThenCloseAndFinish(gPanel);
});
function selectBottomFrame() {
let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
gFrames.selectedIndex = 0;
return updated.then(waitForTick);
}
function testBottomFrame(debugLocation) {
is(gFrames.selectedIndex, 0,
"Oldest frame should be selected after click.");
is(gSources.selectedIndex, 0,
"The first source is now selected in the widget.");
is(gEditor.getText().search(/firstCall/), 118,
"The first source is displayed.");
is(gEditor.getText().search(/debugger/), -1,
"The second source is not displayed.");
is(gEditor.getDebugLocation(), debugLocation,
"Editor debugger location is correct.");
ok(gEditor.hasLineClass(debugLocation, "debug-line"),
"The debugged line is highlighted appropriately.");
}
function performStep(type) {
let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
gToolbar["_on" + type + "Pressed"]();
return updated.then(waitForTick);
}
function testTopFrame(frameIndex) {
is(gFrames.selectedIndex, frameIndex,
"Topmost frame should be selected after click.");
is(gSources.selectedIndex, 1,
"The second source is now selected in the widget.");
is(gEditor.getText().search(/firstCall/), -1,
"The second source is displayed.");
is(gEditor.getText().search(/debugger/), 172,
"The first source is not displayed.");
}
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gSources = null;
gFrames = null;
gToolbar = null;
});

View File

@ -187,7 +187,7 @@ function performTest() {
function getScroll() {
let scrollX = {};
let scrollY = {};
gVariables._boxObject.getPosition(scrollX, scrollY);
gVariables.boxObject.getPosition(scrollX, scrollY);
return [scrollX.value, scrollY.value];
}

View File

@ -0,0 +1,73 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests opening the variable inspection popup on a variable which has a
* simple literal as the value.
*/
const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return popupshown;
}
function hidePopup() {
let popuphiding = once(tooltip, "popuphiding");
bubble.hideContents();
return popuphiding.then(waitForTick);
}
function verifyContents(textContent, className) {
is(tooltip.querySelectorAll(".variables-view-container").length, 0,
"There should be no variables view containers added to the tooltip.");
is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
"There should be a simple text node added to the tooltip instead.");
is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
"The inspected property's value is correct.");
ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.contains(className),
"The inspected property's value is colorized correctly.");
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.start());
yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
// Inspect variables.
yield openPopup({ line: 15, ch: 12 });
verifyContents("1", "token-number");
yield hidePopup().then(() => openPopup({ line: 16, ch: 21 }));
verifyContents("1", "token-number");
yield hidePopup().then(() => openPopup({ line: 17, ch: 21 }));
verifyContents("1", "token-number");
yield hidePopup().then(() => openPopup({ line: 17, ch: 27 }));
verifyContents("\"beta\"", "token-string");
yield hidePopup().then(() => openPopup({ line: 17, ch: 44 }));
verifyContents("false", "token-boolean");
yield hidePopup().then(() => openPopup({ line: 17, ch: 54 }));
verifyContents("null", "token-null");
yield hidePopup().then(() => openPopup({ line: 17, ch: 63 }));
verifyContents("undefined", "token-undefined");
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests opening the variable inspection popup on a variable which has a
* a property accessible via getters and setters.
*/
const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return popupshown;
}
function hidePopup() {
let popuphiding = once(tooltip, "popuphiding");
bubble.hideContents();
return popuphiding.then(waitForTick);
}
function verifyContents(textContent, className) {
is(tooltip.querySelectorAll(".variables-view-container").length, 0,
"There should be no variables view containers added to the tooltip.");
is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
"There should be a simple text node added to the tooltip instead.");
is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
"The inspected property's value is correct.");
ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.contains(className),
"The inspected property's value is colorized correctly.");
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.start());
yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
// Inspect properties.
yield openPopup({ line: 19, ch: 10 });
verifyContents("42", "token-number");
yield hidePopup().then(() => openPopup({ line: 20, ch: 14 }));
verifyContents("42", "token-number");
yield hidePopup().then(() => openPopup({ line: 21, ch: 14 }));
verifyContents("42", "token-number");
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,55 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the inspected indentifier is highlighted.
*/
const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return popupshown;
}
function hidePopup() {
let popuphiding = once(tooltip, "popuphiding");
bubble.hideContents();
return popuphiding.then(waitForTick);
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.start());
yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
// Inspect variable.
yield openPopup({ line: 15, ch: 12 });
ok(!bubble._tooltip.isEmpty(),
"The variable inspection popup isn't empty.");
ok(bubble._markedText,
"There's some marked text in the editor.");
ok(bubble._markedText.clear,
"The marked text in the editor can be cleared.");
yield hidePopup();
ok(bubble._tooltip.isEmpty(),
"The variable inspection popup is now empty.");
ok(!bubble._markedText,
"The marked text in the editor was removed.");
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the variable inspection popup is hidden when the editor scrolls.
*/
const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return popupshown;
}
function scrollEditor() {
let popuphiding = once(tooltip, "popuphiding");
editor.setFirstVisibleLine(0);
return popuphiding.then(waitForTick);
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.start());
yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
// Inspect variable.
yield openPopup({ line: 15, ch: 12 });
yield scrollEditor();
ok(true, "The variable inspection popup was hidden.");
ok(bubble._tooltip.isEmpty(),
"The variable inspection popup is now empty.");
ok(!bubble._markedText,
"The marked text in the editor was removed.");
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests opening the variable inspection popup on a variable which has a
* simple object as the value.
*/
const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_BUBBLE_PROPERTIES);
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return promise.all([popupshown, fetched]);
}
function verifyContents() {
is(tooltip.querySelectorAll(".variables-view-container").length, 1,
"There should be one variables view container added to the tooltip.");
is(tooltip.querySelectorAll(".variables-view-scope[non-header]").length, 1,
"There should be one scope with no header displayed.");
is(tooltip.querySelectorAll(".variables-view-variable[non-header]").length, 1,
"There should be one variable with no header displayed.");
is(tooltip.querySelectorAll(".variables-view-property").length, 2,
"There should be 2 properties displayed.");
is(tooltip.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), "a",
"The first property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), "1",
"The first property's value is correct.");
is(tooltip.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), "__proto__",
"The second property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), "Object",
"The second property's value is correct.");
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.start());
yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
// Inspect variable.
yield openPopup({ line: 16, ch: 12 });
verifyContents();
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,86 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests opening the variable inspection popup on a variable which has a
* complext object as the value.
*/
const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_BUBBLE_PROPERTIES);
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return promise.all([popupshown, fetched]);
}
function verifyContents() {
is(tooltip.querySelectorAll(".variables-view-container").length, 1,
"There should be one variables view container added to the tooltip.");
is(tooltip.querySelectorAll(".variables-view-scope[non-header]").length, 1,
"There should be one scope with no header displayed.");
is(tooltip.querySelectorAll(".variables-view-variable[non-header]").length, 1,
"There should be one variable with no header displayed.");
is(tooltip.querySelectorAll(".variables-view-property").length, 7,
"There should be 7 properties displayed.");
is(tooltip.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), "a",
"The first property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), "1",
"The first property's value is correct.");
is(tooltip.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), "b",
"The second property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), "\"beta\"",
"The second property's value is correct.");
is(tooltip.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), "c",
"The third property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"), "3",
"The third property's value is correct.");
is(tooltip.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), "d",
"The fourth property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"), "false",
"The fourth property's value is correct.");
is(tooltip.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"), "e",
"The fifth property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"), "null",
"The fifth property's value is correct.");
is(tooltip.querySelectorAll(".variables-view-property .name")[5].getAttribute("value"), "f",
"The sixth property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[5].getAttribute("value"), "undefined",
"The sixth property's value is correct.");
is(tooltip.querySelectorAll(".variables-view-property .name")[6].getAttribute("value"), "__proto__",
"The seventh property's name is correct.");
is(tooltip.querySelectorAll(".variables-view-property .value")[6].getAttribute("value"), "Object",
"The seventh property's value is correct.");
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.start());
yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
// Inspect variable.
yield openPopup({ line: 17, ch: 12 });
verifyContents();
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the variable inspection popup behaves correctly when switching
* between simple and complex objects.
*/
const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openSimplePopup(coords) {
let popupshown = once(tooltip, "popupshown");
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return popupshown;
}
function openComplexPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_BUBBLE_PROPERTIES);
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return promise.all([popupshown, fetched]);
}
function hidePopup() {
let popuphiding = once(tooltip, "popuphiding");
bubble.hideContents();
return popuphiding.then(waitForTick);
}
function verifySimpleContents(textContent, className) {
is(tooltip.querySelectorAll(".variables-view-container").length, 0,
"There should be no variables view container added to the tooltip.");
is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
"There should be one simple text node added to the tooltip.");
is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
"The inspected property's value is correct.");
ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.contains(className),
"The inspected property's value is colorized correctly.");
}
function verifyComplexContents(propertyCount) {
is(tooltip.querySelectorAll(".variables-view-container").length, 1,
"There should be one variables view container added to the tooltip.");
is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 0,
"There should be no simple text node added to the tooltip.");
is(tooltip.querySelectorAll(".variables-view-scope[non-header]").length, 1,
"There should be one scope with no header displayed.");
is(tooltip.querySelectorAll(".variables-view-variable[non-header]").length, 1,
"There should be one variable with no header displayed.");
ok(tooltip.querySelectorAll(".variables-view-property").length >= propertyCount,
"There should be some properties displayed.");
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.start());
yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
// Inspect variables.
yield openSimplePopup({ line: 15, ch: 12 });
verifySimpleContents("1", "token-number");
yield hidePopup().then(() => openComplexPopup({ line: 16, ch: 12 }));
verifyComplexContents(2);
yield hidePopup().then(() => openSimplePopup({ line: 19, ch: 10 }));
verifySimpleContents("42", "token-number");
yield hidePopup().then(() => openComplexPopup({ line: 31, ch: 10 }));
verifyComplexContents(100);
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,79 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests opening inspecting variables works across scopes.
*/
const TAB_URL = EXAMPLE_URL + "doc_scope-variable.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let frames = win.DebuggerView.StackFrames;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function openPopup(coords) {
let popupshown = once(tooltip, "popupshown");
let { left, top } = editor.getCoordsFromPosition(coords);
bubble._findIdentifier(left, top);
return popupshown;
}
function hidePopup() {
let popuphiding = once(tooltip, "popuphiding");
bubble.hideContents();
return popuphiding.then(waitForTick);
}
function verifyContents(textContent, className) {
is(tooltip.querySelectorAll(".variables-view-container").length, 0,
"There should be no variables view containers added to the tooltip.");
is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
"There should be a simple text node added to the tooltip instead.");
is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
"The inspected property's value is correct.");
ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.contains(className),
"The inspected property's value is colorized correctly.");
}
function checkView(selectedFrame, caretLine) {
is(win.gThreadClient.state, "paused",
"Should only be getting stack frames while paused.");
is(frames.itemCount, 2,
"Should have two frames.");
is(frames.selectedDepth, selectedFrame,
"The correct frame is selected in the widget.");
ok(isCaretPos(panel, caretLine),
"Editor caret location is correct.");
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.test());
yield waitForSourceAndCaretAndScopes(panel, ".html", 20);
checkView(0, 20);
// Inspect variable in topmost frame.
yield openPopup({ line: 18, ch: 12 });
verifyContents("\"second scope\"", "token-string");
checkView(0, 20);
// Change frame.
let updatedFrame = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
yield hidePopup().then(() => frames.selectedDepth = 1);
yield updatedFrame;
checkView(1, 15);
// Inspect variable in oldest frame.
yield openPopup({ line: 13, ch: 12 });
verifyContents("\"first scope\"", "token-string");
checkView(1, 15);
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,25 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Debugger test page</title>
</head>
<body>
<script type="text/javascript">
function test() {
var a = "first scope";
nest();
}
function nest() {
var a = "second scope";
debugger;
}
</script>
</body>
</html>

View File

@ -727,7 +727,7 @@ var Scratchpad = {
ch: funcStatement.loc.end.column
};
const marker = this.editor.markText(from, to, { className: "eval-text" });
const marker = this.editor.markText(from, to, "eval-text");
setTimeout(() => marker.clear(), EVAL_FUNCTION_TIMEOUT);
return this.evaluate(functionText);

View File

@ -542,7 +542,7 @@ let ShadersEditorsView = {
let tooltip = node._markerErrorsTooltip = new Tooltip(document);
tooltip.defaultOffsetX = GUTTER_ERROR_PANEL_OFFSET_X;
tooltip.setTextContent.apply(tooltip, messages);
tooltip.setTextContent(messages);
tooltip.startTogglingOnHover(node, () => true, GUTTER_ERROR_PANEL_DELAY);
},

View File

@ -32,15 +32,15 @@ function ifWebGLSupported() {
let content = tooltip.content;
ok(tooltip.content,
"Some tooltip's content was set.");
is(tooltip.content.className, "devtools-tooltip-simple-text-container",
ok(tooltip.content.className.contains("devtools-tooltip-simple-text-container"),
"The tooltip's content container was created correctly.");
let messages = content.childNodes;
is(messages.length, 2,
"There are two messages displayed in the tooltip.");
is(messages[0].className, "devtools-tooltip-simple-text",
ok(messages[0].className.contains("devtools-tooltip-simple-text"),
"The first message was created correctly.");
is(messages[1].className, "devtools-tooltip-simple-text",
ok(messages[1].className.contains("devtools-tooltip-simple-text"),
"The second message was created correctly.");
ok(messages[0].textContent.contains("'constructor' : too many arguments"),

View File

@ -13,26 +13,27 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this,
"Reflect", "resource://gre/modules/reflect.jsm");
this.EXPORTED_SYMBOLS = ["Parser"];
this.EXPORTED_SYMBOLS = ["Parser", "ParserHelpers", "SyntaxTreeVisitor"];
/**
* A JS parser using the reflection API.
*/
this.Parser = function Parser() {
this._cache = new Map();
this.errors = [];
};
Parser.prototype = {
/**
* Gets a collection of parser methods for a specified source.
*
* @param string aSource
* The source text content.
* @param string aUrl [optional]
* The source url. The AST nodes will be cached, so you can use this
* identifier to avoid parsing the whole source again.
* @param string aSource
* The source text content.
*/
get: function(aUrl, aSource) {
get: function(aSource, aUrl = "") {
// Try to use the cached AST nodes, to avoid useless parsing operations.
if (this._cache.has(aUrl)) {
return this._cache.get(aUrl);
@ -63,6 +64,7 @@ Parser.prototype = {
let length = aSource.length;
syntaxTrees.push(new SyntaxTree(nodes, aUrl, length));
} catch (e) {
this.errors.push(e);
log(aUrl, e);
}
}
@ -76,13 +78,21 @@ Parser.prototype = {
let length = script.length;
syntaxTrees.push(new SyntaxTree(nodes, aUrl, length, offset));
} catch (e) {
this.errors.push(e);
log(aUrl, e);
}
}
}
let pool = new SyntaxTreesPool(syntaxTrees);
this._cache.set(aUrl, pool);
// Cache the syntax trees pool by the specified url. This is entirely
// optional, but it's strongly encouraged to cache ASTs because
// generating them can be costly with big/complex sources.
if (aUrl) {
this._cache.set(aUrl, pool);
}
return pool;
},
@ -103,7 +113,8 @@ Parser.prototype = {
this._cache.delete(aUrl);
},
_cache: null
_cache: null,
errors: null
};
/**
@ -118,6 +129,13 @@ function SyntaxTreesPool(aSyntaxTrees) {
}
SyntaxTreesPool.prototype = {
/**
* @see SyntaxTree.prototype.getIdentifierAt
*/
getIdentifierAt: function(aLine, aColumn) {
return this._first(this._call("getIdentifierAt", aLine, aColumn));
},
/**
* @see SyntaxTree.prototype.getNamedFunctionDefinitions
*/
@ -126,21 +144,41 @@ SyntaxTreesPool.prototype = {
},
/**
* Finds the offset and length of the script containing the specified offset
* Gets the total number of scripts in the parent source.
* @return number
*/
get scriptCount() {
return this._trees.length;
},
/**
* Finds the start and length of the script containing the specified offset
* relative to its parent source.
*
* @param number aOffset
* The offset relative to the parent source.
* @return array
* @return object
* The offset and length relative to the enclosing script.
*/
getScriptInfo: function(aOffset) {
for (let { offset, length } of this._trees) {
if (offset <= aOffset && offset + length >= aOffset) {
return [offset, length];
return { start: offset, length: length };
}
}
return [-1, -1];
return { start: -1, length: -1 };
},
/**
* Gets the first script results from a source results set.
* If no results are found, null is returned.
*
* @return array
* A collection of parse results for the first script in a source.
*/
_first: function(aSourceResults) {
let scriptResult = aSourceResults.filter(e => !!e.parseResults)[0];
return scriptResult ? scriptResult.parseResults : null;
},
/**
@ -153,9 +191,9 @@ SyntaxTreesPool.prototype = {
* @return array
* The results given by all known syntax trees.
*/
_call: function(aFunction, aParams) {
_call: function(aFunction, ...aParams) {
let results = [];
let requestId = aFunction + aParams; // Cache all the things!
let requestId = aFunction + aParams.toSource(); // Cache all the things!
if (this._cache.has(requestId)) {
return this._cache.get(requestId);
@ -166,7 +204,7 @@ SyntaxTreesPool.prototype = {
sourceUrl: syntaxTree.url,
scriptLength: syntaxTree.length,
scriptOffset: syntaxTree.offset,
parseResults: syntaxTree[aFunction](aParams)
parseResults: syntaxTree[aFunction].apply(syntaxTree, aParams)
});
} catch (e) {
// Can't guarantee that the tree traversal logic is forever perfect :)
@ -203,6 +241,58 @@ function SyntaxTree(aNodes, aUrl, aLength, aOffset = 0) {
};
SyntaxTree.prototype = {
/**
* Gets the identifier at the specified location.
*
* @param number aLine
* The line in the source.
* @param number aColumn
* The column in the source.
* @return object
* An object containing identifier information as { name, location,
* evalString } properties, or null if nothing is found.
*/
getIdentifierAt: function(aLine, aColumn) {
let info = null;
SyntaxTreeVisitor.walk(this.AST, {
/**
* Callback invoked for each identifier node.
* @param Node aNode
*/
onIdentifier: function(aNode) {
if (ParserHelpers.nodeContainsPoint(aNode, aLine, aColumn)) {
info = {
name: aNode.name,
location: ParserHelpers.getNodeLocation(aNode),
evalString: ParserHelpers.getIdentifierEvalString(aNode)
};
// Abruptly halt walking the syntax tree.
SyntaxTreeVisitor.break = true;
}
},
/**
* Callback invoked for each literal node.
* @param Node aNode
*/
onLiteral: function(aNode) {
this.onIdentifier(aNode);
},
/**
* Callback invoked for each 'this' node.
* @param Node aNode
*/
onThisExpression: function(aNode) {
this.onIdentifier(aNode);
}
});
return info;
},
/**
* Searches for all function definitions (declarations and expressions)
* whose names (or inferred names) contain a string.
@ -228,7 +318,7 @@ SyntaxTree.prototype = {
if (functionName.toLowerCase().contains(lowerCaseToken)) {
store.push({
functionName: functionName,
functionLocation: aNode.loc
functionLocation: ParserHelpers.getNodeLocation(aNode)
});
}
},
@ -240,7 +330,7 @@ SyntaxTree.prototype = {
onFunctionExpression: function(aNode) {
// Function expressions don't necessarily have a name.
let functionName = aNode.id ? aNode.id.name : "";
let functionLocation = aNode.loc || null;
let functionLocation = ParserHelpers.getNodeLocation(aNode);
// Infer the function's name from an enclosing syntax tree node.
let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
@ -304,6 +394,56 @@ SyntaxTree.prototype = {
* Parser utility methods.
*/
let ParserHelpers = {
/**
* Gets the location information for a node. Not all nodes have a
* location property directly attached, or the location information
* is incorrect, in which cases it's accessible via the parent.
*
* @param Node aNode
* The node who's location needs to be retrieved.
* @return object
* An object containing { line, column } information.
*/
getNodeLocation: function(aNode) {
if (aNode.type != "Identifier") {
return aNode.loc;
}
// Work around the fact that some identifier nodes don't have the
// correct location attached.
let { loc: parentLocation, type: parentType } = aNode._parent;
let { loc: nodeLocation } = aNode;
if (!nodeLocation) {
if (parentType == "FunctionDeclaration" ||
parentType == "FunctionExpression") {
// e.g. "function foo() {}" or "{ bar: function foo() {} }"
// The location is unavailable for the identifier node "foo".
let loc = JSON.parse(JSON.stringify(parentLocation));
loc.end.line = loc.start.line;
loc.end.column = loc.start.column + aNode.name.length;
return loc;
}
if (parentType == "MemberExpression") {
// e.g. "foo.bar"
// The location is unavailable for the identifier node "bar".
let loc = JSON.parse(JSON.stringify(parentLocation));
loc.start.line = loc.end.line;
loc.start.column = loc.end.column - aNode.name.length;
return loc;
}
} else {
if (parentType == "VariableDeclarator") {
// e.g. "let foo = 42"
// The location incorrectly spans across the whole variable declaration,
// not just the identifier node "foo".
let loc = JSON.parse(JSON.stringify(nodeLocation));
loc.end.line = loc.start.line;
loc.end.column = loc.start.column + aNode.name.length;
return loc;
}
}
return aNode.loc;
},
/**
* Checks if a node's bounds contains a specified line.
*
@ -314,12 +454,9 @@ let ParserHelpers = {
* @return boolean
* True if the line and column is contained in the node's bounds.
*/
isWithinLines: function(aNode, aLine) {
// Not all nodes have location information attached.
if (!aNode.loc) {
return this.isWithinLines(aNode._parent, aLine);
}
return aNode.loc.start.line <= aLine && aNode.loc.end.line >= aLine;
nodeContainsLine: function(aNode, aLine) {
let { start: s, end: e } = this.getNodeLocation(aNode);
return s.line <= aLine && e.line >= aLine;
},
/**
@ -334,13 +471,10 @@ let ParserHelpers = {
* @return boolean
* True if the line and column is contained in the node's bounds.
*/
isWithinBounds: function(aNode, aLine, aColumn) {
// Not all nodes have location information attached.
if (!aNode.loc) {
return this.isWithinBounds(aNode._parent, aLine, aColumn);
}
return aNode.loc.start.line == aLine && aNode.loc.end.line == aLine &&
aNode.loc.start.column <= aColumn && aNode.loc.end.column >= aColumn;
nodeContainsPoint: function(aNode, aLine, aColumn) {
let { start: s, end: e } = this.getNodeLocation(aNode);
return s.line == aLine && e.line == aLine &&
s.column <= aColumn && e.column >= aColumn;
},
/**
@ -364,7 +498,7 @@ let ParserHelpers = {
return {
name: parent.id.name,
chain: null,
loc: parent.loc
loc: this.getNodeLocation(parent.id)
};
}
@ -372,12 +506,12 @@ let ParserHelpers = {
// e.g. foo = function(){} or foo.bar = function(){}, in which case it is
// possible to infer the assignee name ("foo" and "bar" respectively).
if (parent.type == "AssignmentExpression") {
let propertyChain = this.getMemberExpressionPropertyChain(parent.left);
let propertyChain = this._getMemberExpressionPropertyChain(parent.left);
let propertyLeaf = propertyChain.pop();
return {
name: propertyLeaf,
chain: propertyChain,
loc: parent.left.loc
loc: this.getNodeLocation(parent.left)
};
}
@ -385,13 +519,13 @@ let ParserHelpers = {
// e.g. { foo: function(){} }, then it is possible to infer the name
// from the corresponding property.
if (parent.type == "ObjectExpression") {
let propertyKey = this.getObjectExpressionPropertyKeyForValue(aNode);
let propertyChain = this.getObjectExpressionPropertyChain(parent);
let propertyKey = this._getObjectExpressionPropertyKeyForValue(aNode);
let propertyChain = this._getObjectExpressionPropertyChain(parent);
let propertyLeaf = propertyKey.name;
return {
name: propertyLeaf,
chain: propertyChain,
loc: propertyKey.loc
loc: this.getNodeLocation(propertyKey)
};
}
@ -407,6 +541,9 @@ let ParserHelpers = {
* Gets the name of an object expression's property to which a specified
* value is assigned.
*
* Used for inferring function expression information and retrieving
* an identifier evaluation string.
*
* For example, if aNode represents the "bar" identifier in a hypothetical
* "{ foo: bar }" object expression, the returned node is the "foo" identifier.
*
@ -415,7 +552,7 @@ let ParserHelpers = {
* @return object
* The key identifier node in the object expression.
*/
getObjectExpressionPropertyKeyForValue: function(aNode) {
_getObjectExpressionPropertyKeyForValue: function(aNode) {
let parent = aNode._parent;
if (parent.type != "ObjectExpression") {
return null;
@ -431,6 +568,9 @@ let ParserHelpers = {
* Gets an object expression's property chain to its parent
* variable declarator or assignment expression, if available.
*
* Used for inferring function expression information and retrieving
* an identifier evaluation string.
*
* For example, if aNode represents the "baz: {}" object expression in a
* hypothetical "foo = { bar: { baz: {} } }" assignment expression, the
* returned chain is ["foo", "bar", "baz"].
@ -442,11 +582,11 @@ let ParserHelpers = {
* @return array
* The chain to the parent variable declarator, as strings.
*/
getObjectExpressionPropertyChain: function(aNode, aStore = []) {
_getObjectExpressionPropertyChain: function(aNode, aStore = []) {
switch (aNode.type) {
case "ObjectExpression":
this.getObjectExpressionPropertyChain(aNode._parent, aStore);
let propertyKey = this.getObjectExpressionPropertyKeyForValue(aNode);
this._getObjectExpressionPropertyChain(aNode._parent, aStore);
let propertyKey = this._getObjectExpressionPropertyKeyForValue(aNode);
if (propertyKey) {
aStore.push(propertyKey.name);
}
@ -459,14 +599,14 @@ let ParserHelpers = {
// commonly used when defining an object's prototype methods; e.g:
// "Foo.prototype = { ... }".
case "AssignmentExpression":
this.getMemberExpressionPropertyChain(aNode.left, aStore);
this._getMemberExpressionPropertyChain(aNode.left, aStore);
break;
// Additionally handle stuff like "foo = bar.baz({ ... })", because it's
// commonly used in prototype-based inheritance in many libraries; e.g:
// "Foo = Bar.extend({ ... })".
case "NewExpression":
case "CallExpression":
this.getObjectExpressionPropertyChain(aNode._parent, aStore);
this._getObjectExpressionPropertyChain(aNode._parent, aStore);
break;
}
return aStore;
@ -475,6 +615,9 @@ let ParserHelpers = {
/**
* Gets a member expression's property chain.
*
* Used for inferring function expression information and retrieving
* an identifier evaluation string.
*
* For example, if aNode represents a hypothetical "foo.bar.baz"
* member expression, the returned chain ["foo", "bar", "baz"].
*
@ -487,22 +630,65 @@ let ParserHelpers = {
* @return array
* The full member chain, as strings.
*/
getMemberExpressionPropertyChain: function(aNode, aStore = []) {
_getMemberExpressionPropertyChain: function(aNode, aStore = []) {
switch (aNode.type) {
case "MemberExpression":
this.getMemberExpressionPropertyChain(aNode.object, aStore);
this.getMemberExpressionPropertyChain(aNode.property, aStore);
this._getMemberExpressionPropertyChain(aNode.object, aStore);
this._getMemberExpressionPropertyChain(aNode.property, aStore);
break;
case "ThisExpression":
// Such expressions may appear in an assignee chain, for example
// "this.foo.bar = baz", however it seems better to ignore such nodes
// and limit the chain to ["foo", "bar"].
aStore.push("this");
break;
case "Identifier":
aStore.push(aNode.name);
break;
}
return aStore;
},
/**
* Returns an evaluation string which can be used to obtain the
* current value for the respective identifier.
*
* @param Node aNode
* The leaf node (e.g. Identifier, Literal) to begin the scan from.
* @return string
* The corresponding evaluation string, or empty string if
* the specified leaf node can't be used.
*/
getIdentifierEvalString: function(aNode) {
switch (aNode._parent.type) {
case "ObjectExpression":
// If the identifier is the actual property value, it can be used
// directly as an evaluation string. Otherwise, construct the property
// access chain, since the value might have changed.
if (!this._getObjectExpressionPropertyKeyForValue(aNode)) {
let propertyChain = this._getObjectExpressionPropertyChain(aNode._parent);
let propertyLeaf = aNode.name;
return [...propertyChain, propertyLeaf].join(".");
}
break;
case "MemberExpression":
// Make sure this is a property identifier, not the parent object.
if (aNode._parent.property == aNode) {
return this._getMemberExpressionPropertyChain(aNode._parent).join(".");
}
break;
}
switch (aNode.type) {
case "ThisExpression":
return "this";
case "Identifier":
return aNode.name;
case "Literal":
if (typeof aNode.value == "string") {
return "\"" + aNode.value + "\"";
} else {
return aNode.value + "";
}
default:
return "";
}
}
};
@ -527,9 +713,26 @@ let SyntaxTreeVisitor = {
* types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
*/
walk: function(aTree, aCallbacks) {
this.break = false;
this[aTree.type](aTree, aCallbacks);
},
/**
* Filters all the nodes in this syntax tree based on a predicate.
*
* @param object aTree
* The AST nodes generated by the reflection API
* @param function aPredicate
* The predicate ran on each node.
* @return array
* An array of nodes validating the predicate.
*/
filter: function(aTree, aPredicate) {
let store = [];
this.walk(aTree, { onNode: e => { if (aPredicate(e)) store.push(e); } });
return store;
},
/**
* A flag checked on each node in the syntax tree. If true, walking is
* abruptly halted.
@ -2121,4 +2324,4 @@ function log(aStr, aEx) {
dump(msg + "\n");
};
XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", function() Reflect);
XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", () => Reflect);

View File

@ -14,7 +14,16 @@ const {colorUtils} = require("devtools/css-color");
const Heritage = require("sdk/core/heritage");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout",
"resource:///modules/devtools/ViewHelpers.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearNamedTimeout",
"resource:///modules/devtools/ViewHelpers.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
"resource:///modules/devtools/VariablesView.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
"resource:///modules/devtools/VariablesViewController.jsm");
const GRADIENT_RE = /\b(repeating-)?(linear|radial)-gradient\(((rgb|hsl)a?\(.+?\)|[^\)])+\)/gi;
const BORDERCOLOR_RE = /^border-[-a-z]*color$/ig;
@ -24,7 +33,6 @@ const XHTML_NS = "http://www.w3.org/1999/xhtml";
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
const ENTER_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
const SHOW_TIMEOUT = 50;
/**
* Tooltip widget.
@ -185,8 +193,9 @@ module.exports.Tooltip = Tooltip;
Tooltip.prototype = {
defaultPosition: "before_start",
defaultOffsetX: 0,
defaultOffsetY: 0,
defaultOffsetX: 0, // px
defaultOffsetY: 0, // px
defaultShowDelay: 50, // ms
/**
* Show the tooltip. It might be wise to append some content first if you
@ -194,13 +203,11 @@ Tooltip.prototype = {
* tooltip by setting a XUL node to t.content.
* @param {node} anchor
* Which node should the tooltip be shown on
* @param {string} position
* @param {string} position [optional]
* Optional tooltip position. Defaults to before_start
* https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
* @param {number} x
* Optional x offset. Defaults to 0
* @param {number} y
* Optional y offset. Defaults to 0
* @param {number} x, y [optional]
* The left and top offset coordinates, in pixels.
*/
show: function(anchor,
position = this.defaultPosition,
@ -231,6 +238,22 @@ Tooltip.prototype = {
}
},
/**
* Gets this panel's visibility state.
* @return boolean
*/
isHidden: function() {
return this.panel.state == "closed" || this.panel.state == "hiding";
},
/**
* Gets if this panel has any child nodes.
* @return boolean
*/
isEmpty: function() {
return !this.panel.hasChildNodes();
},
/**
* Get rid of references and event listeners
*/
@ -253,7 +276,7 @@ Tooltip.prototype = {
this.doc = null;
this.panel.parentNode.removeChild(this.panel);
this.panel.remove();
this.panel = null;
},
@ -287,9 +310,9 @@ Tooltip.prototype = {
* tooltip if needed. If omitted, the tooltip will be shown everytime.
* @param {Number} showDelay
* An optional delay that will be observed before showing the tooltip.
* Defaults to SHOW_TIMEOUT
* Defaults to this.defaultShowDelay.
*/
startTogglingOnHover: function(baseNode, targetNodeCb, showDelay=SHOW_TIMEOUT) {
startTogglingOnHover: function(baseNode, targetNodeCb, showDelay = this.defaultShowDelay) {
if (this._basedNode) {
this.stopTogglingOnHover();
}
@ -353,7 +376,13 @@ Tooltip.prototype = {
* A node that can be appended in the tooltip XUL element
*/
set content(content) {
if (this.content == content) {
return;
}
this.empty();
this.panel.removeAttribute("clamped-dimensions");
if (content) {
this.panel.appendChild(content);
}
@ -366,18 +395,25 @@ Tooltip.prototype = {
/**
* Sets some text as the content of this tooltip.
*
* @param {string[]} messages
* @param {array} messages
* A list of text messages.
* @param {string} messagesClass [optional]
* A style class for the text messages.
* @param {string} containerClass [optional]
* A style class for the text messages container.
*/
setTextContent: function(...messages) {
setTextContent: function(messages,
messagesClass = "default-tooltip-simple-text-colors",
containerClass = "default-tooltip-simple-text-colors") {
let vbox = this.doc.createElement("vbox");
vbox.className = "devtools-tooltip-simple-text-container";
vbox.className = "devtools-tooltip-simple-text-container " + containerClass;
vbox.setAttribute("flex", "1");
for (let text of messages) {
let description = this.doc.createElement("description");
description.setAttribute("flex", "1");
description.className = "devtools-tooltip-simple-text";
description.className = "devtools-tooltip-simple-text " + messagesClass;
description.textContent = text;
vbox.appendChild(description);
}
@ -385,6 +421,63 @@ Tooltip.prototype = {
this.content = vbox;
},
/**
* Fill the tooltip with a variables view, inspecting an object via its
* corresponding object actor, as specified in the remote debugging protocol.
*
* @param {object} objectActor
* The value grip for the object actor.
* @param {object} viewOptions [optional]
* Options for the variables view visualization.
* @param {object} controllerOptions [optional]
* Options for the variables view controller.
* @param {object} relayEvents [optional]
* A collection of events to listen on the variables view widget.
* For example, { fetched: () => ... }
* @param {boolean} reuseCachedWidget [optional]
* Pass false to instantiate a brand new widget for this variable.
* Otherwise, if a variable was previously inspected, its widget
* will be reused.
*/
setVariableContent: function(
objectActor,
viewOptions = {},
controllerOptions = {},
relayEvents = {},
reuseCachedWidget = true) {
if (reuseCachedWidget && this._cachedVariablesView) {
var [vbox, widget] = this._cachedVariablesView;
} else {
var vbox = this.doc.createElement("vbox");
vbox.className = "devtools-tooltip-variables-view-box";
vbox.setAttribute("flex", "1");
let innerbox = this.doc.createElement("vbox");
innerbox.className = "devtools-tooltip-variables-view-innerbox";
innerbox.setAttribute("flex", "1");
vbox.appendChild(innerbox);
var widget = new VariablesView(innerbox, viewOptions);
for (let e in relayEvents) widget.on(e, relayEvents[e]);
VariablesViewController.attach(widget, controllerOptions);
this._cachedVariablesView = [vbox, widget];
}
// Some of the view options are allowed to change between uses.
widget.searchPlaceholder = viewOptions.searchPlaceholder;
widget.searchEnabled = viewOptions.searchEnabled;
// Use the object actor's grip to display it as a variable in the widget.
// The controller options are allowed to change between uses.
widget.controller.setSingleVariable(
{ objectActor: objectActor }, controllerOptions);
this.content = vbox;
this.panel.setAttribute("clamped-dimensions", "");
},
/**
* Fill the tooltip with an image, displayed over a tiled background useful
* for transparent images. Also adds the image dimension as a label at the

View File

@ -90,7 +90,6 @@ this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
this._list.addEventListener("keypress", this._onViewKeyPress, false);
this._list.addEventListener("keydown", this._onViewKeyDown, false);
this._parent.appendChild(this._list);
this._boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
for (let name in aFlags) {
this[name] = aFlags[name];
@ -196,7 +195,6 @@ VariablesView.prototype = {
this._parent.removeChild(prevList);
this._parent.appendChild(currList);
this._boxObject = currList.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
if (!this._store.length) {
this._appendEmptyNotice();
@ -414,7 +412,7 @@ VariablesView.prototype = {
return;
}
let document = this.document;
let ownerView = this._parent.parentNode;
let ownerNode = this._parent.parentNode;
let container = this._searchboxContainer = document.createElement("hbox");
container.className = "devtools-toolbar";
@ -432,7 +430,7 @@ VariablesView.prototype = {
searchbox.addEventListener("keypress", this._onSearchboxKeyPress, false);
container.appendChild(searchbox);
ownerView.insertBefore(container, this._parent);
ownerNode.insertBefore(container, this._parent);
},
/**
@ -740,7 +738,7 @@ VariablesView.prototype = {
aItem.collapse();
}
aItem._target.focus();
this._boxObject.ensureElementIsVisible(aItem._arrow);
this.boxObject.ensureElementIsVisible(aItem._arrow);
return true;
},
@ -926,6 +924,12 @@ VariablesView.prototype = {
}
},
/**
* Gets the parent node holding this view.
* @return nsIDOMNode
*/
get boxObject() this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject),
/**
* Gets the parent node holding this view.
* @return nsIDOMNode
@ -954,7 +958,6 @@ VariablesView.prototype = {
_nonEnumVisible: true,
_parent: null,
_list: null,
_boxObject: null,
_searchboxNode: null,
_searchboxContainer: null,
_searchboxPlaceholder: "",
@ -1056,7 +1059,8 @@ VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString, aPrefix
// morph it into a plain value.
if ((type == "set" && propertyObject.getter.type == "undefined") ||
(type == "get" && propertyObject.setter.type == "undefined")) {
// Make sure the right getter/setter to value override macro is applied to the target object.
// Make sure the right getter/setter to value override macro is applied
// to the target object.
return propertyObject.evaluationMacro(propertyObject, "undefined", aPrefix);
}
@ -2602,7 +2606,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
// Replace the specified label with a textbox input element.
aLabel.parentNode.replaceChild(input, aLabel);
this._variablesView._boxObject.ensureElementIsVisible(input);
this._variablesView.boxObject.ensureElementIsVisible(input);
input.select();
// When the value is a string (displayed as "value"), then we probably want
@ -2636,7 +2640,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
*/
_deactivateInput: function(aLabel, aInput, aCallbacks) {
aInput.parentNode.replaceChild(aLabel, aInput);
this._variablesView._boxObject.scrollBy(-this._target.clientWidth, 0);
this._variablesView.boxObject.scrollBy(-this._target.clientWidth, 0);
aInput.removeEventListener("keypress", aCallbacks.onKeypress, false);
aInput.removeEventListener("blur", aCallbacks.onBlur, false);

View File

@ -46,31 +46,19 @@ this.EXPORTED_SYMBOLS = ["VariablesViewController", "StackFrameUtils"];
* The view to attach to.
* @param object aOptions [optional]
* Options for configuring the controller. Supported options:
* - getObjectClient: callback for creating an object grip client
* - getLongStringClient: callback for creating a long string grip client
* - getEnvironmentClient: callback for creating an environment client
* - releaseActor: callback for releasing an actor when it's no longer needed
* - overrideValueEvalMacro: callback for creating an overriding eval macro
* - getterOrSetterEvalMacro: callback for creating a getter/setter eval macro
* - simpleValueEvalMacro: callback for creating a simple value eval macro
* - getObjectClient: @see this._setClientGetters
* - getLongStringClient: @see this._setClientGetters
* - getEnvironmentClient: @see this._setClientGetters
* - releaseActor: @see this._setClientGetters
* - overrideValueEvalMacro: @see _setEvaluationMacros
* - getterOrSetterEvalMacro: @see _setEvaluationMacros
* - simpleValueEvalMacro: @see _setEvaluationMacros
*/
function VariablesViewController(aView, aOptions = {}) {
this.addExpander = this.addExpander.bind(this);
this._getObjectClient = aOptions.getObjectClient;
this._getLongStringClient = aOptions.getLongStringClient;
this._getEnvironmentClient = aOptions.getEnvironmentClient;
this._releaseActor = aOptions.releaseActor;
if (aOptions.overrideValueEvalMacro) {
this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
}
if (aOptions.getterOrSetterEvalMacro) {
this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
}
if (aOptions.simpleValueEvalMacro) {
this._simpleValueEvalMacro = aOptions.simpleValueEvalMacro;
}
this._setClientGetters(aOptions);
this._setEvaluationMacros(aOptions);
this._actors = new Set();
this.view = aView;
@ -93,6 +81,52 @@ VariablesViewController.prototype = {
*/
_simpleValueEvalMacro: VariablesView.simpleValueEvalMacro,
/**
* Set the functions used to retrieve debugger client grips.
*
* @param object aOptions
* Options for getting the client grips. Supported options:
* - getObjectClient: callback for creating an object grip client
* - getLongStringClient: callback for creating a long string grip client
* - getEnvironmentClient: callback for creating an environment client
* - releaseActor: callback for releasing an actor when it's no longer needed
*/
_setClientGetters: function(aOptions) {
if (aOptions.getObjectClient) {
this._getObjectClient = aOptions.getObjectClient;
}
if (aOptions.getLongStringClient) {
this._getLongStringClient = aOptions.getLongStringClient;
}
if (aOptions.getEnvironmentClient) {
this._getEnvironmentClient = aOptions.getEnvironmentClient;
}
if (aOptions.releaseActor) {
this._releaseActor = aOptions.releaseActor;
}
},
/**
* Sets the functions used when evaluating strings in the variables view.
*
* @param object aOptions
* Options for configuring the macros. Supported options:
* - overrideValueEvalMacro: callback for creating an overriding eval macro
* - getterOrSetterEvalMacro: callback for creating a getter/setter eval macro
* - simpleValueEvalMacro: callback for creating a simple value eval macro
*/
_setEvaluationMacros: function(aOptions) {
if (aOptions.overrideValueEvalMacro) {
this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
}
if (aOptions.getterOrSetterEvalMacro) {
this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
}
if (aOptions.simpleValueEvalMacro) {
this._simpleValueEvalMacro = aOptions.simpleValueEvalMacro;
}
},
/**
* Populate a long string into a target using a grip.
*
@ -137,6 +171,7 @@ VariablesViewController.prototype = {
*/
_populateFromObject: function(aTarget, aGrip) {
let deferred = promise.defer();
// Mark the specified variable as having retrieved all its properties.
let finish = variable => {
variable._retrieved = true;
@ -147,7 +182,7 @@ VariablesViewController.prototype = {
let objectClient = this._getObjectClient(aGrip);
objectClient.getPrototypeAndProperties(aResponse => {
let { ownProperties, prototype } = aResponse;
// safeGetterValues is new and isn't necessary defined on old actors
// 'safeGetterValues' is new and isn't necessary defined on old actors.
let safeGetterValues = aResponse.safeGetterValues || {};
let sortable = VariablesView.isSortable(aGrip.class);
@ -155,9 +190,9 @@ VariablesViewController.prototype = {
// in VariablesView.
for (let name of Object.keys(safeGetterValues)) {
if (name in ownProperties) {
ownProperties[name].getterValue = safeGetterValues[name].getterValue;
ownProperties[name].getterPrototypeLevel = safeGetterValues[name]
.getterPrototypeLevel;
let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
ownProperties[name].getterValue = getterValue;
ownProperties[name].getterPrototypeLevel = getterPrototypeLevel;
} else {
ownProperties[name] = safeGetterValues[name];
}
@ -180,13 +215,16 @@ VariablesViewController.prototype = {
this.addExpander(proto, prototype);
}
// If the object is a function we need to fetch its scope chain.
// If the object is a function we need to fetch its scope chain
// to show them as closures for the respective function.
if (aGrip.class == "Function") {
objectClient.getScope(aResponse => {
if (aResponse.error) {
console.error(aResponse.error + ": " + aResponse.message);
finish(aTarget);
return;
// This function is bound to a built-in object or it's not present
// in the current scope chain. Not necessarily an actual error,
// it just means that there's no closure for the function.
console.warn(aResponse.error + ": " + aResponse.message);
return void finish(aTarget);
}
this._addVarScope(aTarget, aResponse.scope).then(() => finish(aTarget));
});
@ -347,14 +385,14 @@ VariablesViewController.prototype = {
if (aTarget._fetched) {
return aTarget._fetched;
}
// Make sure the source grip is available.
if (!aSource) {
return promise.reject(new Error("No actor grip was given for the variable."));
}
let deferred = promise.defer();
aTarget._fetched = deferred.promise;
if (!aSource) {
throw new Error("No actor grip was given for the variable.");
}
// If the target is a Variable or Property then we're fetching properties.
if (VariablesView.isVariable(aTarget)) {
this._populateFromObject(aTarget, aSource).then(() => {
@ -447,19 +485,29 @@ VariablesViewController.prototype = {
* Helper function for setting up a single Scope with a single Variable
* contained within it.
*
* This function will empty the variables view.
*
* @param object aOptions
* Options for the contents of the view:
* - objectActor: the grip of the new ObjectActor to show.
* - rawObject: the new raw object to show.
* - label: the new label for the inspected object.
* - rawObject: the raw object to show.
* - label: the label for the inspected object.
* @param object aConfiguration
* Additional options for the controller:
* - overrideValueEvalMacro: @see _setEvaluationMacros
* - getterOrSetterEvalMacro: @see _setEvaluationMacros
* - simpleValueEvalMacro: @see _setEvaluationMacros
* @return Object
* - variable: the created Variable.
* - expanded: the Promise that resolves when the variable expands.
*/
setSingleVariable: function(aOptions) {
setSingleVariable: function(aOptions, aConfiguration = {}) {
this._setEvaluationMacros(aConfiguration);
this.view.empty();
let scope = this.view.addScope(aOptions.label);
scope.expanded = true;
scope.locked = true;
scope.expanded = true; // Expand the scope by default.
scope.locked = true; // Prevent collpasing the scope.
let variable = scope.addItem("", { enumerable: true });
let expanded;

View File

@ -31,14 +31,11 @@
}
.breakpoint.debugLocation {
background-image: url("chrome://browser/skin/devtools/editor-debug-location.png"),
background-image:
url("chrome://browser/skin/devtools/editor-debug-location.png"),
url("chrome://browser/skin/devtools/editor-breakpoint.png");
}
.error-line {
background: rgba(255,0,0,0.2);
}
.CodeMirror {
cursor: text;
}
@ -75,4 +72,4 @@ selector in floating-scrollbar-light.css across all platforms. */
.CodeMirror-dialog input {
font: message-box;
}
}

View File

@ -174,8 +174,11 @@ function setDebugLocation(ctx, line) {
let { ed } = ctx;
let meta = dbginfo.get(ed);
clearDebugLocation(ctx);
meta.debugLocation = line;
ed.addMarker(line, "breakpoints", "debugLocation");
ed.addLineClass(line, "debug-line");
}
/**
@ -199,6 +202,7 @@ function clearDebugLocation(ctx) {
if (meta.debugLocation != null) {
ed.removeMarker(meta.debugLocation, "breakpoints", "debugLocation");
ed.removeLineClass(meta.debugLocation, "debug-line");
meta.debugLocation = null;
}
}

View File

@ -77,8 +77,6 @@ const CM_MAPPING = [
"redo",
"clearHistory",
"openDialog",
"cursorCoords",
"markText",
"refresh"
];
@ -237,6 +235,7 @@ Editor.prototype = {
}, false);
cm.on("focus", () => this.emit("focus"));
cm.on("scroll", () => this.emit("scroll"));
cm.on("change", () => this.emit("change"));
cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
@ -538,6 +537,17 @@ Editor.prototype = {
cm.removeLineClass(line, "wrap", className);
},
/**
* Mark a range of text inside the two {line, ch} bounds. Since the range may
* be modified, for example, when typing text, this method returns a function
* that can be used to remove the mark.
*/
markText: function(from, to, className = "marked-text") {
let cm = editors.get(this);
let mark = cm.markText(from, to, { className: className });
return { clear: () => mark.clear() };
},
/**
* Calculates and returns one or more {line, ch} objects for
* a zero-based index who's value is relative to the start of
@ -567,11 +577,20 @@ Editor.prototype = {
* Returns a {line, ch} object that corresponds to the
* left, top coordinates.
*/
getPositionFromCoords: function (left, top) {
getPositionFromCoords: function ({left, top}) {
let cm = editors.get(this);
return cm.coordsChar({ left: left, top: top });
},
/**
* The reverse of getPositionFromCoords. Similarly, returns a {left, top}
* object that corresponds to the specified line and character number.
*/
getCoordsFromPosition: function ({line, ch}) {
let cm = editors.get(this);
return cm.charCoords({ line: ~~line, ch: ~~ch });
},
/**
* Returns true if there's something to undo and false otherwise.
*/

View File

@ -328,6 +328,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY showAllHistoryCmd.commandkey "H">
<!ENTITY appMenuCustomize.label "Customize">
<!ENTITY appMenuCustomizeExit.label "Exit Customize">
<!ENTITY appMenuHistory.label "History">
<!ENTITY appMenuHistory.showAll.label "Show All History">
<!ENTITY appMenuHistory.clearRecent.label "Clear Recent History…">

View File

@ -140,6 +140,10 @@ emptyChromeGlobalsFilterText=Filter chrome globals (%S)
# appears in the filter text box for the variables view container.
emptyVariablesFilterText=Filter variables
# LOCALIZATION NOTE (emptyPropertiesFilterText): This is the text that
# appears in the filter text box for the editor's variables view bubble.
emptyPropertiesFilterText=Filter properties
# LOCALIZATION NOTE (searchPanelFilter): This is the text that appears in the
# filter panel popup for the filter scripts operation.
searchPanelFilter=Filter scripts (%S)

View File

@ -145,6 +145,34 @@
margin: 2px;
}
/* Variable bubble view */
.devtools-tooltip-simple-text.token-undefined,
.devtools-tooltip-simple-text.token-null {
text-align: center;
color: #666 !important; /* Override the theme's color. */
}
.devtools-tooltip-simple-text.token-boolean {
text-align: center;
color: #10c !important;
}
.devtools-tooltip-simple-text.token-number {
text-align: center;
color: #c00 !important;
}
.devtools-tooltip-simple-text.token-string {
text-align: start;
color: #282 !important;
}
.devtools-tooltip-simple-text.token-other {
text-align: center;
color: #333 !important;
}
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
@ -201,7 +229,7 @@
/* Searchbox and the search operations help panel */
.devtools-searchinput {
#searchbox {
min-width: 220px;
-moz-margin-start: 1px;
}

View File

@ -468,6 +468,7 @@
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
-moz-padding-start: 6px;
-moz-padding-end: 4px;
}
@ -491,6 +492,9 @@
.variables-view-variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid #eee;
}
@ -601,6 +605,7 @@
}
.variables-view-container[aligned-values] .title > .value {
-moz-box-flex: 0;
width: 70vw;
}

View File

@ -147,6 +147,34 @@
margin: 2px;
}
/* Variable bubble view */
.devtools-tooltip-simple-text.token-undefined,
.devtools-tooltip-simple-text.token-null {
text-align: center;
color: #666 !important; /* Override the theme's color. */
}
.devtools-tooltip-simple-text.token-boolean {
text-align: center;
color: #10c !important;
}
.devtools-tooltip-simple-text.token-number {
text-align: center;
color: #c00 !important;
}
.devtools-tooltip-simple-text.token-string {
text-align: start;
color: #282 !important;
}
.devtools-tooltip-simple-text.token-other {
text-align: center;
color: #333 !important;
}
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
@ -203,7 +231,7 @@
/* Searchbox and the search operations help panel */
.devtools-searchinput {
#searchbox {
min-width: 220px;
-moz-margin-start: 1px;
}

View File

@ -462,6 +462,7 @@
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
-moz-padding-start: 6px;
-moz-padding-end: 4px;
}
@ -485,6 +486,9 @@
.variables-view-variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid #eee;
}
@ -595,6 +599,7 @@
}
.variables-view-container[aligned-values] .title > .value {
-moz-box-flex: 0;
width: 70vw;
}

View File

@ -212,19 +212,36 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
border-bottom-style: none;
border-radius: 0;
transition: background-color;
-moz-box-orient: vertical;
flex: 1 1 33.33%;
-moz-box-orient: horizontal;
}
:-moz-any(#PanelUI-help, #PanelUI-customize, #PanelUI-quit) > .toolbarbutton-icon {
margin: 0 0 3px;
#PanelUI-help,
#PanelUI-quit {
min-width: 46px;
}
#PanelUI-customize > .toolbarbutton-text {
text-align: start;
}
#PanelUI-help > .toolbarbutton-text,
#PanelUI-quit > .toolbarbutton-text {
display: none;
}
#PanelUI-help > .toolbarbutton-icon,
#PanelUI-quit > .toolbarbutton-icon {
-moz-margin-end: 0;
}
#PanelUI-customize {
flex: 1;
-moz-padding-start: 15px;
-moz-border-start-style: none;
list-style-image: url(chrome://browser/skin/menuPanel-customize.png);
}
#PanelUI-help {
-moz-border-start-style: none;
list-style-image: url(chrome://browser/skin/menuPanel-help.png);
}

View File

@ -128,6 +128,16 @@
transform: none;
}
.devtools-tooltip[clamped-dimensions] {
max-height: 400px;
max-width: 400px;
}
.devtools-tooltip[clamped-dimensions] .panel-arrowcontent {
overflow: hidden;
}
/* Tooltip: Simple Text */
.devtools-tooltip-simple-text {
max-width: 400px;
margin: 0 -4px; /* Compensate for the .panel-arrowcontent padding. */
@ -143,6 +153,14 @@
margin-bottom: -4px;
}
/* Tooltip: Variables View */
.devtools-tooltip-variables-view-box {
margin: -4px; /* Compensate for the .panel-arrowcontent padding. */
}
/* Tooltip: Tiles */
.devtools-tooltip-tiles {
background-color: #eee;
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),

View File

@ -188,6 +188,27 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */
color: white;
}
/* Highlight for a line that contains an error. */
div.CodeMirror div.error-line {
background: rgba(255,0,0,0.2);
}
/* Highlight for a line that represents a stack frame's location. */
div.CodeMirror div.debug-line {
background: rgba(0,128,255,0.1);
box-shadow:
0 1px 0 0 rgba(0,128,255,0.4),
0 -1px 0 0 rgba(0,128,255,0.4);
}
/* Generic highlighted text */
div.CodeMirror span.marked-text {
background: rgba(255,255,0,0.2);
border: 1px dashed rgba(192,192,0,0.6);
-moz-margin-start: -1px;
-moz-margin-end: -1px;
}
/* Highlight for evaluating current statement. */
div.CodeMirror span.eval-text {
background-color: #556;

View File

@ -187,6 +187,27 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */
color: black;
}
/* Highlight for a line that contains an error. */
div.CodeMirror div.error-line {
background: rgba(255,0,0,0.2);
}
/* Highlight for a line that represents a stack frame's location. */
div.CodeMirror div.debug-line {
background: rgba(0,128,255,0.1);
box-shadow:
0 1px 0 0 rgba(0,128,255,0.4),
0 -1px 0 0 rgba(0,128,255,0.4);
}
/* Generic highlighted text */
div.CodeMirror span.marked-text {
background: rgba(255,255,0,0.2);
border: 1px dashed rgba(192,192,0,0.6);
-moz-margin-start: -1px;
-moz-margin-end: -1px;
}
/* Highlight for evaluating current statement. */
div.CodeMirror span.eval-text {
background-color: #ccd;

View File

@ -145,6 +145,34 @@
margin: 2px;
}
/* Variable bubble view */
.devtools-tooltip-simple-text.token-undefined,
.devtools-tooltip-simple-text.token-null {
text-align: center;
color: #666 !important; /* Override the theme's color. */
}
.devtools-tooltip-simple-text.token-boolean {
text-align: center;
color: #10c !important;
}
.devtools-tooltip-simple-text.token-number {
text-align: center;
color: #c00 !important;
}
.devtools-tooltip-simple-text.token-string {
text-align: start;
color: #282 !important;
}
.devtools-tooltip-simple-text.token-other {
text-align: center;
color: #333 !important;
}
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
@ -201,7 +229,7 @@
/* Searchbox and the search operations help panel */
.devtools-searchinput {
#searchbox {
min-width: 220px;
-moz-margin-start: 1px;
}

View File

@ -465,6 +465,7 @@
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
-moz-padding-start: 6px;
-moz-padding-end: 4px;
}
@ -488,6 +489,9 @@
.variables-view-variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid #eee;
}
@ -598,6 +602,7 @@
}
.variables-view-container[aligned-values] .title > .value {
-moz-box-flex: 0;
width: 70vw;
}

View File

@ -299,6 +299,14 @@ browser.jar:
skin/classic/browser/syncQuota.css
skin/classic/browser/syncProgress.css
#endif
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark.png (../shared/devtools/tooltip/arrow-vertical-dark.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png (../shared/devtools/tooltip/arrow-horizontal-light.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light.png (../shared/devtools/tooltip/arrow-vertical-light.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
#ifdef XP_WIN
browser.jar:
@ -593,11 +601,11 @@ browser.jar:
skin/classic/aero/browser/syncProgress.css
#endif
#endif
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark.png (../shared/devtools/tooltip/arrow-vertical-dark.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png (../shared/devtools/tooltip/arrow-horizontal-light.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light.png (../shared/devtools/tooltip/arrow-vertical-light.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-dark@2x.png (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
skin/classic/aero/browser/devtools/tooltip/arrow-vertical-dark.png (../shared/devtools/tooltip/arrow-vertical-dark.png)
skin/classic/aero/browser/devtools/tooltip/arrow-vertical-dark@2x.png (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light.png (../shared/devtools/tooltip/arrow-horizontal-light.png)
skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light@2x.png (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light.png (../shared/devtools/tooltip/arrow-vertical-light.png)
skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light@2x.png (../shared/devtools/tooltip/arrow-vertical-light@2x.png)

View File

@ -684,6 +684,7 @@ GK_ATOM(ondisabled, "ondisabled")
GK_ATOM(ondischargingtimechange, "ondischargingtimechange")
GK_ATOM(ondisconnected, "ondisconnected")
GK_ATOM(ondisconnecting, "ondisconnecting")
GK_ATOM(ondiscoverystatechanged, "ondiscoverystatechanged")
GK_ATOM(ondownloading, "ondownloading")
GK_ATOM(onDOMActivate, "onDOMActivate")
GK_ATOM(onDOMAttrModified, "onDOMAttrModified")

View File

@ -48,6 +48,10 @@ const kEventConstructors = {
return new BluetoothDeviceEvent(aName, aProps);
},
},
BluetoothDiscoveryStateChangedEvent: { create: function (aName, aProps) {
return new BluetoothDiscoveryStateChangedEvent(aName, aProps);
},
},
BluetoothStatusChangedEvent: { create: function (aName, aProps) {
return new BluetoothStatusChangedEvent(aName, aProps);
},

View File

@ -9,6 +9,7 @@
#include "nsCxPusher.h"
#include "nsDOMClassInfo.h"
#include "nsIDOMBluetoothDeviceEvent.h"
#include "nsIDOMBluetoothDiscoveryStateChangedEvent.h"
#include "nsIDOMBluetoothStatusChangedEvent.h"
#include "nsTArrayHelpers.h"
#include "DOMRequest.h"
@ -320,6 +321,19 @@ BluetoothAdapter::Notify(const BluetoothSignal& aData)
MOZ_ASSERT(arr.Length() == 1);
SetPropertyByValue(arr[0]);
} else if (aData.name().EqualsLiteral(DISCOVERY_STATE_CHANGED_ID)) {
MOZ_ASSERT(v.type() == BluetoothValue::Tbool);
bool isDiscovering = v.get_bool();
nsCOMPtr<nsIDOMEvent> event;
NS_NewDOMBluetoothDiscoveryStateChangedEvent(
getter_AddRefs(event), this, nullptr, nullptr);
nsCOMPtr<nsIDOMBluetoothDiscoveryStateChangedEvent> e =
do_QueryInterface(event);
e->InitBluetoothDiscoveryStateChangedEvent(aData.name(), false, false,
isDiscovering);
DispatchTrustedEvent(event);
} else if (aData.name().EqualsLiteral(PAIRED_STATUS_CHANGED_ID) ||
aData.name().EqualsLiteral(HFP_STATUS_CHANGED_ID) ||
aData.name().EqualsLiteral(SCO_STATUS_CHANGED_ID) ||

View File

@ -147,6 +147,7 @@ public:
SendMediaPlayStatus(const MediaPlayStatus& aMediaPlayStatus, ErrorResult& aRv);
IMPL_EVENT_HANDLER(devicefound);
IMPL_EVENT_HANDLER(discoverystatechanged);
IMPL_EVENT_HANDLER(a2dpstatuschanged);
IMPL_EVENT_HANDLER(hfpstatuschanged);
IMPL_EVENT_HANDLER(pairedstatuschanged);

View File

@ -89,6 +89,11 @@ extern bool gBluetoothDebugFlag;
*/
#define PAIRED_STATUS_CHANGED_ID "pairedstatuschanged"
/**
* This event would be fired when discovery procedure starts or stops.
*/
#define DISCOVERY_STATE_CHANGED_ID "discoverystatechanged"
/**
* When receiving a query about current play status from remote device, we'll
* dispatch an event.

View File

@ -163,6 +163,7 @@ BluetoothProfileController::Start()
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mDeviceAddress.IsEmpty());
MOZ_ASSERT(mProfilesIndex == -1);
NS_ENSURE_TRUE_VOID(mProfiles.Length() > 0);
++mProfilesIndex;
BT_LOGR_PROFILE(mProfiles[mProfilesIndex], "");

View File

@ -40,8 +40,8 @@ BEGIN_BLUETOOTH_NAMESPACE
// Bit 18: Major service class = 0x20, Rendering
#define HAS_RENDERING(cod) (cod & 0x40000)
// Major device class = 0xA, Peripheral
#define IS_PERIPHERAL(cod) (GET_MAJOR_DEVICE_CLASS(cod) == 0xa)
// Major device class = 0x5, Peripheral
#define IS_PERIPHERAL(cod) (GET_MAJOR_DEVICE_CLASS(cod) == 0x5)
class BluetoothProfileManagerBase;
class BluetoothReplyRunnable;

View File

@ -49,7 +49,6 @@ static InfallibleTArray<nsString> sAdapterBondedAddressArray;
static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
@ -496,12 +495,16 @@ DiscoveryStateChangedCallback(bt_discovery_state_t aState)
{
MOZ_ASSERT(!NS_IsMainThread());
if (!sChangeDiscoveryRunnableArray.IsEmpty()) {
BluetoothValue values(true);
DispatchBluetoothReply(sChangeDiscoveryRunnableArray[0],
values, EmptyString());
bool isDiscovering = (aState == BT_DISCOVERY_STARTED);
sChangeDiscoveryRunnableArray.RemoveElementAt(0);
BluetoothSignal signal(NS_LITERAL_STRING(DISCOVERY_STATE_CHANGED_ID),
NS_LITERAL_STRING(KEY_ADAPTER),
isDiscovering);
nsRefPtr<DistributeBluetoothSignalTask>
t = new DistributeBluetoothSignalTask(signal);
if (NS_FAILED(NS_DispatchToMainThread(t))) {
BT_WARNING("Failed to dispatch to main thread!");
}
}
@ -918,17 +921,17 @@ BluetoothServiceBluedroid::StartDiscoveryInternal(
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
int ret = sBtInterface->start_discovery();
if (ret != BT_STATUS_SUCCESS) {
ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StartDiscovery"));
return NS_OK;
return NS_ERROR_FAILURE;
}
sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
DispatchBluetoothReply(aRunnable, true, EmptyString());
return NS_OK;
}
@ -947,10 +950,10 @@ BluetoothServiceBluedroid::StopDiscoveryInternal(
int ret = sBtInterface->cancel_discovery();
if (ret != BT_STATUS_SUCCESS) {
ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StopDiscovery"));
return NS_OK;
return NS_ERROR_FAILURE;
}
sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
DispatchBluetoothReply(aRunnable, true, EmptyString());
return NS_OK;
}

View File

@ -1512,6 +1512,20 @@ EventFilter(DBusConnection* aConn, DBusMessage* aMsg, void* aData)
errorStr,
sAdapterProperties,
ArrayLength(sAdapterProperties));
BluetoothNamedValue& property = v.get_ArrayOfBluetoothNamedValue()[0];
if (property.name().EqualsLiteral("Discovering")) {
bool isDiscovering = property.value();
BluetoothSignal signal(NS_LITERAL_STRING(DISCOVERY_STATE_CHANGED_ID),
NS_LITERAL_STRING(KEY_ADAPTER),
isDiscovering);
nsRefPtr<DistributeBluetoothSignalTask>
t = new DistributeBluetoothSignalTask(signal);
if (NS_FAILED(NS_DispatchToMainThread(t))) {
BT_WARNING("Failed to dispatch to main thread!");
}
}
} else if (dbus_message_is_signal(aMsg, DBUS_DEVICE_IFACE,
"PropertyChanged")) {
ParsePropertyChange(aMsg,

View File

@ -8,6 +8,7 @@ if CONFIG['MOZ_B2G_BT']:
XPIDL_SOURCES += [
'nsIDOMBluetoothDevice.idl',
'nsIDOMBluetoothDeviceEvent.idl',
'nsIDOMBluetoothDiscoveryStateChangedEvent.idl',
'nsIDOMBluetoothStatusChangedEvent.idl',
]
XPIDL_MODULE = 'dom_bluetooth'

View File

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIDOMEvent.idl"
[scriptable, builtinclass, uuid(9de639cb-71c4-4144-8462-09763ec87c20)]
interface nsIDOMBluetoothDiscoveryStateChangedEvent : nsIDOMEvent
{
readonly attribute boolean discovering;
[noscript]
void initBluetoothDiscoveryStateChangedEvent(in DOMString aType,
in boolean aCanBubble,
in boolean aCancelable,
in boolean aDiscovering);
};
dictionary BluetoothDiscoveryStateChangedEventInit : EventInit
{
bool discovering;
};

View File

@ -95,6 +95,7 @@ public:
void GetDiskFreeSpace(int64_t* aSoFar);
void GetStatus(nsAString& aStatus);
void DoFormat(nsAString& aStatus);
static void GetRootDirectoryForType(const nsAString& aStorageType,
const nsAString& aStorageName,
nsIFile** aFile);
@ -237,6 +238,7 @@ public:
already_AddRefed<DOMRequest> FreeSpace(ErrorResult& aRv);
already_AddRefed<DOMRequest> UsedSpace(ErrorResult& aRv);
already_AddRefed<DOMRequest> Available(ErrorResult& aRv);
already_AddRefed<DOMRequest> Format(ErrorResult& aRv);
bool Default();

View File

@ -103,6 +103,16 @@ DeviceStorageRequestChild::
break;
}
case DeviceStorageResponseValue::TFormatStorageResponse:
{
FormatStorageResponse r = aValue;
AutoJSContext cx;
JS::Rooted<JS::Value> result(
cx, StringToJsval(mRequest->GetOwner(), r.mountState()));
mRequest->FireSuccess(result);
break;
}
case DeviceStorageResponseValue::TEnumerationResponse:
{
EnumerationResponse r = aValue;

View File

@ -131,6 +131,18 @@ DeviceStorageRequestParent::Dispatch()
break;
}
case DeviceStorageParams::TDeviceStorageFormatParams:
{
DeviceStorageFormatParams p = mParams;
nsRefPtr<DeviceStorageFile> dsf =
new DeviceStorageFile(p.type(), p.storageName());
nsRefPtr<PostFormatResultEvent> r
= new PostFormatResultEvent(this, dsf);
NS_DispatchToMainThread(r);
break;
}
case DeviceStorageParams::TDeviceStorageEnumerationParams:
{
DeviceStorageEnumerationParams p = mParams;
@ -215,6 +227,14 @@ DeviceStorageRequestParent::EnsureRequiredPermissions(
break;
}
case DeviceStorageParams::TDeviceStorageFormatParams:
{
DeviceStorageFormatParams p = mParams;
type = p.type();
requestType = DEVICE_STORAGE_REQUEST_FORMAT;
break;
}
case DeviceStorageParams::TDeviceStorageEnumerationParams:
{
DeviceStorageEnumerationParams p = mParams;
@ -726,6 +746,34 @@ DeviceStorageRequestParent::PostAvailableResultEvent::CancelableRun()
return NS_OK;
}
DeviceStorageRequestParent::PostFormatResultEvent::
PostFormatResultEvent(DeviceStorageRequestParent* aParent,
DeviceStorageFile* aFile)
: CancelableRunnable(aParent)
, mFile(aFile)
{
}
DeviceStorageRequestParent::PostFormatResultEvent::
~PostFormatResultEvent()
{
}
nsresult
DeviceStorageRequestParent::PostFormatResultEvent::CancelableRun()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
nsString state = NS_LITERAL_STRING("unavailable");
if (mFile) {
mFile->DoFormat(state);
}
FormatStorageResponse response(state);
unused << mParent->Send__delete__(mParent, response);
return NS_OK;
}
} // namespace devicestorage
} // namespace dom
} // namespace mozilla

View File

@ -227,6 +227,16 @@ private:
nsRefPtr<DeviceStorageFile> mFile;
};
class PostFormatResultEvent : public CancelableRunnable
{
public:
PostFormatResultEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
virtual ~PostFormatResultEvent();
virtual nsresult CancelableRun();
private:
nsRefPtr<DeviceStorageFile> mFile;
};
protected:
bool AddRunnable(CancelableRunnable* aRunnable) {
MutexAutoLock lock(mMutex);

View File

@ -53,6 +53,11 @@ struct AvailableStorageResponse
nsString mountState;
};
struct FormatStorageResponse
{
nsString mountState;
};
union DeviceStorageResponseValue
{
ErrorResponse;
@ -62,6 +67,7 @@ union DeviceStorageResponseValue
FreeSpaceStorageResponse;
UsedSpaceStorageResponse;
AvailableStorageResponse;
FormatStorageResponse;
};
sync protocol PDeviceStorageRequest {

View File

@ -390,6 +390,7 @@ DeviceStorageTypeChecker::GetAccessForRequest(
break;
case DEVICE_STORAGE_REQUEST_WRITE:
case DEVICE_STORAGE_REQUEST_DELETE:
case DEVICE_STORAGE_REQUEST_FORMAT:
aAccessResult.AssignLiteral("write");
break;
case DEVICE_STORAGE_REQUEST_CREATE:
@ -1279,6 +1280,36 @@ DeviceStorageFile::IsAvailable()
return status.EqualsLiteral("available");
}
void
DeviceStorageFile::DoFormat(nsAString& aStatus)
{
DeviceStorageTypeChecker* typeChecker
= DeviceStorageTypeChecker::CreateOrGet();
if (!typeChecker) {
return;
}
if (!typeChecker->IsVolumeBased(mStorageType)) {
aStatus.AssignLiteral("notVolume");
return;
}
#ifdef MOZ_WIDGET_GONK
nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(vs);
nsCOMPtr<nsIVolume> vol;
nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
NS_ENSURE_SUCCESS_VOID(rv);
if (!vol) {
return;
}
vol->Format();
aStatus.AssignLiteral("formatting");
#endif
return;
}
void
DeviceStorageFile::GetStatus(nsAString& aStatus)
{
@ -1316,6 +1347,13 @@ DeviceStorageFile::GetStatus(nsAString& aStatus)
aStatus.AssignLiteral("shared");
return;
}
bool isFormatting;
rv = vol->GetIsFormatting(&isFormatting);
NS_ENSURE_SUCCESS_VOID(rv);
if (isFormatting) {
aStatus.AssignLiteral("unavailable");
return;
}
int32_t volState;
rv = vol->GetState(&volState);
NS_ENSURE_SUCCESS_VOID(rv);
@ -1848,6 +1886,39 @@ private:
nsRefPtr<DOMRequest> mRequest;
};
class PostFormatResultEvent : public nsRunnable
{
public:
PostFormatResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
: mFile(aFile)
, mRequest(aRequest)
{
}
~PostFormatResultEvent() {}
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread());
nsString state = NS_LITERAL_STRING("unavailable");
if (mFile) {
mFile->DoFormat(state);
}
AutoJSContext cx;
JS::Rooted<JS::Value> result(cx,
StringToJsval(mRequest->GetOwner(), state));
mRequest->FireSuccess(result);
mRequest = nullptr;
return NS_OK;
}
private:
nsRefPtr<DeviceStorageFile> mFile;
nsRefPtr<DOMRequest> mRequest;
};
class PostResultEvent : public nsRunnable
{
public:
@ -2423,6 +2494,23 @@ public:
mDeviceStorage->mAllowedToWatchFile = true;
return NS_OK;
}
case DEVICE_STORAGE_REQUEST_FORMAT:
{
if (XRE_GetProcessType() != GeckoProcessType_Default) {
PDeviceStorageRequestChild* child
= new DeviceStorageRequestChild(mRequest, mFile);
DeviceStorageFormatParams params(mFile->mStorageType,
mFile->mStorageName);
ContentChild::GetSingleton()
->SendPDeviceStorageRequestConstructor(child, params);
return NS_OK;
}
r = new PostFormatResultEvent(mFile, mRequest);
NS_DispatchToMainThread(r);
return NS_OK;
}
}
if (r) {
@ -3083,6 +3171,26 @@ nsDOMDeviceStorage::Available(ErrorResult& aRv)
return request.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Format(ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
if (!win) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsRefPtr<DOMRequest> request = new DOMRequest(win);
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
nsCOMPtr<nsIRunnable> r
= new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FORMAT,
win, mPrincipal, dsf, request);
NS_DispatchToMainThread(r);
return request.forget();
}
NS_IMETHODIMP
nsDOMDeviceStorage::GetRootDirectoryForFile(const nsAString& aName,
nsIFile** aRootDirectory)

View File

@ -51,7 +51,8 @@ enum DeviceStorageRequestType {
DEVICE_STORAGE_REQUEST_WATCH,
DEVICE_STORAGE_REQUEST_FREE_SPACE,
DEVICE_STORAGE_REQUEST_USED_SPACE,
DEVICE_STORAGE_REQUEST_AVAILABLE
DEVICE_STORAGE_REQUEST_AVAILABLE,
DEVICE_STORAGE_REQUEST_FORMAT
};
class DeviceStorageUsedSpaceCache MOZ_FINAL

View File

@ -116,6 +116,17 @@ dictionary MozStkMenu
* false: no help information available.
*/
boolean isHelpAvailable;
/**
* List of Next Action Indicators.
* Each element should be one of nsIDOMMozIccManager.STK_CMD_*
* or nsIDOMMozIccManager.STK_NEXT_ACTION_*
* If it's STK_NEXT_ACTION_NULL, the terminal should ignore this action
* in corresponding item.
*
* @see TS 11.14, clause 12.24, Items Next Action Indicator.
*/
jsval nextActionList; // unsigned short []
};
dictionary MozStkInput

View File

@ -6,7 +6,7 @@
interface nsIDOMMozIcc;
[scriptable, builtinclass, uuid(23067d6f-e0cb-4f34-8648-77c2b25a11f5)]
[scriptable, builtinclass, uuid(67e40e8e-35ee-40a4-a5b8-414588675133)]
interface nsIDOMMozIccManager : nsIDOMEventTarget
{
/**
@ -47,10 +47,10 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget
const unsigned short STK_CMD_PROVIDE_LOCAL_INFO = 0x26;
const unsigned short STK_CMD_TIMER_MANAGEMENT = 0x27;
const unsigned short STK_CMD_SET_UP_IDLE_MODE_TEXT = 0x28;
const unsigned short STK_CMD_OPEN_CHANNEL = 0x30;
const unsigned short STK_CMD_CLOSE_CHANNEL = 0x31;
const unsigned short STK_CMD_RECEIVE_DATA = 0x32;
const unsigned short STK_CMD_SEND_DATA = 0x33;
const unsigned short STK_CMD_OPEN_CHANNEL = 0x40;
const unsigned short STK_CMD_CLOSE_CHANNEL = 0x41;
const unsigned short STK_CMD_RECEIVE_DATA = 0x42;
const unsigned short STK_CMD_SEND_DATA = 0x43;
/**
* STK result code.
@ -222,6 +222,12 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget
const unsigned short STK_BROWSER_TERMINATION_CAUSE_USER = 0x00;
const unsigned short STK_BROWSER_TERMINATION_CAUSE_ERROR = 0x01;
/**
* Next Action Indicator.
*/
const unsigned short STK_NEXT_ACTION_NULL = 0x00;
const unsigned short STK_NEXT_ACTION_END_PROACTIVE_SESSION = 0x81;
/**
* Array of iccIds that are currently detected.
*/

View File

@ -22,6 +22,7 @@ qemu = true
[test_stk_select_item.js]
[test_stk_setup_menu.js]
[test_stk_setup_idle_mode_text.js]
[test_stk_bip_command.js]
[test_icc_access_invalid_object.js]
disabled = Bug 933654
[test_icc_detected_undetected_event.js]

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_HEAD_JS = "stk_helper.js";
function testBipCommand(command, expect) {
log("STK CMD " + JSON.stringify(command));
is(command.typeOfCommand, expect.typeOfCommand, expect.name);
is(command.options.text, expect.text, expect.name);
runNextTest();
}
let tests = [
{command: "d04b81030140018202818205074f70656e204944350702030403041f0239020578470a065465737447700272730d08f4557365724c6f670d08f4557365725077643c0301ad9c3e052101010101",
func: testBipCommand,
expect: {name: "open_channel_1",
typeOfCommand: iccManager.STK_CMD_OPEN_CHANNEL,
text: "Open ID"}},
{command: "d0448103014001820281820500350702030403041f0239020578470a065465737447700272730d08f4557365724c6f670d08f4557365725077643c0301ad9c3e052101010101",
func: testBipCommand,
expect: {name: "open_channel_2",
typeOfCommand: iccManager.STK_CMD_OPEN_CHANNEL,
text: ""}},
{command: "d05381030140018202818205094f70656e2049442031350702030403041f0239020578470a065465737447700272730d08f4557365724c6f670d08f4557365725077643c0301ad9c3e052101010101d004000900b4",
func: testBipCommand,
expect: {name: "open_channel_3",
typeOfCommand: iccManager.STK_CMD_OPEN_CHANNEL,
text: "Open ID 1"}},
{command: "d01b810301410082028121850a436c6f73652049442031d004000a00b4",
func: testBipCommand,
expect: {name: "close_channel_1",
typeOfCommand: iccManager.STK_CMD_CLOSE_CHANNEL,
text: "Close ID 1"}},
{command: "d022810301420082028121850e5265636569766520446174612031b701c8d004000e00b4",
func: testBipCommand,
expect: {name: "receive_data_1",
typeOfCommand: iccManager.STK_CMD_RECEIVE_DATA,
text: "Receive Data 1"}},
{command: "d026810301430182028121850b53656e6420446174612031b6080001020304050607d004000b00b4",
func: testBipCommand,
expect: {name: "send_data_1",
typeOfCommand: iccManager.STK_CMD_SEND_DATA,
text: "Send Data 1"}},
];
runNextTest();

View File

@ -12,6 +12,10 @@ function testSelectItem(command, expect) {
is(command.options.items[index].identifier, expect.items[index].identifier, expect.name);
is(command.options.items[index].text, expect.items[index].text, expect.name);
}
let length = command.options.nextActionList ? command.options.nextActionList.length : 0;
for (let i = 0; i < length; i++) {
is(command.options.nextActionList[i], expect.nextActionList[i], expect.name);
}
runNextTest();
}
@ -58,7 +62,8 @@ let tests = [
expect: {name: "select_item_cmd_7",
commandQualifier: 0x00,
title: "Toolkit Select",
items: [{identifier: 1, text: "Item 1"}, {identifier: 2, text: "Item 2"}, {identifier: 3, text: "Item 3"}]}},
items: [{identifier: 1, text: "Item 1"}, {identifier: 2, text: "Item 2"}, {identifier: 3, text: "Item 3"}],
nextActionList: [iccManager.STK_CMD_SEND_SMS, iccManager.STK_CMD_SET_UP_CALL, iccManager.STK_CMD_PROVIDE_LOCAL_INFO]}},
{command: "d037810301240082028182850e546f6f6c6b69742053656c6563748f07014974656d20318f07024974656d20328f07034974656d2033900102",
func: testSelectItem,
expect: {name: "select_item_cmd_8",
@ -304,7 +309,14 @@ let tests = [
expect: {name: "select_item_cmd_48",
commandQualifier: 0x00,
title: "82ル0",
items: [{identifier: 1, text: "82ル1"}, {identifier: 2, text: "82ル2"}, {identifier: 3, text: "82ル3"}]}}
items: [{identifier: 1, text: "82ル1"}, {identifier: 2, text: "82ル2"}, {identifier: 3, text: "82ル3"}]}},
{command: "d039810301240082028182850e546f6f6c6b69742053656c6563748f07014974656d20318f07024974656d20328f07034974656d20331803000081",
func: testSelectItem,
expect: {name: "select_item_cmd_49",
commandQualifier: 0x00,
title: "Toolkit Select",
items: [{identifier: 1, text: "Item 1"}, {identifier: 2, text: "Item 2"}, {identifier: 3, text: "Item 3"}],
nextActionList: [iccManager.STK_NEXT_ACTION_NULL, iccManager.STK_NEXT_ACTION_NULL, iccManager.STK_NEXT_ACTION_END_PROACTIVE_SESSION]}},
];
runNextTest();

View File

@ -12,6 +12,10 @@ function testSetupMenu(command, expect) {
is(command.options.items[index].identifier, expect.items[index].identifier, expect.name);
is(command.options.items[index].text, expect.items[index].text, expect.name);
}
let length = command.options.nextActionList ? command.options.nextActionList.length : 0;
for (let i = 0; i < length; i++) {
is(command.options.nextActionList[i], expect.nextActionList[i], expect.name);
}
runNextTest();
}
@ -77,7 +81,8 @@ let tests = [
expect: {name: "setup_menu_cmd_7",
commandQualifier: 0x00,
title: "Toolkit Menu",
items: [{identifier: 1, text: "Item 1"}, {identifier: 2, text: "Item 2"}, {identifier: 3, text: "Item 3"}, {identifier: 4, text: "Item 4"}]}},
items: [{identifier: 1, text: "Item 1"}, {identifier: 2, text: "Item 2"}, {identifier: 3, text: "Item 3"}, {identifier: 4, text: "Item 4"}],
nextActionList: [iccManager.STK_CMD_SEND_SMS, iccManager.STK_CMD_SET_UP_CALL, iccManager.STK_CMD_LAUNCH_BROWSER, iccManager.STK_CMD_PROVIDE_LOCAL_INFO]}},
{command: "d03c810301250082028182850c546f6f6c6b6974204d656e758f07014974656d20318f07024974656d20328f07034974656d20339e0201019f0401050505",
func: testSetupMenu,
expect: {name: "setup_menu_cmd_8",
@ -222,6 +227,13 @@ let tests = [
commandQualifier: 0x00,
title: "80ル0",
items: [{identifier: 17, text: "80ル5"}, {identifier: 18, text: "80ル6"}]}},
{command: "d041810301250082028182850c546f6f6c6b6974204d656e758f07014974656d20318f07024974656d20328f07034974656d20338f07044974656d2034180481000000",
func: testSetupMenu,
expect: {name: "setup_menu_cmd_32",
commandQualifier: 0x00,
title: "Toolkit Menu",
items: [{identifier: 1, text: "Item 1"}, {identifier: 2, text: "Item 2"}, {identifier: 3, text: "Item 3"}, {identifier: 4, text: "Item 4"}],
nextActionList: [iccManager.STK_NEXT_ACTION_END_PROACTIVE_SESSION, iccManager.STK_NEXT_ACTION_NULL, iccManager.STK_NEXT_ACTION_NULL, iccManager.STK_NEXT_ACTION_NULL]}},
{command: "D00D81030125008202818285008F00",
func: testRemoveSetupMenu},
{command:"D03B810301250082028182850C546F6F6C6B6974204D656E758F07014974656D20318F07024974656D20328F07034974656D20338F07044974656D2034",

View File

@ -79,6 +79,12 @@ struct DeviceStorageAvailableParams
nsString storageName;
};
struct DeviceStorageFormatParams
{
nsString type;
nsString storageName;
};
struct DeviceStorageAddParams
{
nsString type;
@ -119,6 +125,7 @@ union DeviceStorageParams
DeviceStorageFreeSpaceParams;
DeviceStorageUsedSpaceParams;
DeviceStorageAvailableParams;
DeviceStorageFormatParams;
};
struct FMRadioRequestEnableParams

View File

@ -554,11 +554,6 @@ TabChild::HandlePossibleViewportChange()
return;
}
// Make sure the viewport height is not shorter than the window when the page
// is zoomed out to show its full width. Note that before we set the viewport
// width, the "full width" of the page isn't properly defined, so that's why
// we have to call SetCSSViewport twice - once to set the width, and the
// second time to figure out the height based on the layout at that width.
float oldBrowserWidth = mOldViewportWidth;
mLastMetrics.mViewport.SizeTo(viewport);
if (!oldBrowserWidth) {
@ -606,13 +601,6 @@ TabChild::HandlePossibleViewportChange()
return;
}
CSSToScreenScale minScale(mInnerSize.width / pageSize.width);
minScale = clamped(minScale, viewportInfo.GetMinZoom(), viewportInfo.GetMaxZoom());
NS_ENSURE_TRUE_VOID(minScale.scale); // (return early rather than divide by 0)
viewport.height = std::max(viewport.height, screenH / minScale.scale);
SetCSSViewport(viewport);
float oldScreenWidth = mLastMetrics.mCompositionBounds.width;
if (!oldScreenWidth) {
oldScreenWidth = mInnerSize.width;

View File

@ -16,6 +16,13 @@
#define PEERCONNECTION_CONTRACTID "@mozilla.org/peerconnection;1"
#include "stun_udp_socket_filter.h"
NS_DEFINE_NAMED_CID(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CID)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsStunUDPSocketFilterHandler)
namespace sipcc
{
// Factory defined in sipcc::, defines sipcc::PeerConnectionImplConstructor
@ -27,11 +34,13 @@ NS_DEFINE_NAMED_CID(PEERCONNECTION_CID);
static const mozilla::Module::CIDEntry kCIDs[] = {
{ &kPEERCONNECTION_CID, false, nullptr, sipcc::PeerConnectionImplConstructor },
{ &kNS_STUN_UDP_SOCKET_FILTER_HANDLER_CID, false, nullptr, nsStunUDPSocketFilterHandlerConstructor },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kContracts[] = {
{ PEERCONNECTION_CONTRACTID, &kPEERCONNECTION_CID },
{ NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID, &kNS_STUN_UDP_SOCKET_FILTER_HANDLER_CID },
{ nullptr }
};

View File

@ -22,6 +22,8 @@ interface nsIUDPSocketChild : nsISupports
{
readonly attribute unsigned short localPort;
readonly attribute AUTF8String localAddress;
attribute AUTF8String filterName;
// Tell the chrome process to bind the UDP socket to a given local host and port
void bind(in nsIUDPSocketInternal socket, in AUTF8String host, in unsigned short port);

View File

@ -67,7 +67,7 @@ UDPSocketChild::Bind(nsIUDPSocketInternal *aSocket,
mSocket = aSocket;
AddIPDLReference();
gNeckoChild->SendPUDPSocketConstructor(this, nsCString(aHost), aPort);
gNeckoChild->SendPUDPSocketConstructor(this, nsCString(aHost), aPort, mFilterName);
return NS_OK;
}
@ -149,6 +149,24 @@ UDPSocketChild::GetLocalAddress(nsACString &aLocalAddress)
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::SetFilterName(const nsACString &aFilterName)
{
if (!mFilterName.IsEmpty()) {
// filter name can only be set once.
return NS_ERROR_FAILURE;
}
mFilterName = aFilterName;
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::GetFilterName(nsACString &aFilterName)
{
aFilterName = mFilterName;
return NS_OK;
}
// PUDPSocketChild Methods
bool
UDPSocketChild::RecvCallback(const nsCString &aType,

View File

@ -46,6 +46,7 @@ public:
private:
uint16_t mLocalPort;
nsCString mLocalAddress;
nsCString mFilterName;
};
} // namespace dom

View File

@ -1,7 +1,10 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIServiceManager.h"
#include "UDPSocketParent.h"
#include "nsComponentManagerUtils.h"
#include "nsIUDPSocket.h"
@ -65,6 +68,7 @@ bool
UDPSocketParent::Init(const nsCString &aHost, const uint16_t aPort)
{
nsresult rv;
NS_ASSERTION(mFilter, "No packet filter");
nsCOMPtr<nsIUDPSocket> sock =
do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
@ -124,6 +128,13 @@ UDPSocketParent::RecvData(const InfallibleTArray<uint8_t> &aData,
const uint16_t& aPort)
{
NS_ENSURE_TRUE(mSocket, true);
NS_ASSERTION(mFilter, "No packet filter");
// TODO, Bug 933102, filter packets that are sent with hostname.
// Until then we simply throw away packets that are sent to a hostname.
return true;
#if 0
// Enable this once we have filtering working with hostname delivery.
uint32_t count;
nsresult rv = mSocket->Send(aRemoteAddress,
aPort, aData.Elements(),
@ -135,6 +146,7 @@ UDPSocketParent::RecvData(const InfallibleTArray<uint8_t> &aData,
NS_ENSURE_SUCCESS(rv, true);
NS_ENSURE_TRUE(count > 0, true);
return true;
#endif
}
bool
@ -142,9 +154,20 @@ UDPSocketParent::RecvDataWithAddress(const InfallibleTArray<uint8_t>& aData,
const mozilla::net::NetAddr& aAddr)
{
NS_ENSURE_TRUE(mSocket, true);
NS_ASSERTION(mFilter, "No packet filter");
uint32_t count;
nsresult rv = mSocket->SendWithAddress(&aAddr, aData.Elements(),
aData.Length(), &count);
nsresult rv;
bool allowed;
rv = mFilter->FilterPacket(&aAddr, aData.Elements(),
aData.Length(), nsIUDPSocketFilter::SF_OUTGOING,
&allowed);
// Sending unallowed data, kill content.
NS_ENSURE_SUCCESS(rv, false);
NS_ENSURE_TRUE(allowed, false);
rv = mSocket->SendWithAddress(&aAddr, aData.Elements(),
aData.Length(), &count);
mozilla::unused <<
PUDPSocketParent::SendCallback(NS_LITERAL_CSTRING("onsent"),
UDPSendResult(rv),
@ -191,6 +214,7 @@ UDPSocketParent::OnPacketReceived(nsIUDPSocket* aSocket, nsIUDPMessage* aMessage
if (!mIPCOpen) {
return NS_OK;
}
NS_ASSERTION(mFilter, "No packet filter");
uint16_t port;
nsCString ip;
@ -205,6 +229,17 @@ UDPSocketParent::OnPacketReceived(nsIUDPSocket* aSocket, nsIUDPMessage* aMessage
const char* buffer = data.get();
uint32_t len = data.Length();
bool allowed;
mozilla::net::NetAddr addr;
fromAddr->GetNetAddr(&addr);
nsresult rv = mFilter->FilterPacket(&addr,
(const uint8_t*)buffer, len,
nsIUDPSocketFilter::SF_INCOMING,
&allowed);
// Receiving unallowed data, drop.
NS_ENSURE_SUCCESS(rv, NS_OK);
NS_ENSURE_TRUE(allowed, NS_OK);
FallibleTArray<uint8_t> fallibleArray;
if (!fallibleArray.InsertElementsAt(0, buffer, len)) {
FireInternalError(this, __LINE__);

View File

@ -1,3 +1,5 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -8,6 +10,7 @@
#include "mozilla/net/PUDPSocketParent.h"
#include "nsCOMPtr.h"
#include "nsIUDPSocket.h"
#include "nsIUDPSocketFilter.h"
namespace mozilla {
namespace dom {
@ -19,7 +22,10 @@ public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIUDPSOCKETLISTENER
UDPSocketParent() : mIPCOpen(true) {}
UDPSocketParent(nsIUDPSocketFilter* filter) :
mIPCOpen(true),
mFilter(filter) {}
virtual ~UDPSocketParent();
bool Init(const nsCString& aHost, const uint16_t aPort);
@ -37,6 +43,7 @@ private:
bool mIPCOpen;
nsCOMPtr<nsIUDPSocket> mSocket;
nsCOMPtr<nsIUDPSocketFilter> mFilter;
};
} // namespace dom

View File

@ -1,9 +0,0 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function run_test() {
Services.prefs.setBoolPref('media.peerconnection.ipc.enabled', true);
run_test_in_child("/udpsocket_child.js", function() {
Services.prefs.clearUserPref('media.peerconnection.ipc.enabled');
do_test_finished();
});
}

View File

@ -1,164 +0,0 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
const SERVER_PORT = 12345;
const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
function UDPSocketInternalImpl() {
}
UDPSocketInternalImpl.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUDPSocketInternal]),
callListenerError: function(type, message, filename, lineNumber, columnNumber) {
if (this.onerror) {
this.onerror();
} else {
do_throw('Received unexpected error: ' + message + ' at ' + filename +
':' + lineNumber + ':' + columnNumber);
}
},
callListenerReceivedData: function(type, host, port, data, dataLength) {
do_print('*** recv data(' + dataLength + ')=' + data.join() + '\n');
if (this.ondata) {
try {
this.ondata(data, dataLength);
} catch(ex) {
if (ex === Cr.NS_ERROR_ABORT)
throw ex;
do_print('Caught exception: ' + ex + '\n' + ex.stack);
do_throw('test is broken; bad ondata handler; see above');
}
} else {
do_throw('Received ' + dataLength + ' bytes of unexpected data!');
}
},
callListenerVoid: function(type) {
switch (type) {
case 'onopen':
if (this.onopen) {
this.onopen();
}
break;
case 'onclose':
if (this.onclose) {
this.onclose();
}
break;
}
},
callListenerSent: function(type, value) {
if (value != Cr.NS_OK) {
do_throw('Previous send was failed with cause: ' + value);
}
},
updateReadyState: function(readyState) {
do_print('*** current state: ' + readyState + '\n');
},
onopen: function() {},
onclose: function() {},
};
function makeSuccessCase(name) {
return function() {
do_print('got expected: ' + name);
run_next_test();
};
}
function makeJointSuccess(names) {
let funcs = {}, successCount = 0;
names.forEach(function(name) {
funcs[name] = function() {
do_print('got excepted: ' + name);
if (++successCount === names.length)
run_next_test();
};
});
return funcs;
}
function makeExpectedData(expectedData, callback) {
return function(receivedData, receivedDataLength) {
if (receivedDataLength != expectedData.length) {
do_throw('Received data size mismatched, expected ' + expectedData.length +
' but got ' + receivedDataLength);
}
for (let i = 0; i < receivedDataLength; i++) {
if (receivedData[i] != expectedData[i]) {
do_throw('Received mismatched data at position ' + i);
}
}
if (callback) {
callback();
} else {
run_next_test();
}
};
}
function makeFailureCase(name) {
return function() {
let argstr;
if (arguments.length) {
argstr = '(args: ' +
Array.map(arguments, function(x) {return x.data + ""; }).join(" ") + ')';
} else {
argstr = '(no arguments)';
}
do_throw('got unexpected: ' + name + ' ' + argstr);
};
}
function createSocketChild() {
return Cc['@mozilla.org/udp-socket-child;1']
.createInstance(Ci.nsIUDPSocketChild);
}
var UDPSocket = createSocketChild();
var callback = new UDPSocketInternalImpl();
function connectSock() {
UDPSocket.bind(callback, '127.0.0.1', SERVER_PORT);
callback.onopen = makeSuccessCase('open');
}
function sendData() {
UDPSocket.send('127.0.0.1', SERVER_PORT, DATA_ARRAY, DATA_ARRAY.length);
callback.ondata = makeExpectedData(DATA_ARRAY);
}
function clientClose() {
UDPSocket.close();
callback.ondata = makeFailureCase('data');
callback.onclose = makeSuccessCase('close');
}
function connectError() {
UDPSocket = createSocketChild();
UDPSocket.bind(callback, 'some non-IP string', SERVER_PORT);
callback.onerror = makeSuccessCase('error');
callback.onopen = makeFailureCase('open');
}
function cleanup() {
UDPSocket = null;
run_next_test();
}
add_test(connectSock);
add_test(sendData);
add_test(clientClose);
add_test(connectError);
add_test(cleanup);
function run_test() {
run_next_test();
}

View File

@ -1,10 +1,7 @@
[DEFAULT]
head =
tail =
support-files =
udpsocket_child.js
[test_tcpsocket_ipc.js]
[test_tcpserversocket_ipc.js]
[test_udpsocket_ipc.js]
run-sequentially = Uses hardcoded port, bug 903830.

View File

@ -263,6 +263,21 @@ public:
UpdateState();
}
void FormatVolume(const nsACString& aVolumeName)
{
RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
if (!vol) {
return;
}
if (vol->IsFormatRequested()) {
return;
}
vol->SetFormatRequested(true);
DBG("Calling UpdateState due to volume %s formatting set to %d",
vol->NameStr(), (int)vol->IsFormatRequested());
UpdateState();
}
private:
AutoVolumeEventObserver mVolumeEventObserver;
@ -428,14 +443,14 @@ AutoMounter::UpdateState()
continue;
}
if (tryToShare && vol->IsSharingEnabled()) {
if ((tryToShare && vol->IsSharingEnabled()) || vol->IsFormatRequested()) {
// We're going to try to unmount and share the volumes
switch (volState) {
case nsIVolume::STATE_MOUNTED: {
if (vol->IsMountLocked()) {
// The volume is currently locked, so leave it in the mounted
// state.
LOGW("UpdateState: Mounted volume %s is locked, not sharing",
LOGW("UpdateState: Mounted volume %s is locked, not sharing or formatting",
vol->NameStr());
break;
}
@ -444,7 +459,11 @@ AutoMounter::UpdateState()
// apps which watch device storage notifications to see the volume
// go into the shared state, and prompt them to close any open files
// that they might have.
vol->SetIsSharing(true);
if (tryToShare && vol->IsSharingEnabled()) {
vol->SetIsSharing(true);
} else if (vol->IsFormatRequested()){
vol->SetIsFormatting(true);
}
// Check to see if there are any open files on the volume and
// don't initiate the unmount while there are open files.
@ -461,7 +480,7 @@ AutoMounter::UpdateState()
fileInfo.mComm.get(),
fileInfo.mExe.get());
} while (fileFinder.Next(&fileInfo));
LOGW("UpdateState: Mounted volume %s has open files, not sharing",
LOGW("UpdateState: Mounted volume %s has open files, not sharing or formatting",
vol->NameStr());
// Check again in a few seconds to see if the files are closed.
@ -495,10 +514,23 @@ AutoMounter::UpdateState()
return; // UpdateState will be called again when the Unmount command completes
}
case nsIVolume::STATE_IDLE: {
// Volume is unmounted. We can go ahead and share.
LOG("UpdateState: Sharing %s", vol->NameStr());
vol->StartShare(mResponseCallback);
return; // UpdateState will be called again when the Share command completes
LOG("UpdateState: Volume %s is nsIVolume::STATE_IDLE", vol->NameStr());
if (vol->IsFormatting() && !vol->IsFormatRequested()) {
vol->SetFormatRequested(false);
LOG("UpdateState: Mounting %s", vol->NameStr());
vol->StartMount(mResponseCallback);
break;
}
if (tryToShare && vol->IsSharingEnabled()) {
// Volume is unmounted. We can go ahead and share.
LOG("UpdateState: Sharing %s", vol->NameStr());
vol->StartShare(mResponseCallback);
} else if (vol->IsFormatRequested()){
// Volume is unmounted. We can go ahead and format.
LOG("UpdateState: Formatting %s", vol->NameStr());
vol->StartFormat(mResponseCallback);
}
return; // UpdateState will be called again when the Share/Format command completes
}
default: {
// Not in a state that we can do anything about.
@ -578,6 +610,15 @@ SetAutoMounterSharingModeIOThread(const nsCString& aVolumeName, const bool& aAll
sAutoMounter->SetSharingMode(aVolumeName, aAllowSharing);
}
static void
AutoMounterFormatVolumeIOThread(const nsCString& aVolumeName)
{
MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
MOZ_ASSERT(sAutoMounter);
sAutoMounter->FormatVolume(aVolumeName);
}
static void
UsbCableEventIOThread()
{
@ -732,10 +773,19 @@ SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing)
{
XRE_GetIOMessageLoop()->PostTask(
FROM_HERE,
NewRunnableFunction(SetAutoMounterSharingModeIOThread,
NewRunnableFunction(SetAutoMounterSharingModeIOThread,
aVolumeName, aAllowSharing));
}
void
AutoMounterFormatVolume(const nsCString& aVolumeName)
{
XRE_GetIOMessageLoop()->PostTask(
FROM_HERE,
NewRunnableFunction(AutoMounterFormatVolumeIOThread,
aVolumeName));
}
void
ShutdownAutoMounter()
{

View File

@ -59,6 +59,15 @@ GetAutoMounterStatus();
void
SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing);
/**
* Formats the volume with specified volume name.
*
* If the volume is ready to format, automounter
* will unmount it, format it and then mount it again.
*/
void
AutoMounterFormatVolume(const nsCString& aVolumeName);
/**
* Shuts down the automounter.
*

Some files were not shown because too many files have changed in this diff Show More