This commit is contained in:
Ryan VanderMeulen 2013-12-02 10:38:28 -05:00
Родитель 2f6d4e0b07 668fbfecaf
Коммит bbe673a3ae
225 изменённых файлов: 8135 добавлений и 1422 удалений

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

@ -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>

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

@ -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";

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

@ -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]

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

@ -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);
}

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

@ -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)

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

@ -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;"

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

@ -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.

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

@ -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();
}

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

@ -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();

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

@ -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) {

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

@ -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,

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

@ -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]

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

@ -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;

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
}

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

@ -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();
});

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

@ -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();
});

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

@ -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]
];

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

@ -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;
});

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

@ -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];
}

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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);
});
}

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

@ -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>

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

@ -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);

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

@ -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);
},

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

@ -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"),

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

@ -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);

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

@ -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

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

@ -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);

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

@ -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;

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

@ -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;
}
}

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

@ -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;
}
}

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

@ -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.
*/

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

@ -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…">

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

@ -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)

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

@ -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;
}

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

@ -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;
}

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

@ -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;
}

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

@ -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;
}

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

@ -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);
}

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

@ -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),

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

@ -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;

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

@ -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;

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

@ -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;
}

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

@ -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;
}

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

@ -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)

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

@ -1,56 +0,0 @@
#!/bin/sh
#
# 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/.
#
# This script will print the depth path for a mozilla directory based
# on the info in Makefile
#
# It's a hack. It's brute force. It's horrible.
# It don't use Artificial Intelligence. It don't use Virtual Reality.
# It's not perl. It's not python. But it works.
#
# Usage: print-depth-path.sh
#
# Send comments, improvements, bugs to jim_nance@yahoo.com
#
# Make sure a Makefile exists
if [ ! -f Makefile ]
then
echo
echo "There ain't no 'Makefile' over here: $pwd, dude."
echo
exit
fi
# awk can be quite primitave. Try enhanced versions first
for AWK in gawk nawk awk; do
if type $AWK 2>/dev/null 1>/dev/null; then
break;
fi
done
$AWK -v PWD=`pwd` '
{
if($1 == "DEPTH") {
DEPTH=$0
}
}
END {
sub("^.*DEPTH.*=[ \t]*", "", DEPTH)
dlen = split(DEPTH, darray, "/")
plen = split(PWD, parray, "/")
fsep=""
for(i=plen-dlen; i<=plen; i++) {
printf("%s%s", fsep, parray[i])
fsep="/"
}
printf("\n")
}' Makefile

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

@ -13,105 +13,109 @@
# Support usage outside of config/rules.mk
ifndef INCLUDED_DEBUGMAKE_MK #{
define shell_quote
'$(subst ','\'',$(1))'
endef
echo-variable-%:
@echo "$($*)"
@echo $(call shell_quote,$($*))
echo-tiers:
@echo $(TIERS)
echo-tier-dirs:
@$(foreach tier,$(TIERS),echo '$(tier):'; echo ' dirs: $(tier_$(tier)_dirs)'; echo ' staticdirs: $(tier_$(tier)_staticdirs)'; )
@$(foreach tier,$(TIERS),echo '$(tier):'; echo ' dirs: $(tier_$(tier)_dirs)'; $(if $(tier_$(tier)_staticdirs),echo ' staticdirs: $(tier_$(tier)_staticdirs)';) )
echo-dirs:
@echo $(DIRS)
@echo $(call shell_quote,$(DIRS))
echo-module:
@echo $(MODULE)
define print_var
@printf '%20s = %s\n' $1 $(call shell_quote,$($1))
echo-depth-path:
@$(topsrcdir)/build/unix/print-depth-path.sh
endef
echo-module-name:
@$(topsrcdir)/build/package/rpm/print-module-name.sh
echo-module-filelist:
@$(topsrcdir)/build/package/rpm/print-module-filelist.sh
define print_vars
$(foreach var,$1,$(call print_var,$(var)))
endef
showtargs:
ifneq (,$(filter $(PROGRAM) $(HOST_PROGRAM) $(SIMPLE_PROGRAMS) $(HOST_LIBRARY) $(LIBRARY) $(SHARED_LIBRARY),$(TARGETS)))
@echo --------------------------------------------------------------------------------
@echo "PROGRAM = $(PROGRAM)"
@echo "SIMPLE_PROGRAMS = $(SIMPLE_PROGRAMS)"
@echo "LIBRARY = $(LIBRARY)"
@echo "SHARED_LIBRARY = $(SHARED_LIBRARY)"
@echo "SHARED_LIBRARY_LIBS = $(SHARED_LIBRARY_LIBS)"
@echo "LIBS = $(LIBS)"
@echo "DEF_FILE = $(DEF_FILE)"
@echo "IMPORT_LIBRARY = $(IMPORT_LIBRARY)"
@echo "STATIC_LIBS = $(STATIC_LIBS)"
@echo "SHARED_LIBS = $(SHARED_LIBS)"
@echo "EXTRA_DSO_LDOPTS = $(EXTRA_DSO_LDOPTS)"
@echo "DEPENDENT_LIBS = $(DEPENDENT_LIBS)"
$(call print_vars,\
PROGRAM \
SIMPLE_PROGRAMS \
LIBRARY \
SHARED_LIBRARY \
SHARED_LIBRARY_LIBS \
LIBS \
DEF_FILE \
IMPORT_LIBRARY \
STATIC_LIBS \
EXTRA_DSO_LDOPTS \
DEPENDENT_LIBS \
)
@echo --------------------------------------------------------------------------------
endif
$(LOOP_OVER_PARALLEL_DIRS)
$(LOOP_OVER_DIRS)
$(LOOP_OVER_TOOL_DIRS)
showbuild:
@echo "MOZ_BUILD_ROOT = $(MOZ_BUILD_ROOT)"
@echo "MOZ_WIDGET_TOOLKIT = $(MOZ_WIDGET_TOOLKIT)"
@echo "CC = $(CC)"
@echo "CXX = $(CXX)"
@echo "CCC = $(CCC)"
@echo "CPP = $(CPP)"
@echo "LD = $(LD)"
@echo "AR = $(AR)"
@echo "IMPLIB = $(IMPLIB)"
@echo "FILTER = $(FILTER)"
@echo "MKSHLIB = $(MKSHLIB)"
@echo "MKCSHLIB = $(MKCSHLIB)"
@echo "RC = $(RC)"
@echo "MC = $(MC)"
@echo "CFLAGS = $(CFLAGS)"
@echo "OS_CFLAGS = $(OS_CFLAGS)"
@echo "COMPILE_CFLAGS = $(COMPILE_CFLAGS)"
@echo "CXXFLAGS = $(CXXFLAGS)"
@echo "OS_CXXFLAGS = $(OS_CXXFLAGS)"
@echo "COMPILE_CXXFLAGS = $(COMPILE_CXXFLAGS)"
@echo "COMPILE_CMFLAGS = $(COMPILE_CMFLAGS)"
@echo "COMPILE_CMMFLAGS = $(COMPILE_CMMFLAGS)"
@echo "LDFLAGS = $(LDFLAGS)"
@echo "OS_LDFLAGS = $(OS_LDFLAGS)"
@echo "DSO_LDOPTS = $(DSO_LDOPTS)"
@echo "OS_INCLUDES = $(OS_INCLUDES)"
@echo "OS_LIBS = $(OS_LIBS)"
@echo "EXTRA_LIBS = $(EXTRA_LIBS)"
@echo "BIN_FLAGS = $(BIN_FLAGS)"
@echo "INCLUDES = $(INCLUDES)"
@echo "DEFINES = $(DEFINES)"
@echo "ACDEFINES = $(ACDEFINES)"
@echo "BIN_SUFFIX = $(BIN_SUFFIX)"
@echo "LIB_SUFFIX = $(LIB_SUFFIX)"
@echo "DLL_SUFFIX = $(DLL_SUFFIX)"
@echo "IMPORT_LIB_SUFFIX = $(IMPORT_LIB_SUFFIX)"
@echo "INSTALL = $(INSTALL)"
@echo "VPATH = $(VPATH)"
$(call print_vars,\
MOZ_BUILD_ROOT \
MOZ_WIDGET_TOOLKIT \
CC \
CXX \
CCC \
CPP \
LD \
AR \
IMPLIB \
FILTER \
MKSHLIB \
MKCSHLIB \
RC \
MC \
CFLAGS \
OS_CFLAGS \
COMPILE_CFLAGS \
CXXFLAGS \
OS_CXXFLAGS \
COMPILE_CXXFLAGS \
COMPILE_CMFLAGS \
COMPILE_CMMFLAGS \
LDFLAGS \
OS_LDFLAGS \
DSO_LDOPTS \
OS_INCLUDES \
OS_LIBS \
EXTRA_LIBS \
BIN_FLAGS \
INCLUDES \
DEFINES \
ACDEFINES \
BIN_SUFFIX \
LIB_SUFFIX \
DLL_SUFFIX \
IMPORT_LIB_SUFFIX \
INSTALL \
VPATH \
)
showhost:
@echo "HOST_CC = $(HOST_CC)"
@echo "HOST_CXX = $(HOST_CXX)"
@echo "HOST_CFLAGS = $(HOST_CFLAGS)"
@echo "HOST_LDFLAGS = $(HOST_LDFLAGS)"
@echo "HOST_LIBS = $(HOST_LIBS)"
@echo "HOST_EXTRA_LIBS = $(HOST_EXTRA_LIBS)"
@echo "HOST_EXTRA_DEPS = $(HOST_EXTRA_DEPS)"
@echo "HOST_PROGRAM = $(HOST_PROGRAM)"
@echo "HOST_OBJS = $(HOST_OBJS)"
@echo "HOST_PROGOBJS = $(HOST_PROGOBJS)"
@echo "HOST_LIBRARY = $(HOST_LIBRARY)"
showbuildmods::
@echo "Module dirs = $(BUILD_MODULE_DIRS)"
$(call print_vars,\
HOST_CC \
HOST_CXX \
HOST_CFLAGS \
HOST_LDFLAGS \
HOST_LIBS \
HOST_EXTRA_LIBS \
HOST_EXTRA_DEPS \
HOST_PROGRAM \
HOST_OBJS \
HOST_PROGOBJS \
HOST_LIBRARY \
)
INCLUDED_DEBUGMAKE_MK = 1
endif #}

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

@ -364,7 +364,7 @@ ifdef MOZ_UPDATE_XTERM
# Its good not to have a newline at the end of the titlebar string because it
# makes the make -s output easier to read. Echo -n does not work on all
# platforms, but we can trick printf into doing it.
UPDATE_TITLE = printf "\033]0;%s in %s\007" $(1) $(shell $(BUILD_TOOLS)/print-depth-path.sh)/$(2) ;
UPDATE_TITLE = printf "\033]0;%s in %s\007" $(1) $(relativesrcdir)/$(2) ;
endif
ifdef MACH

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

@ -21,7 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=238987
var shouldStop = false;
var modifier = 0;
var expectedResult = "i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12";
var expectedResult = "i1,i2,i3,i4,i5,i6,i7,i8,number,i9,i10,i11,i12";
var forwardFocusArray = expectedResult.split(",");
var backwardFocusArray = expectedResult.split(",");
var forwardBlurArray = expectedResult.split(",");
@ -202,6 +202,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=238987
<tr>
<td>type="file"</td><td><input type="file" id="i8"></td>
</tr>
<tr>
<td>type="number"</td><td><input type="number" id="number"></td>
</tr>
<tr>
<td>button</td><td><button id="i9">button</button></td>
</tr>

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

@ -21,6 +21,7 @@
#include "nsFocusManager.h"
#include "nsNumberControlFrame.h"
#include "nsPIDOMWindow.h"
#include "nsRepeatService.h"
#include "nsContentCID.h"
#include "nsIComponentManager.h"
#include "nsIDOMHTMLFormElement.h"
@ -1092,6 +1093,7 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<nsINodeInfo> aNodeInfo,
, mHasRange(false)
, mIsDraggingRange(false)
, mProgressTimerIsActive(false)
, mNumberControlSpinnerIsSpinning(false)
{
// We are in a type=text so we now we currenty need a nsTextEditorState.
mInputData.mState = new nsTextEditorState(this);
@ -1114,6 +1116,9 @@ HTMLInputElement::~HTMLInputElement()
if (mFileList) {
mFileList->Disconnect();
}
if (mNumberControlSpinnerIsSpinning) {
StopNumberControlSpinnerSpin();
}
DestroyImageLoadingContent();
FreeData();
}
@ -2582,6 +2587,26 @@ HTMLInputElement::Notify(nsITimer* aTimer)
return NS_ERROR_INVALID_POINTER;
}
/* static */ void
HTMLInputElement::HandleNumberControlSpin(void* aData)
{
HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
"Should have called nsRepeatService::Stop()");
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(input->GetPrimaryFrame());
if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
// Type has changed (and possibly our frame type hasn't been updated yet)
// or else we've lost our frame. Either way, stop the timer and don't do
// anything else.
input->StopNumberControlSpinnerSpin();
} else {
input->ApplyStep(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
}
}
void
HTMLInputElement::MaybeDispatchProgressEvent(bool aFinalProgress)
{
@ -3274,6 +3299,57 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
}
}
if (mType == NS_FORM_INPUT_NUMBER &&
aVisitor.mEvent->mFlags.mIsTrusted) {
if (mNumberControlSpinnerIsSpinning) {
// If the timer is running the user has depressed the mouse on one of the
// spin buttons. If the mouse exits the button we either want to reverse
// the direction of spin if it has moved over the other button, or else
// we want to end the spin. We do this here (rather than in
// PostHandleEvent) because we don't want to let content preventDefault()
// the end of the spin.
if (aVisitor.mEvent->message == NS_MOUSE_MOVE) {
// Be aggressive about stopping the spin:
bool stopSpin = true;
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
switch (numberControlFrame->GetSpinButtonForPointerEvent(
aVisitor.mEvent->AsMouseEvent())) {
case nsNumberControlFrame::eSpinButtonUp:
mNumberControlSpinnerSpinsUp = true;
stopSpin = false;
break;
case nsNumberControlFrame::eSpinButtonDown:
mNumberControlSpinnerSpinsUp = false;
stopSpin = false;
break;
}
}
if (stopSpin) {
StopNumberControlSpinnerSpin();
}
} else if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_UP) {
StopNumberControlSpinnerSpin();
}
}
if (aVisitor.mEvent->message == NS_FOCUS_CONTENT ||
aVisitor.mEvent->message == NS_BLUR_CONTENT) {
nsIFrame* frame = GetPrimaryFrame();
if (frame) {
if (aVisitor.mEvent->message == NS_FOCUS_CONTENT) {
// Tell our frame it's getting focus so that it can make sure focus
// is moved to our anonymous text control.
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
}
}
}
}
}
nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
// We do this after calling the base class' PreHandleEvent so that
@ -3397,6 +3473,34 @@ HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
false);
}
void
HTMLInputElement::StartNumberControlSpinnerSpin()
{
MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
mNumberControlSpinnerIsSpinning = true;
nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
// Capture the mouse so that we can tell if the pointer moves from one
// spin button to the other, or to some other element:
nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
}
void
HTMLInputElement::StopNumberControlSpinnerSpin()
{
if (mNumberControlSpinnerIsSpinning) {
if (nsIPresShell::GetCapturingContent() == this) {
nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
}
nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
mNumberControlSpinnerIsSpinning = false;
}
}
static bool
SelectTextFieldOnFocus()
{
@ -3484,9 +3588,12 @@ HTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
GetValueInternal(mFocusedValue);
}
if (mIsDraggingRange &&
aVisitor.mEvent->message == NS_BLUR_CONTENT) {
FinishRangeThumbDrag();
if (aVisitor.mEvent->message == NS_BLUR_CONTENT) {
if (mIsDraggingRange) {
FinishRangeThumbDrag();
} else if (mNumberControlSpinnerIsSpinning) {
StopNumberControlSpinnerSpin();
}
}
UpdateValidityUIBits(aVisitor.mEvent->message == NS_FOCUS_CONTENT);
@ -3601,7 +3708,32 @@ HTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
}
if (NS_SUCCEEDED(rv)) {
if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
if (mType == NS_FORM_INPUT_NUMBER &&
keyEvent && keyEvent->message == NS_KEY_PRESS &&
aVisitor.mEvent->mFlags.mIsTrusted &&
(keyEvent->keyCode == NS_VK_UP || keyEvent->keyCode == NS_VK_DOWN) &&
!(keyEvent->IsShift() || keyEvent->IsControl() ||
keyEvent->IsAlt() || keyEvent->IsMeta() ||
keyEvent->IsAltGraph() || keyEvent->IsFn() ||
keyEvent->IsOS())) {
// We handle the up/down arrow keys specially for <input type=number>.
// On some platforms the editor for the nested text control will
// process these keys to send the cursor to the start/end of the text
// control and as a result aVisitor.mEventStatus will already have been
// set to nsEventStatus_eConsumeNoDefault. However, we know that
// whenever the up/down arrow keys cause the value of the number
// control to change the string in the text control will change, and
// the cursor will be moved to the end of the text control, overwriting
// the editor's handling of up/down keypress events. For that reason we
// just ignore aVisitor.mEventStatus here and go ahead and handle the
// event to increase/decrease the value of the number control.
// XXX we still need to allow script to call preventDefault() on the
// event, but right now we can't tell the difference between the editor
// on script doing that (bug 930374).
ApplyStep(keyEvent->keyCode == NS_VK_UP ? 1 : -1);
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
} else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
switch (aVisitor.mEvent->message) {
case NS_FOCUS_CONTENT:
@ -3812,7 +3944,43 @@ HTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
rv = NS_ERROR_FAILURE;
}
}
}
if (mType == NS_FORM_INPUT_NUMBER &&
aVisitor.mEvent->mFlags.mIsTrusted) {
if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
!(mouseEvent->IsShift() || mouseEvent->IsControl() ||
mouseEvent->IsAlt() || mouseEvent->IsMeta() ||
mouseEvent->IsAltGraph() || mouseEvent->IsFn() ||
mouseEvent->IsOS())) {
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_DOWN) {
switch (numberControlFrame->GetSpinButtonForPointerEvent(
aVisitor.mEvent->AsMouseEvent())) {
case nsNumberControlFrame::eSpinButtonUp:
ApplyStep(1);
mNumberControlSpinnerSpinsUp = true;
StartNumberControlSpinnerSpin();
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
break;
case nsNumberControlFrame::eSpinButtonDown:
ApplyStep(-1);
mNumberControlSpinnerSpinsUp = false;
StartNumberControlSpinnerSpin();
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
break;
}
}
}
}
if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
// We didn't handle this to step up/down. Whatever this was, be
// aggressive about stopping the spin. (And don't set
// nsEventStatus_eConsumeNoDefault after doing so, since that
// might prevent, say, the context menu from opening.)
StopNumberControlSpinnerSpin();
}
}
break;
}
@ -4171,9 +4339,9 @@ HTMLInputElement::SanitizeValue(nsAString& aValue)
break;
case NS_FORM_INPUT_NUMBER:
{
nsresult ec;
double val = PromiseFlatString(aValue).ToDouble(&ec);
if (NS_FAILED(ec) || !IsFinite(val)) {
Decimal value;
bool ok = ConvertStringToNumber(aValue, value);
if (!ok) {
aValue.Truncate();
}
}
@ -5605,8 +5773,7 @@ HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t*
}
if (IsSingleLineTextControl(false) ||
mType == NS_FORM_INPUT_RANGE ||
mType == NS_FORM_INPUT_NUMBER) {
mType == NS_FORM_INPUT_RANGE) {
*aIsFocusable = true;
return false;
}
@ -5617,11 +5784,17 @@ HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t*
const bool defaultFocusable = true;
#endif
if (mType == NS_FORM_INPUT_FILE) {
if (mType == NS_FORM_INPUT_FILE ||
mType == NS_FORM_INPUT_NUMBER) {
if (aTabIndex) {
// We only want our native anonymous child to be tabable to, not ourself.
*aTabIndex = -1;
}
*aIsFocusable = defaultFocusable;
if (mType == NS_FORM_INPUT_NUMBER) {
*aIsFocusable = true;
} else {
*aIsFocusable = defaultFocusable;
}
return true;
}

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

@ -678,6 +678,15 @@ public:
HTMLInputElement* GetOwnerNumberControl();
void StartNumberControlSpinnerSpin();
void StopNumberControlSpinnerSpin();
/**
* The callback function used by the nsRepeatService that we use to spin the
* spinner for <input type=number>.
*/
static void HandleNumberControlSpin(void* aData);
bool MozIsTextField(bool aExcludePassword);
nsIEditor* GetEditor();
@ -1231,6 +1240,8 @@ protected:
bool mHasRange : 1;
bool mIsDraggingRange : 1;
bool mProgressTimerIsActive : 1;
bool mNumberControlSpinnerIsSpinning : 1;
bool mNumberControlSpinnerSpinsUp : 1;
private:
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,

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

@ -2684,7 +2684,7 @@ public:
// These notifications run on the media graph thread so we need to
// dispatch events to the main thread.
virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked)
virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) MOZ_OVERRIDE
{
nsCOMPtr<nsIRunnable> event;
if (aBlocked == BLOCKED) {
@ -2694,20 +2694,21 @@ public:
}
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
}
virtual void NotifyFinished(MediaStreamGraph* aGraph)
virtual void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE
{
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &StreamListener::DoNotifyFinished);
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
}
virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph)
virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) MOZ_OVERRIDE
{
MutexAutoLock lock(mMutex);
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
}
virtual void NotifyOutput(MediaStreamGraph* aGraph)
virtual void NotifyOutput(MediaStreamGraph* aGraph,
GraphTime aCurrentTime) MOZ_OVERRIDE
{
MutexAutoLock lock(mMutex);
if (mPendingNotifyOutput)

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

@ -23,7 +23,11 @@ support-files =
[test_input_event.html]
[test_input_file_picker.html]
[test_input_list_attribute.html]
[test_input_number_rounding.html]
[test_input_number_key_events.html]
# Spin buttons are hidden on Firefox OS and Firefox for Android:
#skip-if = toolkit == "gonk" || os == 'android'
#[test_input_number_mouse_events.html]
#[test_input_number_rounding.html]
[test_input_range_attr_order.html]
[test_input_range_key_events.html]
[test_input_range_mouse_and_touch_events.html]
@ -31,6 +35,7 @@ support-files =
[test_input_sanitization.html]
[test_input_textarea_set_value_no_scroll.html]
[test_input_typing_sanitization.html]
[test_input_untrusted_key_events.html]
[test_input_url.html]
[test_label_control_attribute.html]
[test_max_attribute.html]

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

@ -175,7 +175,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=722599
number.value = "";
number.focus();
synthesizeKey("1", {});
is(numberChange, 0, "Change event shouldn't be dispatched on number input element for keyboard input until it is looses focus");
is(numberChange, 0, "Change event shouldn't be dispatched on number input element for keyboard input until it loses focus");
number.blur();
is(numberChange, 1, "Change event should be dispatched on number input element on blur");
@ -187,7 +187,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=722599
is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes that don't change its value");
range.focus();
synthesizeKey("VK_HOME", {});
is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes until it is looses focus");
is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes until it loses focus");
range.blur();
is(rangeChange, 1, "Change event should be dispatched on range input element on blur");
range.focus();

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

@ -0,0 +1,186 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=935506
-->
<head>
<title>Test key events for number control</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<meta charset="UTF-8">
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935506">Mozilla Bug 935506</a>
<p id="display"></p>
<div id="content">
<input id="input" type="number">
</div>
<pre id="test">
<script type="application/javascript">
/**
* Test for Bug 935506
* This test checks how the value of <input type=number> changes in response to
* key events while it is in various states.
**/
SimpleTest.waitForExplicitFinish();
// Turn off Spatial Navigation because it hijacks arrow keydown events:
SpecialPowers.setBoolPref("snav.enabled", false);
SimpleTest.waitForFocus(function() {
test();
SimpleTest.finish();
});
const defaultMinimum = "NaN";
const defaultMaximum = "NaN";
const defaultStep = 1;
// Helpers:
// For the sake of simplicity, we do not currently support fractional value,
// step, etc.
function getMinimum(element) {
return Number(element.min || defaultMinimum);
}
function getMaximum(element) {
return Number(element.max || defaultMaximum);
}
function getDefaultValue(element) {
return 0;
}
function getValue(element) {
return Number(element.value || getDefaultValue(element));
}
function getStep(element) {
if (element.step == "any") {
return "any";
}
var step = Number(element.step || defaultStep);
return step <= 0 ? defaultStep : step;
}
function getStepBase(element) {
return Number(element.getAttribute("min") || "NaN") ||
Number(element.getAttribute("value") || "NaN") || 0;
}
function floorModulo(x, y) {
return (x - y * Math.floor(x / y));
}
function expectedValueAfterStepUpOrDown(stepFactor, element) {
var value = getValue(element);
if (isNaN(value)) {
value = 0;
}
var step = getStep(element);
if (step == "any") {
return value;
}
var minimum = getMinimum(element);
var maximum = getMaximum(element);
if (!isNaN(maximum)) {
// "max - (max - stepBase) % step" is the nearest valid value to max.
maximum = maximum - floorModulo(maximum - getStepBase(element), step);
}
// Cases where we are clearly going in the wrong way.
// We don't use ValidityState because we can be higher than the maximal
// allowed value and still not suffer from range overflow in the case of
// of the value specified in @max isn't in the step.
if ((value <= minimum && stepFactor < 0) ||
(value >= maximum && stepFactor > 0)) {
return value;
}
if (element.validity.stepMismatch &&
value != minimum && value != maximum) {
if (stepFactor > 0) {
value -= floorModulo(value - getStepBase(element), step);
} else if (stepFactor < 0) {
value -= floorModulo(value - getStepBase(element), step);
value += step;
}
}
value += step * stepFactor;
// When stepUp() is called and the value is below minimum, we should clamp on
// minimum unless stepUp() moves us higher than minimum.
if (element.validity.rangeUnderflow && stepFactor > 0 &&
value <= minimum) {
value = minimum;
} else if (element.validity.rangeOverflow && stepFactor < 0 &&
value >= maximum) {
value = maximum;
} else if (stepFactor < 0 && !isNaN(minimum)) {
value = Math.max(value, minimum);
} else if (stepFactor > 0 && !isNaN(maximum)) {
value = Math.min(value, maximum);
}
return value;
}
function expectedValAfterKeyEvent(key, element) {
return expectedValueAfterStepUpOrDown(key == "VK_UP" ? 1 : -1, element);
}
function test() {
var elem = document.getElementById("input");
elem.focus();
elem.min = -3;
elem.max = 3;
elem.step = 2;
var defaultValue = 0;
var oldVal, expectedVal;
for (key of ["VK_UP", "VK_DOWN"]) {
// Start at middle:
oldVal = elem.value = 0;
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test " + key + " for number control with value set to the midpoint (" + oldVal + ")");
// Same again:
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
// Start at maximum:
oldVal = elem.value = elem.max;
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test " + key + " for number control with value set to the maximum (" + oldVal + ")");
// Same again:
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
// Start at minimum:
oldVal = elem.value = elem.min;
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test " + key + " for number control with value set to the minimum (" + oldVal + ")");
// Same again:
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
}
}
</script>
</pre>
</body>
</html>

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

@ -0,0 +1,111 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=935501
-->
<head>
<title>Test mouse events for number</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
input {
margin: 0 ! important;
border: 0 ! important;
padding: 0 ! important;
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935501">Mozilla Bug 935501</a>
<p id="display"></p>
<div id="content">
<input id="input" type="number">
</div>
<pre id="test">
<script type="application/javascript">
/**
* Test for Bug 935501
* This test checks how the value of <input type=number> changes in response to
* various mouse events.
**/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
test();
SimpleTest.finish();
});
function test() {
var input = document.getElementById("input");
var inputRect = input.getBoundingClientRect();
// Points over the input's spin-up and spin-down buttons (as offsets from the
// top-left of the input's bounding client rect):
const SPIN_UP_X = inputRect.width - 3;
const SPIN_UP_Y = 3;
const SPIN_DOWN_X = inputRect.width - 3;
const SPIN_DOWN_Y = inputRect.height - 3;
// Test click on spin-up button:
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
is(input.value, 1, "Test step-up on mousedown on spin-up button");
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
is(input.value, 1, "Test mouseup on spin-up button");
// Test click on spin-down button:
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
is(input.value, 0, "Test step-down on mousedown on spin-down button");
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
is(input.value, 0, "Test mouseup on spin-down button");
// Test that preventDefault() works:
function preventDefault(e) {
e.preventDefault();
}
input.value = 1;
input.addEventListener("mousedown", preventDefault, false);
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, {});
is(input.value, 1, "Test that preventDefault() works for click on spin-up button");
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, {});
is(input.value, 1, "Test that preventDefault() works for click on spin-down button");
input.removeEventListener("mousedown", preventDefault, false);
// XXX TODO
// Test spining when the mouse button is kept depressed on the spin-up
// button:
// XXX TODO
// Test spining when the mouse button is kept depressed on the spin-down
// button:
// XXX TODO
// Test spin direction reverses when the mouse button is depressod on the
// spin-up button, then moved over the spin-down button once the spin begins:
// XXX TODO
// Test spin direction reverses when the mouse button is depressod on the
// spin-down button, then moved over the spin-up button once the spin begins:
// XXX TODO
// Test that the spin is stopped when the mouse button is depressod on the
// spin-down button, then moved outside both buttons once the spin starts:
// XXX TODO
// Test that the spin is stopped when the mouse button is depressod on the
// spin-up button, then moved outside both buttons once the spin starts:
// XXX TODO
// Test that changing the input type in the middle of a spin cancels the spin:
// XXX TODO
// Check that we do not spin when a mousedown occurs outside the spin
// buttons and then the mouse is moved over the buttons:
}
</script>
</pre>
</body>
</html>

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

@ -69,6 +69,9 @@ function test() {
* of them will fail when the widget will be implemented.
*/
/* No other implementations implement this, so we don't either, for now.
Seems like it might be nice though.
for (var i = 1; i < pgUpDnVals.length; ++i) {
synthesizeKey("VK_PAGE_UP", {});
todo_is(elem.value, pgUpDnVals[i], "Test VK_PAGE_UP");
@ -82,10 +85,11 @@ function test() {
todo_is(elem.value, pgUpDnVals[i], "Test VK_PAGE_DOWN");
is(elem.validity.valid, true, "Check element is valid for value " + pgUpDnVals[i]);
}
*/
for (var i = 1; i < stepVals.length; ++i) {
synthesizeKey("VK_UP", {});
todo_is(elem.value, stepVals[i], "Test VK_UP");
is(elem.value, stepVals[i], "Test VK_UP");
is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
}
@ -93,7 +97,7 @@ function test() {
synthesizeKey("VK_DOWN", {});
// TODO: this condition is there because the todo_is() below would pass otherwise.
if (stepVals[i] == 0) { continue; }
todo_is(elem.value, stepVals[i], "Test VK_DOWN");
is(elem.value, stepVals[i], "Test VK_DOWN");
is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
}

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

@ -0,0 +1,99 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for untrusted DOM KeyboardEvent on input element</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content">
<input id="input">
</div>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runNextTest, window);
const kTests = Iterator([
{ type: "text", value: "foo", key: "b", expectedNewValue: "foo" },
{ type: "number", value: "123", key: "4", expectedNewValue: "123" },
{ type: "number", value: "123", key: KeyEvent.DOM_VK_UP, expectedNewValue: "123" },
{ type: "number", value: "123", key: KeyEvent.DOM_VK_DOWN, expectedNewValue: "123" },
]);
function sendUntrustedKeyEvent(eventType, keyCode, target) {
var evt = document.createEvent("KeyboardEvent");
var canBubbleArg = true;
var cancelableArg = true;
var viewArg = document.defaultView;
var ctrlKeyArg = false;
var altKeyArg = false;
var shiftKeyArg = false;
var metaKeyArg = false;
var keyCodeArg = keyCode;
var charCodeArg = 0;
evt.initKeyEvent(eventType, canBubbleArg, cancelableArg, viewArg,
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
keyCodeArg, charCodeArg);
target.dispatchEvent(evt);
}
var input = document.getElementById("input");
var gotEvents = {};
function handleEvent(event) {
gotEvents[event.type] = true;
}
input.addEventListener("keydown", handleEvent, false);
input.addEventListener("keyup", handleEvent, false);
input.addEventListener("keypress", handleEvent, false);
var previousTest = null;
function runNextTest() {
if (previousTest) {
var msg = "For <input " + "type=" + previousTest.type + ">, ";
is(gotEvents.keydown, true, msg + "checking got keydown");
is(gotEvents.keyup, true, msg + "checking got keyup");
is(gotEvents.keypress, true, msg + "checking got keypress");
is(input.value, previousTest.expectedNewValue, msg + "checking element " +
" after being sent '" + previousTest.key + "' key events");
}
// reset flags
gotEvents.keydown = false;
gotEvents.keyup = false;
gotEvents.keypress = false;
try {
var [index, test] = kTests.next();
} catch(e) {
if (e == StopIteration) {
SimpleTest.finish();
return; // We're all done
}
throw e;
}
input.type = test.type;
input.focus(); // make sure we still have focus after type change
input.value = test.value;
sendUntrustedKeyEvent("keydown", test.key, input);
sendUntrustedKeyEvent("keyup", test.key, input);
sendUntrustedKeyEvent("keypress", test.key, input);
previousTest = test;
SimpleTest.executeSoon(runNextTest);
};
</script>
</pre>
</body>
</html>

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

@ -149,6 +149,12 @@ var focusableElements = [
"<input type=\"text\" tabindex=\"1\">",
"<input type=\"text\" contenteditable=\"true\">",
"<input type=\"number\">",
"<input type=\"number\" tabindex=\"-1\">",
"<input type=\"number\" tabindex=\"0\">",
"<input type=\"number\" tabindex=\"1\">",
"<input type=\"number\" contenteditable=\"true\">",
"<object tabindex=\"-1\"></object>",
"<object tabindex=\"0\"></object>",
"<object tabindex=\"1\"></object>",
@ -371,6 +377,12 @@ var focusableInContentEditable = [
"<input type=\"text\" tabindex=\"1\">",
"<input type=\"text\" contenteditable=\"true\">",
"<input type=\"number\">",
"<input type=\"number\" tabindex=\"-1\">",
"<input type=\"number\" tabindex=\"0\">",
"<input type=\"number\" tabindex=\"1\">",
"<input type=\"number\" contenteditable=\"true\">",
"<object tabindex=\"-1\"></object>",
"<object tabindex=\"0\"></object>",
"<object tabindex=\"1\"></object>",

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

@ -199,14 +199,18 @@ MediaDecoder::DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder,
mHaveSentFinishAudio(false),
mHaveSentFinishVideo(false),
mStream(aStream),
mHaveBlockedForPlayState(false)
mHaveBlockedForPlayState(false),
mHaveBlockedForStateMachineNotPlaying(false)
{
mStream->AddMainThreadListener(this);
mListener = new DecodedStreamGraphListener(mStream);
mStream->AddListener(mListener);
}
MediaDecoder::DecodedStreamData::~DecodedStreamData()
{
mStream->RemoveMainThreadListener(this);
mListener->Forget();
mStream->Destroy();
}
@ -214,6 +218,27 @@ void
MediaDecoder::DecodedStreamData::NotifyMainThreadStateChanged()
{
mDecoder->NotifyDecodedStreamMainThreadStateChanged();
if (mStream->IsFinished()) {
mListener->SetFinishedOnMainThread(true);
}
}
MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream)
: mMutex("MediaDecoder::DecodedStreamData::mMutex"),
mStream(aStream),
mLastOutputTime(aStream->GetCurrentTime()),
mStreamFinishedOnMainThread(false)
{
}
void
MediaDecoder::DecodedStreamGraphListener::NotifyOutput(MediaStreamGraph* aGraph,
GraphTime aCurrentTime)
{
MutexAutoLock lock(mMutex);
if (mStream) {
mLastOutputTime = mStream->GraphTimeToStreamTime(aCurrentTime);
}
}
void MediaDecoder::DestroyDecodedStream()
@ -244,6 +269,32 @@ void MediaDecoder::DestroyDecodedStream()
mDecodedStream = nullptr;
}
void MediaDecoder::UpdateStreamBlockingForStateMachinePlaying()
{
GetReentrantMonitor().AssertCurrentThreadIn();
if (!mDecodedStream) {
return;
}
if (mDecoderStateMachine) {
mDecoderStateMachine->SetSyncPointForMediaStream();
}
bool blockForStateMachineNotPlaying =
mDecoderStateMachine && !mDecoderStateMachine->IsPlaying() &&
mDecoderStateMachine->GetState() != MediaDecoderStateMachine::DECODER_STATE_COMPLETED;
if (blockForStateMachineNotPlaying != mDecodedStream->mHaveBlockedForStateMachineNotPlaying) {
mDecodedStream->mHaveBlockedForStateMachineNotPlaying = blockForStateMachineNotPlaying;
int32_t delta = blockForStateMachineNotPlaying ? 1 : -1;
if (NS_IsMainThread()) {
mDecodedStream->mStream->ChangeExplicitBlockerCount(delta);
} else {
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethodWithArg<int32_t>(mDecodedStream->mStream.get(),
&MediaStream::ChangeExplicitBlockerCount, delta);
NS_DispatchToMainThread(runnable);
}
}
}
void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs)
{
MOZ_ASSERT(NS_IsMainThread());
@ -269,6 +320,7 @@ void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs)
}
ConnectDecodedStreamToOutputStream(&os);
}
UpdateStreamBlockingForStateMachinePlaying();
mDecodedStream->mHaveBlockedForPlayState = mPlayState != PLAY_STATE_PLAYING;
if (mDecodedStream->mHaveBlockedForPlayState) {

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

@ -229,6 +229,8 @@ class MediaDecoder : public nsIObserver,
public AbstractMediaDecoder
{
public:
class DecodedStreamGraphListener;
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
@ -353,6 +355,11 @@ public:
int64_t aInitialTime, SourceMediaStream* aStream);
~DecodedStreamData();
virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
StreamTime GetLastOutputTime() { return mListener->GetLastOutputTime(); }
bool IsFinished() { return mListener->IsFinishedOnMainThread(); }
// The following group of fields are protected by the decoder's monitor
// and can be read or written on any thread.
int64_t mLastAudioPacketTime; // microseconds
@ -381,12 +388,51 @@ public:
// The decoder is responsible for calling Destroy() on this stream.
// Can be read from any thread.
const nsRefPtr<SourceMediaStream> mStream;
// Can be read from any thread.
nsRefPtr<DecodedStreamGraphListener> mListener;
// True when we've explicitly blocked this stream because we're
// not in PLAY_STATE_PLAYING. Used on the main thread only.
bool mHaveBlockedForPlayState;
virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
// We also have an explicit blocker on the stream when
// mDecoderStateMachine is non-null and MediaDecoderStateMachine is false.
bool mHaveBlockedForStateMachineNotPlaying;
};
class DecodedStreamGraphListener : public MediaStreamListener {
public:
DecodedStreamGraphListener(MediaStream* aStream);
virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) MOZ_OVERRIDE;
StreamTime GetLastOutputTime()
{
MutexAutoLock lock(mMutex);
return mLastOutputTime;
}
void Forget()
{
MutexAutoLock lock(mMutex);
mStream = nullptr;
}
void SetFinishedOnMainThread(bool aFinished)
{
MutexAutoLock lock(mMutex);
mStreamFinishedOnMainThread = aFinished;
}
bool IsFinishedOnMainThread()
{
MutexAutoLock lock(mMutex);
return mStreamFinishedOnMainThread;
}
private:
Mutex mMutex;
// Protected by mMutex
nsRefPtr<MediaStream> mStream;
// Protected by mMutex
StreamTime mLastOutputTime;
// Protected by mMutex
bool mStreamFinishedOnMainThread;
};
struct OutputStreamData {
void Init(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
{
@ -411,8 +457,14 @@ public:
* Recreates mDecodedStream. Call this to create mDecodedStream at first,
* and when seeking, to ensure a new stream is set up with fresh buffers.
* aStartTimeUSecs is relative to the state machine's mStartTime.
* Decoder monitor must be held.
*/
void RecreateDecodedStream(int64_t aStartTimeUSecs);
/**
* Call this when mDecoderStateMachine or mDecoderStateMachine->IsPlaying() changes.
* Decoder monitor must be held.
*/
void UpdateStreamBlockingForStateMachinePlaying();
/**
* Called when the state of mDecodedStream as visible on the main thread
* has changed. In particular we want to know when the stream has finished

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

@ -371,6 +371,8 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
bool aRealTime) :
mDecoder(aDecoder),
mState(DECODER_STATE_DECODING_METADATA),
mSyncPointInMediaStream(-1),
mSyncPointInDecodedStream(-1),
mResetPlayStartTime(false),
mPlayDuration(0),
mStartTime(-1),
@ -627,10 +629,6 @@ void MediaDecoderStateMachine::SendStreamData()
if (mState == DECODER_STATE_DECODING_METADATA)
return;
if (!mDecoder->IsSameOriginMedia()) {
return;
}
// If there's still an audio thread alive, then we can't send any stream
// data yet since both SendStreamData and the audio thread want to be in
// charge of popping the audio queue. We're waiting for the audio thread
@ -639,97 +637,99 @@ void MediaDecoderStateMachine::SendStreamData()
return;
int64_t minLastAudioPacketTime = INT64_MAX;
SourceMediaStream* mediaStream = stream->mStream;
StreamTime endPosition = 0;
if (!stream->mStreamInitialized) {
if (mInfo.HasAudio()) {
AudioSegment* audio = new AudioSegment();
mediaStream->AddTrack(TRACK_AUDIO, mInfo.mAudio.mRate, 0, audio);
}
if (mInfo.HasVideo()) {
VideoSegment* video = new VideoSegment();
mediaStream->AddTrack(TRACK_VIDEO, RATE_VIDEO, 0, video);
}
stream->mStreamInitialized = true;
}
if (mInfo.HasAudio()) {
nsAutoTArray<AudioData*,10> audio;
// It's OK to hold references to the AudioData because while audio
// is captured, only the decoder thread pops from the queue (see below).
mReader->AudioQueue().GetElementsAfter(stream->mLastAudioPacketTime, &audio);
AudioSegment output;
for (uint32_t i = 0; i < audio.Length(); ++i) {
SendStreamAudio(audio[i], stream, &output);
}
if (output.GetDuration() > 0) {
mediaStream->AppendToTrack(TRACK_AUDIO, &output);
}
if (mReader->AudioQueue().IsFinished() && !stream->mHaveSentFinishAudio) {
mediaStream->EndTrack(TRACK_AUDIO);
stream->mHaveSentFinishAudio = true;
}
minLastAudioPacketTime = std::min(minLastAudioPacketTime, stream->mLastAudioPacketTime);
endPosition = std::max(endPosition,
TicksToTimeRoundDown(mInfo.mAudio.mRate, stream->mAudioFramesWritten));
}
if (mInfo.HasVideo()) {
nsAutoTArray<VideoData*,10> video;
// It's OK to hold references to the VideoData only the decoder thread
// pops from the queue.
mReader->VideoQueue().GetElementsAfter(stream->mNextVideoTime + mStartTime, &video);
VideoSegment output;
for (uint32_t i = 0; i < video.Length(); ++i) {
VideoData* v = video[i];
if (stream->mNextVideoTime + mStartTime < v->mTime) {
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lld ms",
mDecoder.get(), mediaStream,
v->mTime - (stream->mNextVideoTime + mStartTime)));
// Write last video frame to catch up. mLastVideoImage can be null here
// which is fine, it just means there's no video.
WriteVideoToMediaStream(stream->mLastVideoImage,
v->mTime - (stream->mNextVideoTime + mStartTime), stream->mLastVideoImageDisplaySize,
&output);
stream->mNextVideoTime = v->mTime - mStartTime;
}
if (stream->mNextVideoTime + mStartTime < v->GetEndTime()) {
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream %p for %lld ms",
mDecoder.get(), v->mTime, mediaStream,
v->GetEndTime() - (stream->mNextVideoTime + mStartTime)));
WriteVideoToMediaStream(v->mImage,
v->GetEndTime() - (stream->mNextVideoTime + mStartTime), v->mDisplay,
&output);
stream->mNextVideoTime = v->GetEndTime() - mStartTime;
stream->mLastVideoImage = v->mImage;
stream->mLastVideoImageDisplaySize = v->mDisplay;
} else {
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream",
mDecoder.get(), v->mTime));
}
}
if (output.GetDuration() > 0) {
mediaStream->AppendToTrack(TRACK_VIDEO, &output);
}
if (mReader->VideoQueue().IsFinished() && !stream->mHaveSentFinishVideo) {
mediaStream->EndTrack(TRACK_VIDEO);
stream->mHaveSentFinishVideo = true;
}
endPosition = std::max(endPosition,
TicksToTimeRoundDown(RATE_VIDEO, stream->mNextVideoTime - stream->mInitialTime));
}
if (!stream->mHaveSentFinish) {
stream->mStream->AdvanceKnownTracksTime(endPosition);
}
bool finished =
(!mInfo.HasAudio() || mReader->AudioQueue().IsFinished()) &&
(!mInfo.HasVideo() || mReader->VideoQueue().IsFinished());
if (finished && !stream->mHaveSentFinish) {
stream->mHaveSentFinish = true;
stream->mStream->Finish();
if (mDecoder->IsSameOriginMedia()) {
SourceMediaStream* mediaStream = stream->mStream;
StreamTime endPosition = 0;
if (!stream->mStreamInitialized) {
if (mInfo.HasAudio()) {
AudioSegment* audio = new AudioSegment();
mediaStream->AddTrack(TRACK_AUDIO, mInfo.mAudio.mRate, 0, audio);
}
if (mInfo.HasVideo()) {
VideoSegment* video = new VideoSegment();
mediaStream->AddTrack(TRACK_VIDEO, RATE_VIDEO, 0, video);
}
stream->mStreamInitialized = true;
}
if (mInfo.HasAudio()) {
nsAutoTArray<AudioData*,10> audio;
// It's OK to hold references to the AudioData because while audio
// is captured, only the decoder thread pops from the queue (see below).
mReader->AudioQueue().GetElementsAfter(stream->mLastAudioPacketTime, &audio);
AudioSegment output;
for (uint32_t i = 0; i < audio.Length(); ++i) {
SendStreamAudio(audio[i], stream, &output);
}
if (output.GetDuration() > 0) {
mediaStream->AppendToTrack(TRACK_AUDIO, &output);
}
if (mReader->AudioQueue().IsFinished() && !stream->mHaveSentFinishAudio) {
mediaStream->EndTrack(TRACK_AUDIO);
stream->mHaveSentFinishAudio = true;
}
minLastAudioPacketTime = std::min(minLastAudioPacketTime, stream->mLastAudioPacketTime);
endPosition = std::max(endPosition,
TicksToTimeRoundDown(mInfo.mAudio.mRate, stream->mAudioFramesWritten));
}
if (mInfo.HasVideo()) {
nsAutoTArray<VideoData*,10> video;
// It's OK to hold references to the VideoData only the decoder thread
// pops from the queue.
mReader->VideoQueue().GetElementsAfter(stream->mNextVideoTime + mStartTime, &video);
VideoSegment output;
for (uint32_t i = 0; i < video.Length(); ++i) {
VideoData* v = video[i];
if (stream->mNextVideoTime + mStartTime < v->mTime) {
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lld ms",
mDecoder.get(), mediaStream,
v->mTime - (stream->mNextVideoTime + mStartTime)));
// Write last video frame to catch up. mLastVideoImage can be null here
// which is fine, it just means there's no video.
WriteVideoToMediaStream(stream->mLastVideoImage,
v->mTime - (stream->mNextVideoTime + mStartTime), stream->mLastVideoImageDisplaySize,
&output);
stream->mNextVideoTime = v->mTime - mStartTime;
}
if (stream->mNextVideoTime + mStartTime < v->GetEndTime()) {
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream %p for %lld ms",
mDecoder.get(), v->mTime, mediaStream,
v->GetEndTime() - (stream->mNextVideoTime + mStartTime)));
WriteVideoToMediaStream(v->mImage,
v->GetEndTime() - (stream->mNextVideoTime + mStartTime), v->mDisplay,
&output);
stream->mNextVideoTime = v->GetEndTime() - mStartTime;
stream->mLastVideoImage = v->mImage;
stream->mLastVideoImageDisplaySize = v->mDisplay;
} else {
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream",
mDecoder.get(), v->mTime));
}
}
if (output.GetDuration() > 0) {
mediaStream->AppendToTrack(TRACK_VIDEO, &output);
}
if (mReader->VideoQueue().IsFinished() && !stream->mHaveSentFinishVideo) {
mediaStream->EndTrack(TRACK_VIDEO);
stream->mHaveSentFinishVideo = true;
}
endPosition = std::max(endPosition,
TicksToTimeRoundDown(RATE_VIDEO, stream->mNextVideoTime - stream->mInitialTime));
}
if (!stream->mHaveSentFinish) {
stream->mStream->AdvanceKnownTracksTime(endPosition);
}
if (finished && !stream->mHaveSentFinish) {
stream->mHaveSentFinish = true;
stream->mStream->Finish();
}
}
if (mAudioCaptured) {
@ -748,6 +748,7 @@ void MediaDecoderStateMachine::SendStreamData()
mReader->AudioQueue().PushFront(a.forget());
break;
}
mAudioEndTime = std::max(mAudioEndTime, a->GetEndTime());
}
if (finished) {
@ -1317,6 +1318,20 @@ void MediaDecoderStateMachine::StopPlayback()
// so it can pause audio playback.
mDecoder->GetReentrantMonitor().NotifyAll();
NS_ASSERTION(!IsPlaying(), "Should report not playing at end of StopPlayback()");
mDecoder->UpdateStreamBlockingForStateMachinePlaying();
}
void MediaDecoderStateMachine::SetSyncPointForMediaStream()
{
AssertCurrentThreadInMonitor();
DecodedStreamData* stream = mDecoder->GetDecodedStream();
if (!stream) {
return;
}
mSyncPointInMediaStream = stream->GetLastOutputTime();
mSyncPointInDecodedStream = mStartTime + mPlayDuration;
}
void MediaDecoderStateMachine::StartPlayback()
@ -1334,6 +1349,7 @@ void MediaDecoderStateMachine::StartPlayback()
NS_WARNING("Failed to create audio thread");
}
mDecoder->GetReentrantMonitor().NotifyAll();
mDecoder->UpdateStreamBlockingForStateMachinePlaying();
}
void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime)
@ -2328,7 +2344,8 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
// end of the media, and so that we update the readyState.
if (mState == DECODER_STATE_COMPLETED &&
(mReader->VideoQueue().GetSize() > 0 ||
(HasAudio() && !mAudioCompleted)))
(HasAudio() && !mAudioCompleted) ||
(mDecoder->GetDecodedStream() && !mDecoder->GetDecodedStream()->IsFinished())))
{
AdvanceFrame();
NS_ASSERTION(mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING ||
@ -2431,10 +2448,16 @@ int64_t MediaDecoderStateMachine::GetClock() {
// Determine the clock time. If we've got audio, and we've not reached
// the end of the audio, use the audio clock. However if we've finished
// audio, or don't have audio, use the system clock.
// audio, or don't have audio, use the system clock. If our output is being
// fed to a MediaStream, use that stream as the source of the clock.
int64_t clock_time = -1;
DecodedStreamData* stream = mDecoder->GetDecodedStream();
if (!IsPlaying()) {
clock_time = mPlayDuration + mStartTime;
} else if (stream) {
NS_ASSERTION(mSyncPointInDecodedStream >= 0, "Should have set up sync point");
StreamTime streamDelta = stream->GetLastOutputTime() - mSyncPointInMediaStream;
clock_time = mSyncPointInDecodedStream + MediaTimeToMicroseconds(streamDelta);
} else {
int64_t audio_time = GetAudioClock();
if (HasAudio() && !mAudioCompleted && audio_time != -1) {

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

@ -328,9 +328,16 @@ public:
mDecoder = nullptr;
}
// Called when a "MozAudioAvailable" event listener is added to the media
// element. Called on the main thread.
void NotifyAudioAvailableListener();
// If we're playing into a MediaStream, record the current point in the
// MediaStream and the current point in our media resource so later we can
// convert MediaStream playback positions to media resource positions. Best to
// call this while we're not playing (while the MediaStream is blocked). Can
// be called on any thread with the decoder monitor held.
void SetSyncPointForMediaStream();
// Called when a "MozAudioAvailable" event listener is added to the media
// element. Called on the main thread.
void NotifyAudioAvailableListener();
// Copy queued audio/video data in the reader to any output MediaStreams that
// need it.
@ -345,6 +352,10 @@ public:
void QueueMetadata(int64_t aPublishTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags);
// Returns true if we're currently playing. The decoder monitor must
// be held.
bool IsPlaying();
protected:
virtual uint32_t GetAmpleVideoFrames() { return mAmpleVideoFrames; }
@ -519,10 +530,6 @@ private:
void StartDecodeMetadata();
// Returns true if we're currently playing. The decoder monitor must
// be held.
bool IsPlaying();
// Returns the "media time". This is the absolute time which the media
// playback has reached. i.e. this returns values in the range
// [mStartTime, mEndTime], and mStartTime will not be 0 if the media does
@ -619,6 +626,12 @@ private:
// Accessed only via the state machine thread.
TimeStamp mPlayStartTime;
// When we start writing decoded data to a new DecodedDataStream, or we
// restart writing due to PlaybackStarted(), we record where we are in the
// MediaStream and what that corresponds to in the media.
StreamTime mSyncPointInMediaStream;
int64_t mSyncPointInDecodedStream; // microseconds
// When the playbackRate changes, and there is no audio clock, it is necessary
// to reset the mPlayStartTime. This is done next time the clock is queried,
// when this member is true. Access protected by decoder monitor.
@ -743,10 +756,12 @@ private:
bool mPositionChangeQueued;
// True if the audio playback thread has finished. It is finished
// when either all the audio frames in the Vorbis bitstream have completed
// playing, or we've moved into shutdown state, and the threads are to be
// when either all the audio frames have completed playing, or we've moved
// into shutdown state, and the threads are to be
// destroyed. Written by the audio playback thread and read and written by
// the state machine thread. Synchronised via decoder monitor.
// When data is being sent to a MediaStream, this is true when all data has
// been written to the MediaStream.
bool mAudioCompleted;
// True if mDuration has a value obtained from an HTTP header, or from

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

@ -40,6 +40,11 @@ inline double MediaTimeToSeconds(MediaTime aTime)
return aTime*(1.0/(1 << MEDIA_TIME_FRAC_BITS));
}
inline int64_t MediaTimeToMicroseconds(MediaTime aTime)
{
return aTime*(1000000.0/(1 << MEDIA_TIME_FRAC_BITS));
}
/**
* A number of ticks at a rate determined by some underlying track (e.g.
* audio sample rate). We want to make sure that multiplying TrackTicks by

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

@ -359,6 +359,8 @@ MediaStreamGraphImpl::UpdateCurrentTime()
}
nsTArray<MediaStream*> streamsReadyToFinish;
nsAutoTArray<bool,800> streamHasOutput;
streamHasOutput.SetLength(mStreams.Length());
for (uint32_t i = 0; i < mStreams.Length(); ++i) {
MediaStream* stream = mStreams[i];
@ -387,12 +389,7 @@ MediaStreamGraphImpl::UpdateCurrentTime()
// AdvanceTimeVaryingValuesToCurrentTime can rely on the value of mBlocked.
stream->mBlocked.AdvanceCurrentTime(nextCurrentTime);
if (blockedTime < nextCurrentTime - prevCurrentTime) {
for (uint32_t i = 0; i < stream->mListeners.Length(); ++i) {
MediaStreamListener* l = stream->mListeners[i];
l->NotifyOutput(this);
}
}
streamHasOutput[i] = blockedTime < nextCurrentTime - prevCurrentTime;
if (stream->mFinished && !stream->mNotifiedFinished) {
streamsReadyToFinish.AppendElement(stream);
@ -404,7 +401,18 @@ MediaStreamGraphImpl::UpdateCurrentTime()
mCurrentTime = nextCurrentTime;
// Do this after setting mCurrentTime so that StreamTimeToGraphTime works properly.
// Do these after setting mCurrentTime so that StreamTimeToGraphTime works properly.
for (uint32_t i = 0; i < streamHasOutput.Length(); ++i) {
if (!streamHasOutput[i]) {
continue;
}
MediaStream* stream = mStreams[i];
for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
MediaStreamListener* l = stream->mListeners[j];
l->NotifyOutput(this, mCurrentTime);
}
}
for (uint32_t i = 0; i < streamsReadyToFinish.Length(); ++i) {
MediaStream* stream = streamsReadyToFinish[i];
if (StreamTimeToGraphTime(stream, stream->GetBufferEnd()) <= mCurrentTime) {

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

@ -138,9 +138,11 @@ public:
virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) {}
/**
* Notify that the stream output is advancing.
* Notify that the stream output is advancing. aCurrentTime is the graph's
* current time. MediaStream::GraphTimeToStreamTime can be used to get the
* stream time.
*/
virtual void NotifyOutput(MediaStreamGraph* aGraph) {}
virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) {}
/**
* Notify that the stream finished.

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

@ -257,6 +257,7 @@ support-files =
[test_streams_element_capture.html]
[test_streams_element_capture_reset.html]
[test_streams_element_capture_createObjectURL.html]
[test_streams_element_capture_playback.html]
[test_streams_gc.html]
[test_streams_tracks.html]
[test_texttrack.html]

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

@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test that capturing a stream doesn't stop the underlying element from firing events</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
</head>
<body>
<audio id="a"></audio>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var a = document.getElementById('a');
var validTimeUpdate = false;
function startTest() {
a.src = "big.wav";
var context = new AudioContext();
var node = context.createMediaElementSource(a);
node.connect(context.destination);
a.addEventListener("timeupdate", function() {
if (a.currentTime > 0.0 && a.currentTime < 5.0 && !validTimeUpdate) {
validTimeUpdate = true;
ok(true, "Received reasonable currentTime in a timeupdate");
SimpleTest.finish();
}
});
a.addEventListener("ended", function() {
if (!validTimeUpdate) {
ok(false, "Received reasonable currentTime in a timeupdate");
SimpleTest.finish();
}
});
a.play();
}
if (a.canPlayType("audio/wave")) {
startTest();
} else {
todo(false, "No playable audio");
}
</script>
</pre>
</body>
</html>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -0,0 +1,228 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_bluetooth_bluetoothoppmanager_h__
#define mozilla_dom_bluetooth_bluetoothoppmanager_h__
#include "BluetoothCommon.h"
#include "BluetoothProfileManagerBase.h"
#include "BluetoothSocketObserver.h"
#include "DeviceStorage.h"
#include "mozilla/dom/ipc/Blob.h"
#include "mozilla/ipc/UnixSocket.h"
#include "nsCOMArray.h"
class nsIOutputStream;
class nsIInputStream;
class nsIVolumeMountLock;
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothSocket;
class ObexHeaderSet;
class SendFileBatch;
class BluetoothOppManager : public BluetoothSocketObserver
, public BluetoothProfileManagerBase
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
BT_DECL_PROFILE_MGR_BASE
virtual void GetName(nsACString& aName)
{
aName.AssignLiteral("OPP");
}
/*
* Channel of reserved services are fixed values, please check
* function add_reserved_service_records() in
* external/bluetooth/bluez/src/adapter.c for more information.
*/
static const int DEFAULT_OPP_CHANNEL = 10;
static const int MAX_PACKET_LENGTH = 0xFFFE;
~BluetoothOppManager();
static BluetoothOppManager* Get();
void ClientDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
void ServerDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
bool Listen();
bool SendFile(const nsAString& aDeviceAddress, BlobParent* aActor);
bool StopSendingFile();
bool ConfirmReceivingFile(bool aConfirm);
void SendConnectRequest();
void SendPutHeaderRequest(const nsAString& aFileName, int aFileSize);
void SendPutRequest(uint8_t* aFileBody, int aFileBodyLength);
void SendPutFinalRequest();
void SendDisconnectRequest();
void ExtractPacketHeaders(const ObexHeaderSet& aHeader);
bool ExtractBlobHeaders();
void CheckPutFinal(uint32_t aNumRead);
// The following functions are inherited from BluetoothSocketObserver
void ReceiveSocketData(
BluetoothSocket* aSocket,
nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
virtual void OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
private:
BluetoothOppManager();
bool Init();
void HandleShutdown();
void StartFileTransfer();
void StartSendingNextFile();
void FileTransferComplete();
void UpdateProgress();
void ReceivingFileConfirmation();
bool CreateFile();
bool WriteToFile(const uint8_t* aData, int aDataLength);
void DeleteReceivedFile();
void ReplyToConnect();
void ReplyToDisconnectOrAbort();
void ReplyToPut(bool aFinal, bool aContinue);
void ReplyError(uint8_t aError);
void AfterOppConnected();
void AfterFirstPut();
void AfterOppDisconnected();
void ValidateFileName();
bool IsReservedChar(PRUnichar c);
void ClearQueue();
void RetrieveSentFileName();
void NotifyAboutFileChange();
bool AcquireSdcardMountLock();
void SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
void AppendBlobToSend(const nsAString& aDeviceAddress, BlobParent* aActor);
void DiscardBlobsToSend();
bool ProcessNextBatch();
void ConnectInternal(const nsAString& aDeviceAddress);
/**
* Usually we won't get a full PUT packet in one operation, which means that
* a packet may be devided into several parts and BluetoothOppManager should
* be in charge of assembling.
*
* @return true if a packet has been fully received.
* false if the received length exceeds/not reaches the expected
* length.
*/
bool ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage);
/**
* OBEX session status.
* Set when OBEX session is established.
*/
bool mConnected;
nsString mConnectedDeviceAddress;
/**
* Remote information
*/
uint8_t mRemoteObexVersion;
uint8_t mRemoteConnectionFlags;
int mRemoteMaxPacketLength;
/**
* For sending files, we decide our next action based on current command and
* previous one.
* For receiving files, we don't need previous command and it is set to 0
* as a default value.
*/
int mLastCommand;
int mPacketLength;
int mPacketReceivedLength;
int mBodySegmentLength;
int mUpdateProgressCounter;
/**
* When it is true and the target service on target device couldn't be found,
* refreshing SDP records is necessary.
*/
bool mNeedsUpdatingSdpRecords;
/**
* Set when StopSendingFile() is called.
*/
bool mAbortFlag;
/**
* Set when receiving the first PUT packet of a new file
*/
bool mNewFileFlag;
/**
* Set when receiving a PutFinal packet
*/
bool mPutFinalFlag;
/**
* Set when FileTransferComplete() is called
*/
bool mSendTransferCompleteFlag;
/**
* Set when a transfer is successfully completed.
*/
bool mSuccessFlag;
/**
* True: Receive file (Server)
* False: Send file (Client)
*/
bool mIsServer;
/**
* Set when receiving the first PUT packet and wait for
* ConfirmReceivingFile() to be called.
*/
bool mWaitingForConfirmationFlag;
nsString mFileName;
nsString mContentType;
uint32_t mFileLength;
uint32_t mSentFileLength;
bool mWaitingToSendPutFinal;
nsAutoArrayPtr<uint8_t> mBodySegment;
nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
int mCurrentBlobIndex;
nsCOMPtr<nsIDOMBlob> mBlob;
nsTArray<SendFileBatch> mBatches;
/**
* A seperate member thread is required because our read calls can block
* execution, which is not allowed to happen on the IOThread.
*/
nsCOMPtr<nsIThread> mReadFileThread;
nsCOMPtr<nsIOutputStream> mOutputStream;
nsCOMPtr<nsIInputStream> mInputStream;
nsCOMPtr<nsIVolumeMountLock> mMountLock;
nsRefPtr<DeviceStorageFile> mDsFile;
// If a connection has been established, mSocket will be the socket
// communicating with the remote socket. We maintain the invariant that if
// mSocket is non-null, mRfcommSocket and mL2capSocket must be null (and vice
// versa).
nsRefPtr<BluetoothSocket> mSocket;
// Server sockets. Once an inbound connection is established, it will hand
// over the ownership to mSocket, and get a new server socket while Listen()
// is called.
nsRefPtr<BluetoothSocket> mRfcommSocket;
nsRefPtr<BluetoothSocket> mL2capSocket;
};
END_BLUETOOTH_NAMESPACE
#endif

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

@ -0,0 +1,98 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BluetoothSocket.h"
#include "BluetoothSocketObserver.h"
#include "BluetoothUnixSocketConnector.h"
#include "nsThreadUtils.h"
using namespace mozilla::ipc;
USING_BLUETOOTH_NAMESPACE
BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
BluetoothSocketType aType,
bool aAuth,
bool aEncrypt)
: mObserver(aObserver)
, mType(aType)
, mAuth(aAuth)
, mEncrypt(aEncrypt)
{
MOZ_ASSERT(aObserver);
}
bool
BluetoothSocket::Connect(const nsACString& aDeviceAddress, int aChannel)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!aDeviceAddress.IsEmpty());
nsAutoPtr<BluetoothUnixSocketConnector> c(
new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt));
if (!ConnectSocket(c.forget(), aDeviceAddress.BeginReading())) {
nsAutoString addr;
GetAddress(addr);
BT_LOGD("%s failed. Current connected device address: %s",
__FUNCTION__, NS_ConvertUTF16toUTF8(addr).get());
return false;
}
return true;
}
bool
BluetoothSocket::Listen(int aChannel)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoPtr<BluetoothUnixSocketConnector> c(
new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt));
if (!ListenSocket(c.forget())) {
nsAutoString addr;
GetAddress(addr);
BT_LOGD("%s failed. Current connected device address: %s",
__FUNCTION__, NS_ConvertUTF16toUTF8(addr).get());
return false;
}
return true;
}
void
BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mObserver);
mObserver->ReceiveSocketData(this, aMessage);
}
void
BluetoothSocket::OnConnectSuccess()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mObserver);
mObserver->OnSocketConnectSuccess(this);
}
void
BluetoothSocket::OnConnectError()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mObserver);
mObserver->OnSocketConnectError(this);
}
void
BluetoothSocket::OnDisconnect()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mObserver);
mObserver->OnSocketDisconnect(this);
}

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

@ -0,0 +1,285 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
* Copyright 2009, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NOTE: Due to being based on the dbus compatibility layer for
* android's bluetooth implementation, this file is licensed under the
* apache license instead of MPL.
*
*/
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#ifdef MOZ_B2G_BT_BLUEZ
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sco.h>
#endif
#include "BluetoothUnixSocketConnector.h"
#include "nsThreadUtils.h"
using namespace mozilla::ipc;
USING_BLUETOOTH_NAMESPACE
static const int RFCOMM_SO_SNDBUF = 70 * 1024; // 70 KB send buffer
static const int L2CAP_SO_SNDBUF = 400 * 1024; // 400 KB send buffer
static const int L2CAP_SO_RCVBUF = 400 * 1024; // 400 KB receive buffer
static const int L2CAP_MAX_MTU = 65000;
#ifdef MOZ_B2G_BT_BLUEZ
static
int get_bdaddr(const char *str, bdaddr_t *ba)
{
char *d = ((char*)ba) + 5, *endp;
for (int i = 0; i < 6; i++) {
*d-- = strtol(str, &endp, 16);
MOZ_ASSERT(!(*endp != ':' && i != 5));
str = endp + 1;
}
return 0;
}
static
void get_bdaddr_as_string(const bdaddr_t *ba, char *str) {
const uint8_t *b = (const uint8_t *)ba;
sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
b[5], b[4], b[3], b[2], b[1], b[0]);
}
#endif
BluetoothUnixSocketConnector::BluetoothUnixSocketConnector(
BluetoothSocketType aType,
int aChannel,
bool aAuth,
bool aEncrypt) : mType(aType)
, mChannel(aChannel)
, mAuth(aAuth)
, mEncrypt(aEncrypt)
{
}
bool
BluetoothUnixSocketConnector::SetUp(int aFd)
{
#ifdef MOZ_B2G_BT_BLUEZ
int lm = 0;
int sndbuf, rcvbuf;
/* kernel does not yet support LM for SCO */
switch (mType) {
case BluetoothSocketType::RFCOMM:
lm |= mAuth ? RFCOMM_LM_AUTH : 0;
lm |= mEncrypt ? RFCOMM_LM_ENCRYPT : 0;
break;
case BluetoothSocketType::L2CAP:
case BluetoothSocketType::EL2CAP:
lm |= mAuth ? L2CAP_LM_AUTH : 0;
lm |= mEncrypt ? L2CAP_LM_ENCRYPT : 0;
break;
case BluetoothSocketType::SCO:
break;
default:
MOZ_CRASH("Unknown socket type!");
}
if (lm) {
if (mType == BluetoothSocketType::RFCOMM) {
if (setsockopt(aFd, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm))) {
BT_WARNING("setsockopt(RFCOMM_LM) failed, throwing");
return false;
}
} else if (mType == BluetoothSocketType::L2CAP ||
mType == BluetoothSocketType::EL2CAP) {
if (setsockopt(aFd, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm))) {
BT_WARNING("setsockopt(L2CAP_LM) failed, throwing");
return false;
}
}
}
if (mType == BluetoothSocketType::RFCOMM) {
sndbuf = RFCOMM_SO_SNDBUF;
if (setsockopt(aFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) {
BT_WARNING("setsockopt(SO_SNDBUF) failed, throwing");
return false;
}
}
/* Setting L2CAP socket options */
if (mType == BluetoothSocketType::L2CAP ||
mType == BluetoothSocketType::EL2CAP) {
struct l2cap_options opts;
socklen_t optlen = sizeof(opts);
int err;
err = getsockopt(aFd, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen);
if (!err) {
/* setting MTU for [E]L2CAP */
opts.omtu = opts.imtu = L2CAP_MAX_MTU;
/* Enable ERTM for [E]L2CAP */
if (mType == BluetoothSocketType::EL2CAP) {
opts.flush_to = 0xffff; /* infinite */
opts.mode = L2CAP_MODE_ERTM;
opts.fcs = 1;
opts.txwin_size = 64;
opts.max_tx = 10;
}
err = setsockopt(aFd, SOL_L2CAP, L2CAP_OPTIONS, &opts, optlen);
}
/* Set larger SNDBUF & RCVBUF for EL2CAP connections */
if (mType == BluetoothSocketType::EL2CAP) {
sndbuf = L2CAP_SO_SNDBUF;
if (setsockopt(aFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) {
BT_WARNING("setsockopt(SO_SNDBUF) failed, throwing");
return false;
}
rcvbuf = L2CAP_SO_RCVBUF;
if (setsockopt(aFd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))) {
BT_WARNING("setsockopt(SO_RCVBUF) failed, throwing");
return false;
}
}
}
#endif
return true;
}
bool
BluetoothUnixSocketConnector::SetUpListenSocket(int aFd)
{
// Nothing to do here.
return true;
}
int
BluetoothUnixSocketConnector::Create()
{
MOZ_ASSERT(!NS_IsMainThread());
int fd = -1;
#ifdef MOZ_B2G_BT_BLUEZ
switch (mType) {
case BluetoothSocketType::RFCOMM:
fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
break;
case BluetoothSocketType::SCO:
fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
break;
case BluetoothSocketType::L2CAP:
fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
break;
case BluetoothSocketType::EL2CAP:
fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_L2CAP);
break;
default:
MOZ_CRASH();
}
if (fd < 0) {
BT_WARNING("Could not open bluetooth socket!");
return -1;
}
if (!SetUp(fd)) {
BT_WARNING("Could not set up socket!");
return -1;
}
#endif
return fd;
}
bool
BluetoothUnixSocketConnector::CreateAddr(bool aIsServer,
socklen_t& aAddrSize,
sockaddr_any& aAddr,
const char* aAddress)
{
#ifdef MOZ_B2G_BT_BLUEZ
// Set to BDADDR_ANY, if it's not a server, we'll reset.
bdaddr_t bd_address_obj = {{0, 0, 0, 0, 0, 0}};
if (!aIsServer && aAddress && strlen(aAddress) > 0) {
if (get_bdaddr(aAddress, &bd_address_obj)) {
BT_WARNING("Can't get bluetooth address!");
return false;
}
}
// Initialize
memset(&aAddr, 0, sizeof(aAddr));
switch (mType) {
case BluetoothSocketType::RFCOMM:
struct sockaddr_rc addr_rc;
aAddrSize = sizeof(addr_rc);
aAddr.rc.rc_family = AF_BLUETOOTH;
aAddr.rc.rc_channel = mChannel;
memcpy(&aAddr.rc.rc_bdaddr, &bd_address_obj, sizeof(bd_address_obj));
break;
case BluetoothSocketType::L2CAP:
case BluetoothSocketType::EL2CAP:
struct sockaddr_l2 addr_l2;
aAddrSize = sizeof(addr_l2);
aAddr.l2.l2_family = AF_BLUETOOTH;
aAddr.l2.l2_psm = mChannel;
memcpy(&aAddr.l2.l2_bdaddr, &bd_address_obj, sizeof(bdaddr_t));
break;
case BluetoothSocketType::SCO:
struct sockaddr_sco addr_sco;
aAddrSize = sizeof(addr_sco);
aAddr.sco.sco_family = AF_BLUETOOTH;
memcpy(&aAddr.sco.sco_bdaddr, &bd_address_obj, sizeof(bd_address_obj));
break;
default:
BT_WARNING("Socket type unknown!");
return false;
}
#endif
return true;
}
void
BluetoothUnixSocketConnector::GetSocketAddr(const sockaddr_any& aAddr,
nsAString& aAddrStr)
{
#ifdef MOZ_B2G_BT_BLUEZ
char addr[18];
switch (mType) {
case BluetoothSocketType::RFCOMM:
get_bdaddr_as_string((bdaddr_t*)(&aAddr.rc.rc_bdaddr), addr);
break;
case BluetoothSocketType::SCO:
get_bdaddr_as_string((bdaddr_t*)(&aAddr.sco.sco_bdaddr), addr);
break;
case BluetoothSocketType::L2CAP:
case BluetoothSocketType::EL2CAP:
get_bdaddr_as_string((bdaddr_t*)(&aAddr.l2.l2_bdaddr), addr);
break;
default:
MOZ_CRASH("Socket should be either RFCOMM or SCO!");
}
aAddrStr.AssignASCII(addr);
#endif
}

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

@ -0,0 +1,42 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_bluetooth_BluetoothUnixSocketConnector_h
#define mozilla_dom_bluetooth_BluetoothUnixSocketConnector_h
#include "BluetoothCommon.h"
#include <sys/socket.h>
#include <mozilla/ipc/UnixSocket.h>
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothUnixSocketConnector : public mozilla::ipc::UnixSocketConnector
{
public:
BluetoothUnixSocketConnector(BluetoothSocketType aType, int aChannel,
bool aAuth, bool aEncrypt);
virtual ~BluetoothUnixSocketConnector()
{}
virtual int Create() MOZ_OVERRIDE;
virtual bool CreateAddr(bool aIsServer,
socklen_t& aAddrSize,
mozilla::ipc::sockaddr_any& aAddr,
const char* aAddress) MOZ_OVERRIDE;
virtual bool SetUp(int aFd) MOZ_OVERRIDE;
virtual bool SetUpListenSocket(int aFd) MOZ_OVERRIDE;
virtual void GetSocketAddr(const mozilla::ipc::sockaddr_any& aAddr,
nsAString& aAddrStr) MOZ_OVERRIDE;
private:
BluetoothSocketType mType;
int mChannel;
bool mAuth;
bool mEncrypt;
};
END_BLUETOOTH_NAMESPACE
#endif

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

@ -66,7 +66,8 @@ PEPseudoSelNotPE=Expected pseudo-element but found '%1$S'.
PEPseudoSelDoubleNot=Negation pseudo-class can't be negated '%1$S'.
PEPseudoSelPEInNot=Pseudo-elements can't be negated '%1$S'.
PEPseudoSelNewStyleOnly=This pseudo-element must use the "::" form: '%1$S'.
PEPseudoSelTrailing=Expected end of selector or a user action pseudo-class after pseudo-element but found '%1$S'.
PEPseudoSelEndOrUserActionPC=Expected end of selector or a user action pseudo-class after pseudo-element but found '%1$S'.
PEPseudoSelNoUserActionPC=Expected end of selector after pseudo-element that does not support user action pseudo-classes but found '%1$S'.
PEPseudoSelMultiplePE=Extra pseudo-element '%1$S'.
PEPseudoSelUnknown=Unknown pseudo-class or pseudo-element '%1$S'.
PENegationEOF=selector within negation

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

@ -282,7 +282,7 @@ GetSendMmsMessageRequestFromParams(uint32_t aServiceId,
}
for (uint32_t i = 0; i < params.mAttachments.Value().Length(); i++) {
dom::MmsAttachment& attachment = params.mAttachments.Value()[i];
mozilla::dom::MmsAttachment& attachment = params.mAttachments.Value()[i];
MmsAttachmentData mmsAttachment;
mmsAttachment.id().Assign(attachment.mId);
mmsAttachment.location().Assign(attachment.mLocation);

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

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GLBlitTextureImageHelper.h"
#include "DecomposeIntoNoRepeatTriangles.h"
#include "GLContext.h"
#include "nsRect.h"
#include "gfx2DGlue.h"

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

@ -109,6 +109,9 @@ public:
#include "gfxCrashReporterUtils.h"
#include "ScopedGLHelpers.h"
#include "GLBlitHelper.h"
using namespace mozilla::gfx;
#ifdef MOZ_WIDGET_GONK

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

@ -5,6 +5,8 @@
#include "GLContext.h"
#include "GLContextUtils.h"
#include "mozilla/gfx/2D.h"
#include "gfx2DGlue.h"
using namespace mozilla::gfx;

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

@ -20,7 +20,7 @@ namespace mozilla {
namespace gl {
TemporaryRef<gfx::DataSourceSurface>
ReadBackSurface(GLContext* aContext, GLuint aTexture, bool aYInvert, SurfaceFormat aFormat);
ReadBackSurface(GLContext* aContext, GLuint aTexture, bool aYInvert, gfx::SurfaceFormat aFormat);
} // namespace gl
} // namespace mozilla

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

@ -17,6 +17,7 @@
#ifdef XP_MACOSX
#include "SharedSurfaceIO.h"
#endif
#include "ScopedGLHelpers.h"
using namespace mozilla::gfx;

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

@ -9,6 +9,7 @@
#include "gfxPlatform.h"
#include "gfxUtils.h"
#include "gfx2DGlue.h"
#include "ScopedGLHelpers.h"
namespace mozilla {
namespace gl {

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

@ -3,6 +3,7 @@
* 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 "GLContext.h"
#include "ScopedGLHelpers.h"
namespace mozilla {

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

@ -6,8 +6,11 @@
#ifndef SCOPEDGLHELPERS_H_
#define SCOPEDGLHELPERS_H_
#include "GLContext.h"
namespace mozilla {
namespace gl {
//RAII via CRTP!
template <class Derived>
struct ScopedGLWrapper

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше