зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound.
This commit is contained in:
Коммит
bbe673a3ae
|
@ -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
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче