Merge autoland to mozilla-central a=merge

This commit is contained in:
Coroiu Cristina 2019-10-08 00:48:53 +03:00
Родитель f155d3a218 14b2bea568
Коммит faadf02d25
148 изменённых файлов: 4994 добавлений и 3029 удалений

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

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1570053270833">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1570441214263">
<emItems>
<emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
<prefs/>
@ -3485,6 +3485,10 @@
<prefs/>
<versionRange minVersion="0" maxVersion="*" severity="3"/>
</emItem>
<emItem blockID="19f599bd-2226-49e2-90fd-685fd106fc3d" id="sparalarm@chip.de">
<prefs/>
<versionRange minVersion="0" maxVersion="*" severity="3"/>
</emItem>
</emItems>
<pluginItems>
<pluginItem blockID="p332">

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

@ -14,6 +14,9 @@ origin uitour 1 https://support.mozilla.org
origin uitour 1 about:home
origin uitour 1 about:newtab
# XPInstall
origin install 1 https://addons.mozilla.org
# Remote troubleshooting
origin remote-troubleshooting 1 https://support.mozilla.org

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

@ -2072,7 +2072,7 @@ var gBrowserInit = {
BrowserSearch.delayedStartupInit();
AutoShowBookmarksToolbar.init();
gProtectionsHandler.init();
HomePage.init().catch(Cu.reportError);
HomePage.delayedStartup().catch(Cu.reportError);
let safeMode = document.getElementById("helpSafeMode");
if (Services.appinfo.inSafeMode) {

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

@ -10,8 +10,6 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.jsm",
ExtensionSettingsStore: "resource://gre/modules/ExtensionSettingsStore.jsm",
HomePage: "resource:///modules/HomePage.jsm",
TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
});
// Named this way so they correspond to the extensions
@ -24,8 +22,6 @@ const CONTROLLED_BY_OTHER = "controlled_by_other_extensions";
const NOT_CONTROLLABLE = "not_controllable";
const HOMEPAGE_URL_PREF = "browser.startup.homepage";
const HOMEPAGE_EXTENSION_CONTROLLED =
"browser.startup.homepage_override.extensionControlled";
const getHomePageURL = () => {
return Services.prefs.getStringPref(HOMEPAGE_URL_PREF);
@ -614,49 +610,3 @@ add_task(async function test_overriding_home_page_incognito_external() {
await extension.unload();
await BrowserTestUtils.closeWindow(win);
});
add_task(async function test_overriding_with_ignored_url() {
// Manually poke into the ignore list a value to be ignored.
HomePage._ignoreList.push("ignore=me");
Services.prefs.setBoolPref(HOMEPAGE_EXTENSION_CONTROLLED, false);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
browser_specific_settings: {
gecko: {
id: "ignore_homepage@example.com",
},
},
chrome_settings_overrides: { homepage: "https://example.com/?ignore=me" },
name: "extension",
},
useAddonManager: "temporary",
});
await extension.startup();
ok(HomePage.isDefault, "Should still have the default homepage");
is(
Services.prefs.getBoolPref(
"browser.startup.homepage_override.extensionControlled"
),
false,
"Should not be extension controlled."
);
TelemetryTestUtils.assertEvents(
[
{
object: "ignore",
value: "set_blocked_extension",
extra: { webExtensionId: "ignore_homepage@example.com" },
},
],
{
category: "homepage",
method: "preference",
}
);
await extension.unload();
HomePage._ignoreList.pop();
});

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

@ -0,0 +1,156 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const { AddonTestUtils } = ChromeUtils.import(
"resource://testing-common/AddonTestUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
HomePage: "resource:///modules/HomePage.jsm",
RemoteSettings: "resource://services-settings/remote-settings.js",
sinon: "resource://testing-common/Sinon.jsm",
TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
});
const HOMEPAGE_EXTENSION_CONTROLLED =
"browser.startup.homepage_override.extensionControlled";
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"42"
);
async function setupRemoteSettings() {
const settings = await RemoteSettings("hijack-blocklists");
sinon.stub(settings, "get").returns([
{
id: "homepage-urls",
matches: ["ignore=me"],
_status: "synced",
},
]);
}
add_task(async function setup() {
await AddonTestUtils.promiseStartupManager();
await setupRemoteSettings();
});
add_task(async function test_overriding_with_ignored_url() {
// Manually poke into the ignore list a value to be ignored.
HomePage._ignoreList.push("ignore=me");
Services.prefs.setBoolPref(HOMEPAGE_EXTENSION_CONTROLLED, false);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
browser_specific_settings: {
gecko: {
id: "ignore_homepage@example.com",
},
},
chrome_settings_overrides: { homepage: "https://example.com/?ignore=me" },
name: "extension",
},
useAddonManager: "temporary",
});
await extension.startup();
Assert.ok(HomePage.isDefault, "Should still have the default homepage");
Assert.equal(
Services.prefs.getBoolPref(
"browser.startup.homepage_override.extensionControlled"
),
false,
"Should not be extension controlled."
);
TelemetryTestUtils.assertEvents(
[
{
object: "ignore",
value: "set_blocked_extension",
extra: { webExtensionId: "ignore_homepage@example.com" },
},
],
{
category: "homepage",
method: "preference",
}
);
await extension.unload();
HomePage._ignoreList.pop();
});
add_task(async function test_overriding_cancelled_after_ignore_update() {
const oldHomePageIgnoreList = HomePage._ignoreList;
let extension = ExtensionTestUtils.loadExtension({
manifest: {
browser_specific_settings: {
gecko: {
id: "ignore_homepage1@example.com",
},
},
chrome_settings_overrides: {
homepage: "https://example.com/?ignore1=me",
},
name: "extension",
},
useAddonManager: "temporary",
});
await extension.startup();
Assert.ok(!HomePage.isDefault, "Should have overriden the new homepage");
Assert.equal(
Services.prefs.getBoolPref(
"browser.startup.homepage_override.extensionControlled"
),
true,
"Should be extension controlled."
);
let prefChanged = TestUtils.waitForPrefChange(
"browser.startup.homepage_override.extensionControlled"
);
await HomePage._handleIgnoreListUpdated({
data: {
current: [{ id: "homepage-urls", matches: ["ignore1=me"] }],
},
});
await prefChanged;
await TestUtils.waitForCondition(
() =>
!Services.prefs.getBoolPref(
"browser.startup.homepage_override.extensionControlled",
false
),
"Should not longer be extension controlled"
);
Assert.ok(HomePage.isDefault, "Should have reset the homepage");
TelemetryTestUtils.assertEvents(
[
{
object: "ignore",
value: "saved_reset",
},
],
{
category: "homepage",
method: "preference",
}
);
await extension.unload();
HomePage._ignoreList = oldHomePageIgnoreList;
});

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

@ -10,6 +10,7 @@ dupe-manifest =
[test_ext_browsingData_downloads.js]
[test_ext_browsingData_passwords.js]
[test_ext_browsingData_settings.js]
[test_ext_chrome_settings_overrides_home.js]
[test_ext_chrome_settings_overrides_update.js]
[test_ext_distribution_popup.js]
[test_ext_history.js]

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

@ -283,7 +283,17 @@ var gSyncPane = {
});
},
_chooseWhatToSync(isAlreadySyncing) {
async _chooseWhatToSync(isAlreadySyncing) {
// Assuming another device is syncing and we're not,
// we update the engines selection so the correct
// checkboxes are pre-filed.
if (!isAlreadySyncing) {
try {
await Weave.Service.updateLocalEnginesState();
} catch (err) {
console.error("Error updating the local engines state", err);
}
}
let params = {};
if (isAlreadySyncing) {
// If we are already syncing then we also offer to disconnect.

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

@ -811,6 +811,25 @@ class UrlbarInput {
this.setValueFromResult(result);
}
/**
* Invoked by the view when the first result is received.
* To prevent selection flickering, we apply autofill on input through a
* placeholder, without waiting for results.
* But, if the first result is not an autofill one, the autofill prediction
* was wrong and we should restore the original user typed string.
* @param {UrlbarResult} firstResult The first result received.
*/
maybeClearAutofillPlaceholder(firstResult) {
if (
this._autofillPlaceholder &&
!firstResult.autofill &&
// Avoid clobbering added spaces (for token aliases, for example).
!this.value.endsWith(" ")
) {
this._setValue(this.window.gBrowser.userTypedValue, false);
}
}
/**
* Starts a query based on the current input value.
*
@ -979,7 +998,9 @@ class UrlbarInput {
if (
!this.hasAttribute("breakout") ||
this.hasAttribute("breakout-extend") ||
this.selectionStart != this.selectionEnd ||
// Avoid extending when the user is copying a part of the text, provided
// the view is not open, otherwise it may be the autofill selection.
(this.selectionStart != this.selectionEnd && !this.view.isOpen) ||
!(
(this.getAttribute("focused") == "true" &&
!this.textbox.classList.contains("hidden-focus")) ||

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

@ -32,7 +32,8 @@ var UrlbarTokenizer = {
REGEXP_LIKE_PROTOCOL: /^[A-Z+.-]+:\/*(?!\/)/i,
REGEXP_USERINFO_INVALID_CHARS: /[^\w.~%!$&'()*+,;=:-]/,
REGEXP_HOSTPORT_INVALID_CHARS: /[^\[\]A-Z0-9.:-]/i,
REGEXP_HOSTPORT_IP_LIKE: /^[a-f0-9\.\[\]:]+$/i,
REGEXP_SINGLE_WORD_HOST: /^[^.:]$/i,
REGEXP_HOSTPORT_IP_LIKE: /^(?=(.*[.:].*){2})[a-f0-9\.\[\]:]+$/i,
// This accepts partial IPv4.
REGEXP_HOSTPORT_INVALID_IP: /\.{2,}|\d{5,}|\d{4,}(?![:\]])|^\.|^(\d+\.){4,}\d+$|^\d{4,}$/,
// This only accepts complete IPv4.
@ -182,7 +183,8 @@ var UrlbarTokenizer = {
!this.REGEXP_LIKE_PROTOCOL.test(hostPort) &&
!this.REGEXP_USERINFO_INVALID_CHARS.test(userinfo) &&
!this.REGEXP_HOSTPORT_INVALID_CHARS.test(hostPort) &&
(!this.REGEXP_HOSTPORT_IP_LIKE.test(hostPort) ||
(this.REGEXP_SINGLE_WORD_HOST.test(hostPort) ||
!this.REGEXP_HOSTPORT_IP_LIKE.test(hostPort) ||
!this.REGEXP_HOSTPORT_INVALID_IP.test(hostPort))
);
},

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

@ -442,6 +442,10 @@ class UrlbarView {
(trimmedValue[0] != UrlbarTokenizer.RESTRICT.SEARCH ||
trimmedValue.length != 1)
);
// The input field applies autofill on input, without waiting for results.
// Once we get results, we can ask it to correct wrong predictions.
this.input.maybeClearAutofillPlaceholder(queryContext.results[0]);
}
this._openPanel();

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

@ -155,7 +155,49 @@ add_task(async function noMatch2() {
await cleanUp();
});
async function searchAndCheck(searchString, expectedAutofillValue) {
add_task(async function clear_placeholder_for_keyword_or_alias() {
info("Clear the autofill placeholder if a keyword is typed");
await PlacesTestUtils.addVisits("http://example.com/");
await PlacesUtils.keywords.insert({
keyword: "ex",
url: "http://somekeyword.com/",
});
let engine = await Services.search.addEngineWithDetails("AutofillTest", {
alias: "exam",
template: "http://example.com/?search={searchTerms}",
});
registerCleanupFunction(async function() {
await PlacesUtils.keywords.remove("ex");
await Services.search.removeEngine(engine);
});
// Do an initial search that triggers autofill so that the placeholder has an
// initial value.
await promiseAutocompleteResultPopup("e", window, true);
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
Assert.ok(details.autofill);
Assert.equal(gURLBar.value, "example.com/");
Assert.equal(gURLBar.selectionStart, "e".length);
Assert.equal(gURLBar.selectionEnd, "example.com/".length);
// The values are initially autofilled on input, then the placeholder is
// removed when the first non-autofill result arrives.
// Matches the keyword.
await searchAndCheck("ex", "example.com/", "ex");
await searchAndCheck("EXA", "EXAmple.com/", "EXAmple.com/");
// Matches the alias.
await searchAndCheck("eXaM", "eXaMple.com/", "eXaM");
await searchAndCheck("examp", "example.com/", "example.com/");
await cleanUp();
});
async function searchAndCheck(
searchString,
expectedAutofillValue,
onCompleteValue = ""
) {
gURLBar.value = searchString;
// Placeholder autofill is done on input, so fire an input event. As the
@ -172,6 +214,13 @@ async function searchAndCheck(searchString, expectedAutofillValue) {
Assert.equal(gURLBar.selectionEnd, expectedAutofillValue.length);
await UrlbarTestUtils.promiseSearchComplete(window);
if (onCompleteValue) {
// Check the final value after the results arrived.
Assert.equal(gURLBar.value, onCompleteValue);
Assert.equal(gURLBar.selectionStart, searchString.length);
Assert.equal(gURLBar.selectionEnd, onCompleteValue.length);
}
}
async function cleanUp() {

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

@ -82,8 +82,8 @@ add_task(async function test_display_keyword_without_query() {
);
Assert.equal(
result.displayed.title,
"example.com",
"Node should contain the name of the bookmark"
"https://example.com/browser/browser/components/urlbar/tests/browser/print_postdata.sjs?q=",
"Node should contain the url of the bookmark"
);
Assert.equal(
result.displayed.action,

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

@ -36,12 +36,4 @@ add_task(async function() {
"The Urlbar should not have the breakout-extend attribute."
);
Assert.ok(win.gURLBar.focused, "The Urlbar should be focused.");
// Simulating a user switching out of the Firefox window and back in.
let newWin = await BrowserTestUtils.openNewBrowserWindow();
await BrowserTestUtils.closeWindow(newWin);
Assert.ok(
win.gURLBar.hasAttribute("breakout-extend"),
"The Urlbar should have the breakout-extend attribute."
);
});

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

@ -171,9 +171,10 @@ add_task(async function test_keyword_result() {
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
// Because only the keyword is typed, we show the bookmark url.
assertElementsDisplayed(details, {
separator: true,
title: "example.com",
title: TEST_URL + "?q=",
type: UrlbarUtils.RESULT_TYPE.KEYWORD,
});

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

@ -375,6 +375,18 @@ add_task(async function test_tokenizer() {
{ value: "eXaMpLe", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
],
},
// This is not properly correct, an origin cannot be completely numeric,
// but we use this to check whether we should match against origins, thus
// whether an origin could start with this string.
// In the future we may evaluate reporting this as TEXT and instead
// introduce a "looksLikeStartOfOrigin".
{
desc: "plain number",
searchString: "1001",
expectedTokens: [
{ value: "1001", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
],
},
];
for (let queryContext of testContexts) {

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

@ -11,6 +11,9 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
ExtensionPreferencesManager:
"resource://gre/modules/ExtensionPreferencesManager.jsm",
IgnoreLists: "resource://gre/modules/IgnoreLists.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
@ -71,7 +74,7 @@ let HomePage = {
* homepage, but this is deemed acceptable, as we'll correct it once
* initialised.
*/
async init() {
async delayedStartup() {
if (this._initializationPromise) {
await this._initializationPromise;
return;
@ -178,7 +181,7 @@ let HomePage = {
* `|` separated list of URLs.
*/
async set(value) {
await this.init();
await this.delayedStartup();
if (await this.shouldIgnore(value)) {
Cu.reportError(
@ -234,7 +237,7 @@ let HomePage = {
* True if the url should be ignored.
*/
async shouldIgnore(url) {
await this.init();
await this.delayedStartup();
const lowerURL = url.toLowerCase();
return this._ignoreList.some(code => lowerURL.includes(code.toLowerCase()));
@ -261,8 +264,37 @@ let HomePage = {
if (
this._ignoreList.some(code => homePages.includes(code.toLowerCase()))
) {
this.clear();
Services.prefs.clearUserPref(kExtensionControllerPref);
if (Services.prefs.getBoolPref(kExtensionControllerPref, false)) {
if (Services.appinfo.inSafeMode) {
// Add-ons don't get started in safe mode, so just abort this.
// We'll get to remove them when we next start in normal mode.
return;
}
// getSetting does not need the module to be loaded.
const item = await ExtensionPreferencesManager.getSetting(
"homepage_override"
);
if (item && item.id) {
// During startup some modules may not be loaded yet, so we load
// the setting we need prior to removal.
await ExtensionParent.apiManager.asyncLoadModule(
"chrome_settings_overrides"
);
ExtensionPreferencesManager.removeSetting(
item.id,
"homepage_override"
).catch(Cu.reportError);
} else {
// If we don't have a setting for it, we assume the pref has
// been incorrectly set somehow.
Services.prefs.clearUserPref(kExtensionControllerPref);
Services.prefs.clearUserPref(
"browser.startup.homepage_override.privateAllowed"
);
}
} else {
this.clear();
}
Services.telemetry.recordEvent(
"homepage",
"preference",

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

@ -44,7 +44,7 @@ add_task(async function test_initWithIgnoredPageCausesReset() {
);
Assert.ok(HomePage.overridden, "Should have overriden the homepage");
await HomePage.init();
await HomePage.delayedStartup();
Assert.ok(
!HomePage.overridden,

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

@ -75,8 +75,6 @@ function jest() {
const jsonOut = out.substring(out.indexOf("{"), out.lastIndexOf("}") + 1);
const results = JSON.parse(jsonOut);
const failed = results.numFailedTests == 0;
// The individual failing tests are in jammed into the same message string :/
const errors = [].concat(
...results.testResults.map(r =>
@ -85,7 +83,7 @@ function jest() {
);
logErrors("jest", errors);
return failed;
return errors.length == 0;
}
function stylelint() {

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

@ -40,5 +40,8 @@ module.exports = {
moduleNameMapper: {
"\\.css$": "<rootDir>/src/test/__mocks__/styleMock.js",
"\\.svg$": "<rootDir>/src/test/__mocks__/svgMock.js",
"^Services": "<rootDir>/src/test/fixtures/Services",
// Map all require("devtools/...") to the real devtools root.
"^devtools\\/(.*)": "<rootDir>/../../$1",
},
};

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

@ -72,7 +72,7 @@ function convertToList(results, source) {
for (const column of results[line]) {
positions.push({
line: Number(line),
column: column,
column,
sourceId: id,
sourceUrl: url,
});
@ -186,7 +186,7 @@ async function _setBreakpointPositions(cx, sourceId, line, thunkArgs) {
dispatch({
type: "ADD_BREAKPOINT_POSITIONS",
cx,
source: source,
source,
positions,
});
}

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

@ -225,7 +225,7 @@ export function toggleBreakpointAtLine(cx: Context, line: number) {
addBreakpoint(cx, {
sourceId: selectedSource.id,
sourceUrl: selectedSource.url,
line: line,
line,
})
);
};

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

@ -39,7 +39,7 @@ export function continueToHere(
await dispatch(
addHiddenBreakpoint(cx, {
line,
column: column,
column,
sourceId: selectedSource.id,
})
);

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

@ -45,7 +45,7 @@ export async function prettyPrintSource(
const url = getPrettySourceURL(generatedSource.url);
const { code, mappings } = await prettyPrint({
text: content.value,
url: url,
url,
});
await sourceMaps.applySourceMap(generatedSource.id, url, code, mappings);
@ -90,7 +90,7 @@ function selectPrettyLocation(cx: Context, prettySource: Source) {
return async ({ dispatch, sourceMaps, getState }: ThunkArgs) => {
let location = getSelectedLocation(getState());
if (location) {
if (location && location.line >= 1) {
location = await sourceMaps.getOriginalLocation(location);
return dispatch(
selectSpecificLocation(cx, { ...location, sourceId: prettySource.id })

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

@ -148,7 +148,7 @@ describe("sources - new sources", () => {
return [
{
id: generatedToOriginalId(source.id, url),
url: url,
url,
},
];
},

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

@ -29,7 +29,7 @@ describe("file text search", () => {
count: 2,
index: 2,
matchIndex: 1,
matches: matches,
matches,
});
});

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

@ -55,7 +55,7 @@ export async function onConnect(connection: any, actions: Object) {
// they are active once attached.
actions.addEventListenerBreakpoints([]).catch(e => console.error(e));
const traits = tabTarget.traits;
const { traits } = tabTarget;
await actions.connect(
tabTarget.url,
threadFront.actor,

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

@ -34,8 +34,7 @@ function addThreadEventListeners(thread: ThreadFront) {
}
function setupEvents(dependencies: Dependencies) {
const threadFront = dependencies.threadFront;
const tabTarget = dependencies.tabTarget;
const { tabTarget, threadFront } = dependencies;
actions = dependencies.actions;
sourceQueue.initialize(actions);

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

@ -66,7 +66,7 @@ export class DebugLine extends PureComponent<Props> {
if (!isDocumentReady(source, location)) {
return;
}
const sourceId = location.sourceId;
const { sourceId } = location;
const doc = getDocument(sourceId);
let { line, column } = toEditorPosition(location);
@ -104,9 +104,8 @@ export class DebugLine extends PureComponent<Props> {
this.debugExpression.clear();
}
const sourceId = location.sourceId;
const { line } = toEditorPosition(location);
const doc = getDocument(sourceId);
const doc = getDocument(location.sourceId);
const { lineClass } = this.getTextClasses(why);
doc.removeLineClass(line, "line", lineClass);
}

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

@ -154,7 +154,7 @@ class SourceFooter extends PureComponent<Props, State> {
onClick={() => toggleBlackBox(cx, selectedSource)}
className={classnames("action", type, {
active: sourceLoaded,
blackboxed: blackboxed,
blackboxed,
})}
key={type}
title={tooltip}

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

@ -239,7 +239,7 @@ const mapStateToProps = (state, { source }) => {
return {
cx: getContext(state),
tabSources: getSourcesForTabs(state),
selectedSource: selectedSource,
selectedSource,
activeSearch: getActiveSearch(state),
hasSiblingOfSameName: getHasSiblingOfSameName(state, source),
};

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

@ -45,9 +45,9 @@ function generateDefaults(
},
},
location: breakpoint.location,
source: source,
breakpoint: breakpoint,
log: log,
source,
breakpoint,
log,
getDefaultValue: jest.fn(),
openConditionalPanel: jest.fn(),
closeConditionalPanel: jest.fn(),

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

@ -28,7 +28,6 @@ import PreviewFunction from "../shared/PreviewFunction";
import { uniq, sortBy } from "lodash";
import type {
AstLocation,
SymbolDeclarations,
SymbolDeclaration,
FunctionDeclaration,
@ -126,17 +125,19 @@ export class Outline extends Component<Props, State> {
this.setState({ focusedItem: closestItem });
}
selectItem(location: AstLocation) {
selectItem(selectedItem: ?SymbolDeclaration) {
const { cx, selectedSource, selectLocation } = this.props;
if (!selectedSource) {
if (!selectedSource || !selectedItem) {
return;
}
selectLocation(cx, {
sourceId: selectedSource.id,
line: location.start.line,
column: location.start.column,
line: selectedItem.location.start.line,
column: selectedItem.location.start.column,
});
this.setState({ focusedItem: selectedItem });
}
onContextMenu(event: SyntheticEvent<HTMLElement>, func: SymbolDeclaration) {
@ -205,7 +206,7 @@ export class Outline extends Component<Props, State> {
this.focusedElRef = el;
}
}}
onClick={() => this.selectItem(location)}
onClick={() => this.selectItem(func)}
onContextMenu={e => this.onContextMenu(e, func)}
>
<span className="outline-list__element-icon">λ</span>
@ -232,7 +233,8 @@ export class Outline extends Component<Props, State> {
const classFunctions = functions.filter(func => func.klass === klass);
const classInfo = this.props.symbols.classes.find(c => c.name === klass);
const isFocused = focusedItem === (classFunc || classInfo);
const item = classFunc || classInfo;
const isFocused = focusedItem === item;
return (
<li
@ -246,7 +248,7 @@ export class Outline extends Component<Props, State> {
>
<h2
className={classnames("", { focused: isFocused })}
onClick={classInfo ? () => this.selectItem(classInfo.location) : null}
onClick={() => this.selectItem(item)}
>
{classFunc
? this.renderFunction(classFunc)

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

@ -93,7 +93,7 @@ function shouldAutoExpand(depth, item, debuggeeUrl, projectRoot) {
function findSource({ threads, sources }, itemPath, source) {
const targetThread = threads.find(thread => itemPath.includes(thread.actor));
if (targetThread && source) {
const actor = targetThread.actor;
const { actor } = targetThread;
if (sources[actor]) {
return sources[actor][source.id];
}

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

@ -172,12 +172,11 @@ class SourceTreeItem extends Component<Props, State> {
};
handleDownloadFile = async (cx: Context, source: ?Source, item: TreeNode) => {
const name = item.name;
if (!this.props.sourceContent) {
const { sourceContent } = this.props;
if (!sourceContent) {
await this.props.loadSourceText({ cx, source });
}
const data = this.props.sourceContent;
downloadFile(data, name);
downloadFile(sourceContent, item.name);
};
addCollapseExpandAllOptions = (menuOptions: ContextMenu, item: TreeNode) => {

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

@ -30,7 +30,7 @@ describe("SourcesTree", () => {
it("Should show a 'No Sources' message if there are no sources", async () => {
const { component, defaultState } = render();
const sourceTree = defaultState.sourceTree;
const { sourceTree } = defaultState;
sourceTree.contents = [];
component.setState({ sourceTree: sourceTree });
expect(component).toMatchSnapshot();

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

@ -83,7 +83,7 @@ function filter(values, query) {
return fuzzyAldrin.filter(values, query, {
key: "value",
maxResults: maxResults,
maxResults,
preparedQuery,
});
}

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

@ -33,7 +33,7 @@ function generateDefaults(disabled) {
{
...makeMockBreakpoint(source, 1),
id: "https://example.com/main.js:1:",
disabled: disabled,
disabled,
options: {
condition: "",
logValue: "",
@ -43,7 +43,7 @@ function generateDefaults(disabled) {
{
...makeMockBreakpoint(source, 2),
id: "https://example.com/main.js:2:",
disabled: disabled,
disabled,
options: {
hidden: false,
},
@ -51,7 +51,7 @@ function generateDefaults(disabled) {
{
...makeMockBreakpoint(source, 3),
id: "https://example.com/main.js:3:",
disabled: disabled,
disabled,
},
];

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

@ -156,7 +156,7 @@ class Expressions extends Component<Props, State> {
}
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const target = e.target;
const { target } = e;
if (features.autocompleteExpression) {
this.findAutocompleteMatches(target.value, target.selectionStart);
}

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

@ -69,14 +69,13 @@ export default function FrameMenu(
const menuOptions = [];
const source = frame.source;
const toggleFrameworkElement = toggleFrameworkGroupingElement(
callbacks.toggleFrameworkGrouping,
frameworkGroupingOn
);
menuOptions.push(toggleFrameworkElement);
const { source } = frame;
if (source) {
const copySourceUri2 = copySourceElement(source.url);
menuOptions.push(copySourceUri2);

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

@ -50,7 +50,7 @@ class WhyPaused extends PureComponent<Props, State> {
return exception;
}
const preview = exception.preview;
const { preview } = exception;
if (!preview || !preview.name || !preview.message) {
return;
}

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

@ -134,16 +134,14 @@ class XHRBreakpoints extends Component<Props, State> {
};
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const target = e.target;
this.setState({ inputValue: target.value });
this.setState({ inputValue: e.target.value });
};
handleMethodChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const target = e.target;
this.setState({
focused: true,
editing: true,
inputMethod: target.value,
inputMethod: e.target.value,
});
};

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

@ -125,7 +125,7 @@ export default function SmartGap({
const tokenRect = token.getBoundingClientRect();
// $FlowIgnore
const previewRect = preview.getBoundingClientRect();
const orientation = coords.orientation;
const { orientation } = coords;
let optionalMarginLeft, optionalMarginTop;
if (orientation === "down") {
@ -156,8 +156,8 @@ export default function SmartGap({
version="1.1"
xmlns="http://www.w3.org/2000/svg"
style={{
height: height,
width: width,
height,
width,
position: "absolute",
marginLeft: optionalMarginLeft,
marginTop: optionalMarginTop,

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

@ -75,7 +75,7 @@ describe("SearchInput", () => {
it("stores scroll history in state", () => {
const onHistoryScroll = jest.fn();
wrapper.setProps({
onHistoryScroll: onHistoryScroll,
onHistoryScroll,
onKeyDown: jest.fn(),
});
wrapper.find("input").simulate("keyDown", createSearch(searches[0]));

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

@ -27,9 +27,7 @@ function generateDefaults(overrides) {
flashLineRange: jest.fn(),
isHidden: false,
symbols: {},
selectedLocation: {
sourceId: sourceId,
},
selectedLocation: { sourceId },
onAlphabetizeClick: jest.fn(),
...overrides,
};
@ -147,7 +145,7 @@ describe("Outline", () => {
};
const { component } = render({
symbols: symbols,
symbols,
alphabetizeOutline: true,
});
expect(component).toMatchSnapshot();
@ -181,7 +179,7 @@ describe("Outline", () => {
],
};
const { component } = render({ symbols: symbols });
const { component } = render({ symbols });
expect(component).toMatchSnapshot();
});
@ -199,7 +197,7 @@ describe("Outline", () => {
};
const { component } = render({
symbols: symbols,
symbols,
alphabetizeOutline: true,
});
expect(component).toMatchSnapshot();
@ -211,13 +209,13 @@ describe("Outline", () => {
classes: [makeSymbolDeclaration("x_klass", 24, 27)],
};
const { component, props } = render({ symbols: symbols });
const { component, props } = render({ symbols });
await component.find("h2").simulate("click", {});
expect(props.selectLocation).toHaveBeenCalledWith(mockcx, {
line: 24,
sourceId: sourceId,
sourceId,
});
});
@ -296,7 +294,7 @@ describe("Outline", () => {
expect(copyToTheClipboard).toHaveBeenCalledWith(mockFunctionText);
expect(props.flashLineRange).toHaveBeenCalledWith({
end: endLine,
sourceId: sourceId,
sourceId,
start: startLine,
});
});

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

@ -362,7 +362,7 @@ describe("QuickOpenModal", () => {
expect(props.selectSpecificLocation).toHaveBeenCalledWith(mockcx, {
column: 12,
line: 34,
sourceId: sourceId,
sourceId,
});
});
@ -636,7 +636,7 @@ describe("QuickOpenModal", () => {
expect(wrapper.state().selectedIndex).toEqual(0);
expect(props.highlightLineRange).toHaveBeenCalledWith({
end: 3,
sourceId: sourceId,
sourceId,
start: 1,
});
});

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

@ -9,10 +9,7 @@ import { shallow } from "enzyme";
import WhyPaused from "../SecondaryPanes/WhyPaused.js";
function render(why: Object, delay: ?number) {
const props = {
why: why,
delay: delay,
};
const props = { why, delay };
// $FlowIgnore
const component = shallow(<WhyPaused.WrappedComponent {...props} />);

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

@ -39,7 +39,7 @@ export function initialBreakpointsState(
): BreakpointsState {
return {
breakpoints: {},
xhrBreakpoints: xhrBreakpoints,
xhrBreakpoints,
breakpointsDisabled: false,
};
}

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

@ -71,7 +71,7 @@ function update(
return zip(inputs, results).reduce(
(_state, [input, result]) =>
updateExpressionInList(_state, input, {
input: input,
input,
value: result,
updating: false,
}),

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

@ -61,7 +61,6 @@ function update(
return { ...state, query: action.query };
case "ADD_SEARCH_RESULT":
const results = state.results;
if (action.result.matches.length === 0) {
return state;
}
@ -71,7 +70,7 @@ function update(
...action.result,
matches: action.result.matches.map(m => ({ type: "MATCH", ...m })),
};
return { ...state, results: [...results, result] };
return { ...state, results: [...state.results, result] };
case "UPDATE_STATUS":
const ongoingSearch =

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

@ -18,7 +18,6 @@ import type { PauseState } from "./pause";
import type { PreviewState } from "./preview";
import type { PendingBreakpointsState } from "../selectors";
import type { ProjectTextSearchState } from "./project-text-search";
import type { Record } from "../utils/makeRecord";
import type { SourcesState } from "./sources";
import type { SourceActorsState } from "./source-actors";
import type { TabList } from "./tabs";
@ -29,7 +28,7 @@ import type { EventListenersState } from "./event-listeners";
export type State = {
ast: ASTState,
breakpoints: BreakpointsState,
expressions: Record<ExpressionState>,
expressions: ExpressionState,
eventListenerBreakpoints: EventListenersState,
threads: ThreadsState,
fileSearch: FileSearchState,

9
devtools/client/debugger/src/test/fixtures/Services.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.exports = {
appinfo: "",
};

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

@ -117,7 +117,7 @@ function mockIndexeddDB() {
// NOTE: We polyfill finally because TRY uses node 8
if (!global.Promise.prototype.finally) {
global.Promise.prototype.finally = function finallyPolyfill(callback) {
const constructor = this.constructor;
const { constructor } = this;
return this.then(
function(value) {

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

@ -340,6 +340,8 @@ export type Expression = {
value: Object,
from: string,
updating: boolean,
exception?: string,
error?: string,
};
/**

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

@ -41,6 +41,7 @@ export function findFunctionByName(
return null;
}
const functions = symbols.functions;
return functions.find(node => node.name === name && node.index === index);
return symbols.functions.find(
node => node.name === name && node.index === index
);
}

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

@ -114,10 +114,7 @@ export function toSourceLine(sourceId: string, line: number): ?number {
}
export function scrollToColumn(codeMirror: any, line: number, column: number) {
const { top, left } = codeMirror.charCoords(
{ line: line, ch: column },
"local"
);
const { top, left } = codeMirror.charCoords({ line, ch: column }, "local");
if (!isVisible(codeMirror, top, left)) {
const scroller = codeMirror.getScrollerElement();
@ -268,7 +265,7 @@ export function getCursorColumn(codeMirror: Object): number {
export function getTokenEnd(codeMirror: Object, line: number, column: number) {
const token = codeMirror.getTokenAt({
line: line,
line,
ch: column + 1,
});
const tokenString = token.string;

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

@ -159,7 +159,7 @@ export default class SourceEditor {
* @memberof utils/source-editor
*/
setFirstVisibleLine(line: number) {
const { top } = this.editor.charCoords({ line: line, ch: 0 }, "local");
const { top } = this.editor.charCoords({ line, ch: 0 }, "local");
this.editor.scrollTo(0, top);
}
}

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

@ -32,28 +32,28 @@ function isUnavailable(value) {
}
export function getValue(expression: Expression) {
const value = expression.value;
const { value, from, exception, error } = expression;
if (!value) {
return {
path: expression.from,
path: from,
value: { unavailable: true },
};
}
if (value.exception) {
if (isUnavailable(value.exception)) {
if (exception) {
if (isUnavailable(exception)) {
return { value: { unavailable: true } };
}
return {
path: value.from,
value: value.exception,
path: from,
value: exception,
};
}
if (value.error) {
if (error) {
return {
path: value.from,
value: value.error,
path: from,
value: error,
};
}

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

@ -1,103 +0,0 @@
/* 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/>. */
// @flow
/**
* Immutable JS conversion utils
* @deprecated
* @module utils/fromJS
*/
import * as I from "immutable";
import { isFunction } from "lodash";
// hasOwnProperty is defensive because it is possible that the
// object that we're creating a map for has a `hasOwnProperty` field
function hasOwnProperty(value, key) {
if (value.hasOwnProperty && isFunction(value.hasOwnProperty)) {
return value.hasOwnProperty(key);
}
if (value.prototype && value.prototype.hasOwnProperty) {
return value.prototype.hasOwnProperty(key);
}
return false;
}
/*
creates an immutable map, where each of the value's
items are transformed into their own map.
NOTE: we guard against `length` being a property because
length confuses Immutable's internal algorithm.
*/
function createMap(value) {
const hasLength = hasOwnProperty(value, "length");
const length = value.length;
if (hasLength) {
value.length = `${value.length}`;
}
let map = I.Seq(value)
.map(fromJS)
.toMap();
if (hasLength) {
map = map.set("length", length);
value.length = length;
}
return map;
}
function createList(value) {
return I.Seq(value)
.map(fromJS)
.toList();
}
/**
* When our app state is fully typed, we should be able to get rid of
* this function. This is only temporarily necessary to support
* converting typed objects to immutable.js, which usually happens in
* reducers.
*
* @memberof utils/fromJS
* @static
*/
function fromJS(value: any): any {
if (Array.isArray(value)) {
return createList(value);
}
if (value && value.constructor && value.constructor.meta) {
// This adds support for tcomb objects which are native JS objects
// but are not "plain", so the above checks fail. Since they
// behave the same we can use the same constructors, but we need
// special checks for them.
const kind = value.constructor.meta.kind;
if (kind === "struct") {
return createMap(value);
} else if (kind === "list") {
return createList(value);
}
}
// If it's a primitive type, just return the value. Note `==` check
// for null, which is intentionally used to match either `null` or
// `undefined`.
if (value == null || typeof value !== "object") {
return value;
}
// Otherwise, treat it like an object. We can't reliably detect if
// it's a plain object because we might be objects from other JS
// contexts so `Object !== Object`.
return createMap(value);
}
module.exports = fromJS;

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

@ -1,50 +0,0 @@
/* 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/>. */
// @flow
/**
* When Flow 0.29 is released (very soon), we can use this Record type
* instead of the builtin immutable.js Record type. This is better
* because all the fields are actually typed, unlike the builtin one.
* This depends on a performance fix that will go out in 0.29 though;
* @module utils/makeRecord
*/
import * as I from "immutable";
/**
* @memberof utils/makeRecord
* @static
*/
export type Record<T: Object> = {
equals<A>(other: A): boolean,
get<A>(key: $Keys<T>, notSetValue?: any): A,
getIn<A>(keyPath: Array<any>, notSetValue?: any): A,
hasIn<A>(keyPath: Array<any>): boolean,
set<A>(key: $Keys<T>, value: A): Record<T>,
setIn(keyPath: Array<any>, ...iterables: Array<any>): Record<T>,
merge(values: $Shape<T>): Record<T>,
mergeIn(keyPath: Array<any>, ...iterables: Array<any>): Record<T>,
delete<A>(key: $Keys<T>, value: A): Record<T>,
deleteIn(keyPath: Array<any>, ...iterables: Array<any>): Record<T>,
update<A>(key: $Keys<T>, value: A): Record<T>,
updateIn(keyPath: Array<any>, ...iterables: Array<any>): Record<T>,
remove<A>(key: $Keys<T>): Record<T>,
toJS(): T,
} & T;
/**
* Make an immutable record type
*
* @param spec - the keys and their default values
* @return a state record factory function
* @memberof utils/makeRecord
* @static
*/
function makeRecord<T>(spec: T & Object): () => Record<T> {
return I.Record(spec);
}
export default makeRecord;

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

@ -24,13 +24,11 @@ CompiledModules(
'defer.js',
'DevToolsUtils.js',
'expressions.js',
'fromJS.js',
'function.js',
'indentation.js',
'isMinified.js',
'location.js',
'log.js',
'makeRecord.js',
'memoize.js',
'memoizeLast.js',
'memoizableAction.js',

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

@ -60,7 +60,7 @@ export function getScope(
const key = `${actor}-${scopeIndex}`;
if (type === "function" || type === "block") {
const bindings = scope.bindings;
const { bindings } = scope;
let vars = getBindingVariables(bindings, key);

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

@ -11,7 +11,7 @@ export function getFramePopVariables(why: Why, path: string): NamedValue[] {
const vars: Array<NamedValue> = [];
if (why && why.frameFinished) {
const frameFinished = why.frameFinished;
const { frameFinished } = why;
// Always display a `throw` property if present, even if it is falsy.
if (Object.prototype.hasOwnProperty.call(frameFinished, "throw")) {

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

@ -84,8 +84,7 @@ export function isJavaScript(source: Source, content: SourceContent): boolean {
* @static
*/
export function isPretty(source: Source): boolean {
const url = source.url;
return isPrettyURL(url);
return isPrettyURL(source.url);
}
export function isPrettyURL(url: string): boolean {
@ -398,8 +397,7 @@ export function getTextAtPosition(
asyncContent: AsyncValue<SourceContent> | null,
location: SourceLocation
) {
const column = location.column || 0;
const line = location.line;
const { column, line = 0 } = location;
const lineText = getLineText(sourceId, asyncContent, line);
return lineText.slice(column, column + 100).trim();

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

@ -1,79 +0,0 @@
/* 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/>. */
// @flow
import fromJS from "../fromJS";
const preview = {
kind: "ArrayLike",
length: 201,
items: [
{
type: "null",
},
"a test",
"a",
{
type: "null",
},
{
type: "null",
},
{
type: "null",
},
{
type: "null",
},
{
type: "null",
},
{
type: "null",
},
{
type: "null",
},
],
};
describe("fromJS", () => {
it("supports array like objects", () => {
const iPreview = fromJS(preview);
expect(iPreview.get("length")).toEqual(201);
expect(iPreview.get("items").size).toEqual(10);
});
it("supports arrays", () => {
const iItems = fromJS(preview.items);
expect(iItems.getIn([0, "type"])).toEqual("null");
expect(iItems.size).toEqual(10);
});
it("supports objects without a prototype", () => {
expect(() => fromJS(Object.create(null))).not.toThrow();
});
it("supports objects with `hasOwnProperty` fields", () => {
const value = {
lookupIterator: {
value: {},
writable: true,
},
hasOwnProperty: {
value: {},
writable: true,
},
arguments: {
value: {},
writable: false,
},
};
const newMap = fromJS(value);
expect(newMap.getIn(["hasOwnProperty", "writable"])).toEqual(true);
});
});

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

@ -47,7 +47,7 @@ export function getWasmText(sourceId: string, data: Uint8Array) {
result = { lines: ["No luck with wast conversion"], offsets: [0], done };
}
const offsets = result.offsets;
const { offsets } = result;
const lines = [];
for (let i = 0; i < offsets.length; i++) {
lines[offsets[i]] = i;

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

@ -839,7 +839,7 @@ const scopeCollectionVisitor = {
scope && scope !== parentScope;
scope = scope.parent
) {
const freeVariables = state.freeVariables;
const { freeVariables } = state;
state.freeVariables = state.freeVariableStack.pop();
const parentFreeVariables = state.freeVariables;

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

@ -196,7 +196,7 @@ function extractSymbol(path: SimplePath, symbols, state) {
property: { name, loc },
} = callee;
symbols.callExpressions.push({
name: name,
name,
values: args.filter(arg => arg.value).map(arg => arg.value),
location: loc,
});
@ -435,8 +435,7 @@ function getSnippet(
return expression;
}
const name = node.name;
const prop = extendSnippet(name, expression, path, prevPath);
const prop = extendSnippet(node.name, expression, path, prevPath);
return prop;
}
@ -455,8 +454,7 @@ function getSnippet(
}
if (t.isIdentifier(path)) {
const node = path.node;
return `${node.name}.${expression}`;
return `${path.node.name}.${expression}`;
}
if (t.isObjectProperty(path)) {

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

@ -61,7 +61,7 @@ function _getNextStep(
if (nextStatement) {
return {
...nextStatement.node.loc.start,
sourceId: sourceId,
sourceId,
};
}

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

@ -31,7 +31,7 @@ type InvertedMapping = {
function prettyPrint({ url, indent, sourceText }) {
const prettified = prettyFast(sourceText, {
url: url,
url,
indent: " ".repeat(indent),
});

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

@ -8,13 +8,14 @@ function getItems(dbg) {
function getNthItem(dbg, index) {
return findElement(dbg, "outlineItem", index);
}
// Tests that the editor highlights the correct location when the
// debugger pauses
// Tests that clicking a function in outline panel, the editor highlights the correct location.
// Tests that outline panel can sort functions alphabetically.
add_task(async function() {
const dbg = await initDebugger("doc-scripts.html", "simple1");
const {
selectors: { getSelectedSource },
getState
getState,
} = dbg;
await selectSource(dbg, "simple1", 1);
@ -22,13 +23,17 @@ add_task(async function() {
findElementWithSelector(dbg, ".outline-tab").click();
is(getItems(dbg).length, 5, "5 items in the list");
// click on an element
info("Click an item in outline panel");
const item = getNthItem(dbg, 3);
is(item.innerText, "evaledFunc()", "got evaled func");
item.click();
assertHighlightLocation(dbg, "simple1", 15);
ok(
item.parentNode.classList.contains("focused"),
"The clicked item li is focused"
);
// Ensure "main()" is the first function listed
info("Ensure main() is the first function listed");
const firstFunction = findElementWithSelector(
dbg,
".outline-list__element .function-signature"
@ -38,7 +43,8 @@ add_task(async function() {
"main()",
"Natural first function is first listed"
);
// Sort the list
info("Sort the list");
findElementWithSelector(dbg, ".outline-footer button").click();
// Button becomes active to show alphabetization
is(
@ -46,11 +52,15 @@ add_task(async function() {
"active",
"Alphabetize button is highlighted when active"
);
// Ensure "doEval()" is the first function listed after alphabetization
info("Ensure doEval() is the first function listed after alphabetization");
const firstAlphaFunction = findElementWithSelector(
dbg,
".outline-list__element .function-signature"
);
is(firstAlphaFunction.innerText, "doEval()",
"Alphabetized first function is correct");
is(
firstAlphaFunction.innerText,
"doEval()",
"Alphabetized first function is correct"
);
});

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

@ -1545,7 +1545,7 @@ async function getTokenFromPosition(dbg, {line, ch}) {
cm.scrollIntoView({ line: line - 1, ch }, 0);
// Ensure the line is visible with margin because the bar at the bottom of
// the editor overlaps into what the editor things is its own space, blocking
// the editor overlaps into what the editor thinks is its own space, blocking
// the click event below.
await waitForScrolling(cm);
@ -1601,7 +1601,7 @@ async function codeMirrorGutterElement(dbg, line) {
}
async function clickAtPos(dbg, pos) {
const tokenEl = await getTokenFromPosition(dbg, pos)
const tokenEl = await getTokenFromPosition(dbg, pos);
if (!tokenEl) {
return false;
@ -1621,7 +1621,7 @@ async function clickAtPos(dbg, pos) {
}
async function hoverAtPos(dbg, pos) {
const tokenEl = await getTokenFromPosition(dbg, pos)
const tokenEl = await getTokenFromPosition(dbg, pos);
if (!tokenEl) {
return false;

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

@ -20,18 +20,6 @@ const Description = createFactory(
require("devtools/client/performance-new/components/Description.js")
);
const actions = require("devtools/client/performance-new/store/actions");
const {
recordingState: {
NOT_YET_KNOWN,
AVAILABLE_TO_RECORD,
REQUEST_TO_START_RECORDING,
REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER,
REQUEST_TO_STOP_PROFILER,
RECORDING,
OTHER_IS_RECORDING,
LOCKED_BY_PRIVATE_BROWSING,
},
} = require("devtools/client/performance-new/utils");
const selectors = require("devtools/client/performance-new/store/selectors");
/**
@ -90,15 +78,15 @@ class Perf extends PureComponent {
let recordingState = this.props.recordingState;
// It's theoretically possible we got an event that already let us know about
// the current state of the profiler.
if (recordingState === NOT_YET_KNOWN && isSupportedPlatform) {
if (recordingState === "not-yet-known" && isSupportedPlatform) {
if (isLockedForPrivateBrowsing) {
recordingState = LOCKED_BY_PRIVATE_BROWSING;
recordingState = "locked-by-private-browsing";
} else if (isActive) {
// The popup is a global control for the recording, so allow it to take
// control of it.
recordingState = isPopup ? RECORDING : OTHER_IS_RECORDING;
recordingState = isPopup ? "recording" : "other-is-recording";
} else {
recordingState = AVAILABLE_TO_RECORD;
recordingState = "available-to-record";
}
}
reportProfilerReady(isSupportedPlatform, recordingState);
@ -127,17 +115,17 @@ class Perf extends PureComponent {
componentWillUnmount() {
switch (this.props.recordingState) {
case NOT_YET_KNOWN:
case AVAILABLE_TO_RECORD:
case REQUEST_TO_STOP_PROFILER:
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
case LOCKED_BY_PRIVATE_BROWSING:
case OTHER_IS_RECORDING:
case "not-yet-known":
case "available-to-record":
case "request-to-stop-profiler":
case "request-to-get-profile-and-stop-profiler":
case "locked-by-private-browsing":
case "other-is-recording":
// Do nothing for these states.
break;
case RECORDING:
case REQUEST_TO_START_RECORDING:
case "recording":
case "request-to-start-recording":
this.props.perfFront.stopProfilerAndDiscardProfile();
break;
@ -149,34 +137,34 @@ class Perf extends PureComponent {
handleProfilerStarting() {
const { changeRecordingState, recordingState, isPopup } = this.props;
switch (recordingState) {
case NOT_YET_KNOWN:
case "not-yet-known":
// We couldn't have started it yet, so it must have been someone
// else. (fallthrough)
case AVAILABLE_TO_RECORD:
case "available-to-record":
// We aren't recording, someone else started it up. (fallthrough)
case REQUEST_TO_STOP_PROFILER:
case "request-to-stop-profiler":
// We requested to stop the profiler, but someone else already started
// it up. (fallthrough)
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
case "request-to-get-profile-and-stop-profiler":
if (isPopup) {
// The profiler popup doesn't care who is recording. It will take control
// of it.
changeRecordingState(RECORDING);
changeRecordingState("recording");
} else {
// Someone re-started the profiler while we were asking for the completed
// profile.
changeRecordingState(OTHER_IS_RECORDING);
changeRecordingState("other-is-recording");
}
break;
case REQUEST_TO_START_RECORDING:
case "request-to-start-recording":
// Wait for the profiler to tell us that it has started.
changeRecordingState(RECORDING);
changeRecordingState("recording");
break;
case LOCKED_BY_PRIVATE_BROWSING:
case OTHER_IS_RECORDING:
case RECORDING:
case "locked-by-private-browsing":
case "other-is-recording":
case "recording":
// These state cases don't make sense to happen, and means we have a logical
// fallacy somewhere.
throw new Error(
@ -191,27 +179,27 @@ class Perf extends PureComponent {
handleProfilerStopping() {
const { changeRecordingState, recordingState } = this.props;
switch (recordingState) {
case NOT_YET_KNOWN:
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
case REQUEST_TO_STOP_PROFILER:
case OTHER_IS_RECORDING:
changeRecordingState(AVAILABLE_TO_RECORD);
case "not-yet-known":
case "request-to-get-profile-and-stop-profiler":
case "request-to-stop-profiler":
case "other-is-recording":
changeRecordingState("available-to-record");
break;
case REQUEST_TO_START_RECORDING:
case "request-to-start-recording":
// Highly unlikely, but someone stopped the recorder, this is fine.
// Do nothing (fallthrough).
case LOCKED_BY_PRIVATE_BROWSING:
case "locked-by-private-browsing":
// The profiler is already locked, so we know about this already.
break;
case RECORDING:
changeRecordingState(AVAILABLE_TO_RECORD, {
case "recording":
changeRecordingState("available-to-record", {
didRecordingUnexpectedlyStopped: true,
});
break;
case AVAILABLE_TO_RECORD:
case "available-to-record":
throw new Error(
"The profiler stopped recording, when it shouldn't have been able to."
);
@ -224,24 +212,24 @@ class Perf extends PureComponent {
const { recordingState, changeRecordingState } = this.props;
switch (recordingState) {
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
case "request-to-get-profile-and-stop-profiler":
// This one is a tricky case. Go ahead and act like nothing went wrong, maybe
// it will resolve correctly? (fallthrough)
case REQUEST_TO_STOP_PROFILER:
case AVAILABLE_TO_RECORD:
case OTHER_IS_RECORDING:
case NOT_YET_KNOWN:
changeRecordingState(LOCKED_BY_PRIVATE_BROWSING);
case "request-to-stop-profiler":
case "available-to-record":
case "other-is-recording":
case "not-yet-known":
changeRecordingState("locked-by-private-browsing");
break;
case REQUEST_TO_START_RECORDING:
case RECORDING:
changeRecordingState(LOCKED_BY_PRIVATE_BROWSING, {
case "request-to-start-recording":
case "recording":
changeRecordingState("locked-by-private-browsing", {
didRecordingUnexpectedlyStopped: false,
});
break;
case LOCKED_BY_PRIVATE_BROWSING:
case "locked-by-private-browsing":
// Do nothing
break;
@ -253,7 +241,7 @@ class Perf extends PureComponent {
handlePrivateBrowsingEnding() {
// No matter the state, go ahead and set this as ready to record. This should
// be the only logical state to go into.
this.props.changeRecordingState(AVAILABLE_TO_RECORD);
this.props.changeRecordingState("available-to-record");
}
render() {

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

@ -13,18 +13,6 @@ const {
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const actions = require("devtools/client/performance-new/store/actions");
const {
recordingState: {
NOT_YET_KNOWN,
AVAILABLE_TO_RECORD,
REQUEST_TO_START_RECORDING,
REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER,
REQUEST_TO_STOP_PROFILER,
RECORDING,
OTHER_IS_RECORDING,
LOCKED_BY_PRIVATE_BROWSING,
},
} = require("devtools/client/performance-new/utils");
const selectors = require("devtools/client/performance-new/store/selectors");
/**
@ -124,10 +112,10 @@ class RecordingButton extends PureComponent {
// TODO - L10N all of the messages. Bug 1418056
switch (recordingState) {
case NOT_YET_KNOWN:
case "not-yet-known":
return null;
case AVAILABLE_TO_RECORD:
case "available-to-record":
return this.renderButton({
onClick: startRecording,
label: span(
@ -143,39 +131,39 @@ class RecordingButton extends PureComponent {
: null,
});
case REQUEST_TO_STOP_PROFILER:
case "request-to-stop-profiler":
return this.renderButton({
label: "Stopping recording",
disabled: true,
});
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
case "request-to-get-profile-and-stop-profiler":
return this.renderButton({
label: "Capturing profile",
disabled: true,
});
case REQUEST_TO_START_RECORDING:
case RECORDING:
case "request-to-start-recording":
case "recording":
return this.renderButton({
label: "Capture recording",
isPrimary: true,
onClick: this._getProfileAndStopProfiler,
disabled: recordingState === REQUEST_TO_START_RECORDING,
disabled: recordingState === "request-to-start-recording",
additionalButton: {
label: "Cancel recording",
onClick: stopProfilerAndDiscardProfile,
},
});
case OTHER_IS_RECORDING:
case "other-is-recording":
return this.renderButton({
label: "Stop and discard the other recording",
onClick: stopProfilerAndDiscardProfile,
additionalMessage: "Another tool is currently recording.",
});
case LOCKED_BY_PRIVATE_BROWSING:
case "locked-by-private-browsing":
return this.renderButton({
label: span(
null,

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

@ -0,0 +1,12 @@
{
"name": "devtools-bin",
"version": "1.0.0",
"scripts": {
"test": "tsc",
"test-ci": "tsc"
},
"license": "MPL-2.0",
"devDependencies": {
"typescript": "^3.6.3"
}
}

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

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// @ts-ignore - No support yet for lazyRequireGetter.
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
/**
@ -12,17 +13,37 @@ loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
* with wiring this panel into the rest of DevTools and fetching the Actor's fronts.
*/
/**
* @typedef {import("./types").PanelWindow} PanelWindow
* @typedef {import("./types").Toolbox} Toolbox
* @typedef {import("./types").Target} Target
*/
class PerformancePanel {
/**
* @param {PanelWindow} iframeWindow
* @param {Toolbox} toolbox
*/
constructor(iframeWindow, toolbox) {
this.panelWin = iframeWindow;
this.toolbox = toolbox;
// @ts-ignore - No support yet for lazyRequireGetter.
EventEmitter.decorate(this);
}
/**
* This is implemented (and overwritten) by the EventEmitter. Is there a way
* to use mixins with JSDoc?
*
* @param {string} eventName
*/
emit(eventName) {}
/**
* Open is effectively an asynchronous constructor.
* @return {Promise} Resolves when the Perf tool completes opening.
* @return {Promise<PerformancePanel>} Resolves when the Perf tool completes
* opening.
*/
open() {
if (!this._opening) {
@ -31,6 +52,10 @@ class PerformancePanel {
return this._opening;
}
/**
* This function is the actual implementation of the open() method.
* @returns Promise<PerformancePanel>
*/
async _doOpen() {
this.panelWin.gToolbox = this.toolbox;
this.panelWin.gTarget = this.target;
@ -48,6 +73,9 @@ class PerformancePanel {
// DevToolPanel API:
/**
* @returns {Target} target
*/
get target() {
return this.toolbox.target;
}
@ -62,4 +90,5 @@ class PerformancePanel {
this._destroyed = true;
}
}
exports.PerformancePanel = PerformancePanel;

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

@ -4,19 +4,25 @@
"use strict";
const selectors = require("devtools/client/performance-new/store/selectors");
const {
recordingState: {
AVAILABLE_TO_RECORD,
REQUEST_TO_START_RECORDING,
REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER,
REQUEST_TO_STOP_PROFILER,
},
} = require("devtools/client/performance-new/utils");
/**
* @typedef {import("../types").Action} Action
* @typedef {import("../types").Library} Library
* @typedef {import("../types").PerfFront} PerfFront
* @typedef {import("../types").SymbolTableAsTuple} SymbolTableAsTuple
* @typedef {import("../types").RecordingState} RecordingState
*/
/**
* @template T
* @typedef {import("../types").ThunkAction<T>} ThunkAction<T>
*
/**
* The recording state manages the current state of the recording panel.
* @param {string} state - A valid state in `recordingState`.
* @param {object} options
* @param {RecordingState} state - A valid state in `recordingState`.
* @param {{ didRecordingUnexpectedlyStopped: boolean }} options
* @return {Action}
*/
const changeRecordingState = (exports.changeRecordingState = (
state,
@ -31,7 +37,8 @@ const changeRecordingState = (exports.changeRecordingState = (
* This is the result of the initial questions about the state of the profiler.
*
* @param {boolean} isSupportedPlatform - This is a supported platform.
* @param {string} recordingState - A valid state in `recordingState`.
* @param {RecordingState} recordingState - A valid state in `recordingState`.
* @return {Action}
*/
exports.reportProfilerReady = (isSupportedPlatform, recordingState) => ({
type: "REPORT_PROFILER_READY",
@ -41,7 +48,8 @@ exports.reportProfilerReady = (isSupportedPlatform, recordingState) => ({
/**
* Dispatch the given action, and then update the recording settings.
* @param {object} action
* @param {Action} action
* @return {ThunkAction<void>}
*/
function _dispatchAndUpdatePreferences(action) {
return (dispatch, getState) => {
@ -63,6 +71,7 @@ function _dispatchAndUpdatePreferences(action) {
/**
* Updates the recording settings for the interval.
* @param {number} interval
* @return {ThunkAction<void>}
*/
exports.changeInterval = interval =>
_dispatchAndUpdatePreferences({
@ -73,6 +82,7 @@ exports.changeInterval = interval =>
/**
* Updates the recording settings for the entries.
* @param {number} entries
* @return {ThunkAction<void>}
*/
exports.changeEntries = entries =>
_dispatchAndUpdatePreferences({
@ -82,7 +92,8 @@ exports.changeEntries = entries =>
/**
* Updates the recording settings for the features.
* @param {object} features
* @param {Object} features
* @return {ThunkAction<void>}
*/
exports.changeFeatures = features =>
_dispatchAndUpdatePreferences({
@ -92,7 +103,8 @@ exports.changeFeatures = features =>
/**
* Updates the recording settings for the threads.
* @param {array} threads
* @param {string[]} threads
* @return {ThunkAction<void>}
*/
exports.changeThreads = threads =>
_dispatchAndUpdatePreferences({
@ -102,7 +114,8 @@ exports.changeThreads = threads =>
/**
* Updates the recording settings for the objdirs.
* @param {array} objdirs
* @param {string[]} objdirs
* @return {ThunkAction<void>}
*/
exports.changeObjdirs = objdirs =>
_dispatchAndUpdatePreferences({
@ -113,7 +126,8 @@ exports.changeObjdirs = objdirs =>
/**
* Receive the values to intialize the store. See the reducer for what values
* are expected.
* @param {object} threads
* @param {object} values
* @return {ThunkAction<void>}
*/
exports.initializeStore = values => ({
type: "INITIALIZE_STORE",
@ -122,6 +136,7 @@ exports.initializeStore = values => ({
/**
* Start a new recording with the perfFront and update the internal recording state.
* @return {ThunkAction<void>}
*/
exports.startRecording = () => {
return (dispatch, getState) => {
@ -130,7 +145,7 @@ exports.startRecording = () => {
// In the case of the profiler popup, the startProfiler can be synchronous.
// In order to properly allow the React components to handle the state changes
// make sure and change the recording state first, then start the profiler.
dispatch(changeRecordingState(REQUEST_TO_START_RECORDING));
dispatch(changeRecordingState("request-to-start-recording"));
perfFront.startProfiler(recordingSettings);
};
};
@ -138,11 +153,12 @@ exports.startRecording = () => {
/**
* Stops the profiler, and opens the profile in a new window.
* @param {object} window - The current window for the page.
* @return {ThunkAction<void>}
*/
exports.getProfileAndStopProfiler = window => {
return async (dispatch, getState) => {
const perfFront = selectors.getPerfFront(getState());
dispatch(changeRecordingState(REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER));
dispatch(changeRecordingState("request-to-get-profile-and-stop-profiler"));
const profile = await perfFront.getProfileAndStopProfiler();
if (window.gClosePopup) {
@ -152,20 +168,19 @@ exports.getProfileAndStopProfiler = window => {
const getSymbolTable = selectors.getSymbolTableGetter(getState())(profile);
const receiveProfile = selectors.getReceiveProfileFn(getState());
receiveProfile(profile, getSymbolTable);
dispatch(changeRecordingState(AVAILABLE_TO_RECORD));
dispatch(changeRecordingState("available-to-record"));
};
};
/**
* Stops the profiler, but does not try to retrieve the profile.
* @return {ThunkAction<void>}
*/
exports.stopProfilerAndDiscardProfile = () => {
return async (dispatch, getState) => {
const perfFront = selectors.getPerfFront(getState());
dispatch(changeRecordingState(REQUEST_TO_STOP_PROFILER));
dispatch(changeRecordingState("request-to-stop-profiler"));
perfFront.stopProfilerAndDiscardProfile();
};
};

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

@ -2,16 +2,26 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { combineReducers } = require("devtools/client/shared/vendor/redux");
const {
recordingState: { NOT_YET_KNOWN },
} = require("devtools/client/performance-new/utils");
/**
* @typedef {import("../types").Action} Action
* @typedef {import("../types").State} State
* @typedef {import("../types").RecordingState} RecordingState
* @typedef {import("../types").InitializedValues} InitializedValues
*/
/**
* @template S
* @typedef {import("../types").Reducer<S>} Reducer<S>
*/
/**
* The current state of the recording.
* @param state - A recordingState key.
* @type {Reducer<RecordingState>}
*/
function recordingState(state = NOT_YET_KNOWN, action) {
function recordingState(state = "not-yet-known", action) {
switch (action.type) {
case "CHANGE_RECORDING_STATE":
return action.state;
@ -25,7 +35,7 @@ function recordingState(state = NOT_YET_KNOWN, action) {
/**
* Whether or not the recording state unexpectedly stopped. This allows
* the UI to display a helpful message.
* @param {boolean} state
* @type {Reducer<boolean>}
*/
function recordingUnexpectedlyStopped(state = false, action) {
switch (action.type) {
@ -39,7 +49,7 @@ function recordingUnexpectedlyStopped(state = false, action) {
/**
* The profiler needs to be queried asynchronously on whether or not
* it supports the user's platform.
* @param {boolean | null} state
* @type {Reducer<boolean | null>}
*/
function isSupportedPlatform(state = null, action) {
switch (action.type) {
@ -55,7 +65,7 @@ function isSupportedPlatform(state = null, action) {
/**
* The setting for the recording interval. Defaults to 1ms.
* @param {number} state
* @type {Reducer<number>}
*/
function interval(state = 1000, action) {
switch (action.type) {
@ -70,7 +80,7 @@ function interval(state = 1000, action) {
/**
* The number of entries in the profiler's circular buffer. Defaults to 90mb.
* @param {number} state
* @type {Reducer<number>}
*/
function entries(state = 10000000, action) {
switch (action.type) {
@ -85,7 +95,7 @@ function entries(state = 10000000, action) {
/**
* The features that are enabled for the profiler.
* @param {array} state
* @type {Reducer<string[]>}
*/
function features(state = ["js", "stackwalk", "responsiveness"], action) {
switch (action.type) {
@ -100,7 +110,7 @@ function features(state = ["js", "stackwalk", "responsiveness"], action) {
/**
* The current threads list.
* @param {array of strings} state
* @type {Reducer<string[]>}
*/
function threads(state = ["GeckoMain", "Compositor"], action) {
switch (action.type) {
@ -115,7 +125,7 @@ function threads(state = ["GeckoMain", "Compositor"], action) {
/**
* The current objdirs list.
* @param {array of strings} state
* @type {Reducer<string[]>}
*/
function objdirs(state = [], action) {
switch (action.type) {
@ -129,18 +139,10 @@ function objdirs(state = [], action) {
}
/**
* These are all the values used to initialize the profiler. They should never change
* once added to the store.
* These are all the values used to initialize the profiler. They should never
* change once added to the store.
*
* state = {
* toolbox - The current toolbox.
* perfFront - The current Front to the Perf actor.
* receiveProfile - A function to receive the profile and open it into a new window.
* setRecordingPreferences - A function to set the recording settings.
* isPopup - A boolean value that sets lets the UI know if it is in the popup window
* or inside of devtools.
* getSymbolTableGetter - Run this function to get the getSymbolTable function.
* }
* @type {Reducer<InitializedValues | null>}
*/
function initializedValues(state = null, action) {
switch (action.type) {
@ -157,7 +159,14 @@ function initializedValues(state = null, action) {
}
}
/**
* The main reducer for the performance-new client.
* @type {Reducer<State>}
*/
module.exports = combineReducers({
// TODO - The object going into `combineReducers` is not currently type checked
// as being correct for. For instance, recordingState here could be removed, or
// not return the right state, and TypeScript will not create an error.
recordingState,
recordingUnexpectedlyStopped,
isSupportedPlatform,

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

@ -3,17 +3,54 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* @typedef {import("../types").RecordingState} RecordingState
* @typedef {import("../types").RecordingStateFromPreferences} RecordingStateFromPreferences
* @typedef {import("../types").InitializedValues} InitializedValues
* @typedef {import("../types").PerfFront} PerfFront
* @typedef {import("../types").ReceiveProfile} ReceiveProfile
* @typedef {import("../types").SetRecordingPreferences} SetRecordingPreferences
* @typedef {import("../types").GetSymbolTableCallback} GetSymbolTableCallback
*/
/**
* @template S
* @typedef {import("../types").Selector<S>} Selector<S>
*/
/** @type {Selector<RecordingState>} */
const getRecordingState = state => state.recordingState;
/** @type {Selector<boolean>} */
const getRecordingUnexpectedlyStopped = state =>
state.recordingUnexpectedlyStopped;
/** @type {Selector<boolean>} */
const getIsSupportedPlatform = state => state.isSupportedPlatform;
/** @type {Selector<number>} */
const getInterval = state => state.interval;
/** @type {Selector<number>} */
const getEntries = state => state.entries;
/** @type {Selector<string[]>} */
const getFeatures = state => state.features;
/** @type {Selector<string[]>} */
const getThreads = state => state.threads;
/** @type {Selector<string>} */
const getThreadsString = state => getThreads(state).join(",");
/** @type {Selector<string[]>} */
const getObjdirs = state => state.objdirs;
/**
* Warning! This function returns a new object on every run, and so should not
* be used directly as a React prop.
*
* @type {Selector<RecordingStateFromPreferences>}
*/
const getRecordingSettings = state => {
return {
entries: getEntries(state),
@ -24,6 +61,7 @@ const getRecordingSettings = state => {
};
};
/** @type {Selector<InitializedValues>} */
const getInitializedValues = state => {
const values = state.initializedValues;
if (!values) {
@ -32,11 +70,20 @@ const getInitializedValues = state => {
return values;
};
/** @type {Selector<PerfFront>} */
const getPerfFront = state => getInitializedValues(state).perfFront;
/** @type {Selector<ReceiveProfile>} */
const getReceiveProfileFn = state => getInitializedValues(state).receiveProfile;
/** @type {Selector<SetRecordingPreferences>} */
const getSetRecordingPreferencesFn = state =>
getInitializedValues(state).setRecordingPreferences;
/** @type {Selector<boolean>} */
const getIsPopup = state => getInitializedValues(state).isPopup;
/** @type {Selector<(profile: Object) => GetSymbolTableCallback>} */
const getSymbolTableGetter = state =>
getInitializedValues(state).getSymbolTableGetter;

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

@ -0,0 +1,20 @@
{
"compilerOptions": {
"module": "commonjs",
// Set the baseUrl to the root of the project.
"baseUrl": "../../..",
"paths": {},
"strict": true,
"allowJs": true,
"checkJs": true,
"noEmit": true,
"target": "esnext",
"lib": ["esnext", "dom"]
},
// For now, manually include typed files.
"include": [
"utils.js",
"panel.js",
"store"
]
}

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

@ -0,0 +1,199 @@
/* 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/. */
import {
Reducer as ReduxReducer,
Store as ReduxStore,
} from "devtools/client/shared/vendor/redux";
export interface PanelWindow extends Window {
gToolbox?: any;
gTarget?: any;
gInit(perfFront: any, preferenceFront: any): void;
gDestroy(): void;
}
/**
* TODO
*/
export interface Target {
// TODO
client: any;
}
/**
* TODO
*/
export interface Toolbox {
target: Target;
}
/**
* TODO
*/
export interface PerfFront {
startProfiler: any;
getProfileAndStopProfiler: any;
stopProfilerAndDiscardProfile: any;
getSymbolTable: any;
isActive: any;
isSupportedPlatform: any;
isLockedForPrivateBrowsing: any;
}
/**
* TODO
*/
export interface PreferenceFront {}
export type RecordingState =
// The initial state before we've queried the PerfActor
| "not-yet-known"
// The profiler is available, we haven't started recording yet.
| "available-to-record"
// An async request has been sent to start the profiler.
| "request-to-start-recording"
// An async request has been sent to get the profile and stop the profiler.
| "request-to-get-profile-and-stop-profiler"
// An async request has been sent to stop the profiler.
| "request-to-stop-profiler"
// The profiler notified us that our request to start it actually started
// it.
| "recording"
// Some other code with access to the profiler started it.
| "other-is-recording"
// Profiling is not available when in private browsing mode.
| "locked-by-private-browsing";
export interface State {
recordingState: RecordingState;
recordingUnexpectedlyStopped: boolean;
isSupportedPlatform: boolean;
interval: number;
entries: number;
features: string[];
threads: string[];
objdirs: string[];
initializedValues: InitializedValues | null;
}
export type Selector<T> = (state: State) => T;
export type ThunkDispatch = <Returns>(action: ThunkAction<Returns>) => Returns;
export type PlainDispatch = (action: Action) => Action;
export type GetState = () => State;
export type SymbolTableAsTuple = [Uint32Array, Uint32Array, Uint8Array];
/**
* The `dispatch` function can accept either a plain action or a thunk action.
* This is similar to a type `(action: Action | ThunkAction) => any` except this
* allows to type the return value as well.
*/
export type Dispatch = PlainDispatch & ThunkDispatch;
export type ThunkAction<Returns> = (
dispatch: Dispatch,
getState: GetState
) => Returns;
export interface Library {
start: number;
end: number;
offset: number;
name: string;
path: string;
debugName: string;
debugPath: string;
breakpadId: string;
arch: string;
}
export interface GeckoProfile {
// Only type properties that we rely on.
}
export type GetSymbolTableCallback = (
debugName: string,
breakpadId: string
) => Promise<SymbolTableAsTuple>;
export type ReceiveProfile = (
geckoProfile: GeckoProfile,
getSymbolTableCallback: GetSymbolTableCallback
) => void;
export type SetRecordingPreferences = (settings: Object) => void;
export interface RecordingStateFromPreferences {
entries: number;
interval: number;
features: string[];
threads: string[];
objdirs: string[];
}
/**
* A Redux Reducer that knows about the performance-new client's Actions.
*/
export type Reducer<S> = (state: S | undefined, action: Action) => S;
export interface InitializedValues {
// The current Front to the Perf actor.
perfFront: PerfFront;
// A function to receive the profile and open it into a new window.
receiveProfile: ReceiveProfile;
// A function to set the recording settings.
setRecordingPreferences: SetRecordingPreferences;
// A boolean value that sets lets the UI know if it is in the popup window
// or inside of devtools.
isPopup: boolean;
// The popup and devtools panel use different codepaths for getting symbol tables.
getSymbolTableGetter: (profile: Object) => GetSymbolTableCallback;
}
/**
* Export a store that is opinionated about our State definition, and the union
* of all Actions, as well as specific Dispatch behavior.
*/
export type Store = ReduxStore<State, Action>;
export type Action =
| {
type: "CHANGE_RECORDING_STATE";
state: RecordingState;
didRecordingUnexpectedlyStopped: boolean;
}
| {
type: "REPORT_PROFILER_READY";
isSupportedPlatform: boolean;
recordingState: RecordingState;
}
| {
type: "CHANGE_INTERVAL";
interval: number;
}
| {
type: "CHANGE_ENTRIES";
entries: number;
}
| {
type: "CHANGE_FEATURES";
features: string[];
}
| {
type: "CHANGE_THREADS";
threads: string[];
}
| {
type: "CHANGE_OBJDIRS";
objdirs: string[];
}
| {
type: "INITIALIZE_STORE";
perfFront: PerfFront;
receiveProfile: ReceiveProfile;
setRecordingPreferences: SetRecordingPreferences;
isPopup: boolean;
recordingSettingsFromPreferences: RecordingStateFromPreferences;
getSymbolTableGetter: (profile: Object) => GetSymbolTableCallback;
};

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

@ -3,37 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// @ts-ignore
const { OS } = require("resource://gre/modules/osfile.jsm");
const recordingState = {
// The initial state before we've queried the PerfActor
NOT_YET_KNOWN: "not-yet-known",
// The profiler is available, we haven't started recording yet.
AVAILABLE_TO_RECORD: "available-to-record",
// An async request has been sent to start the profiler.
REQUEST_TO_START_RECORDING: "request-to-start-recording",
// An async request has been sent to get the profile and stop the profiler.
REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
"request-to-get-profile-and-stop-profiler",
// An async request has been sent to stop the profiler.
REQUEST_TO_STOP_PROFILER: "request-to-stop-profiler",
// The profiler notified us that our request to start it actually started it.
RECORDING: "recording",
// Some other code with access to the profiler started it.
OTHER_IS_RECORDING: "other-is-recording",
// Profiling is not available when in private browsing mode.
LOCKED_BY_PRIVATE_BROWSING: "locked-by-private-browsing",
};
const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
/**
* Linearly interpolate between values.
* https://en.wikipedia.org/wiki/Linear_interpolation
*
* @param {number} frac - Value ranged 0 - 1 to interpolate between the range
* start and range end.
* @param {number} rangeState - The value to start from.
* @param {number} frac - Value ranged 0 - 1 to interpolate between the range start and range end.
* @param {number} rangeStart - The value to start from.
* @param {number} rangeEnd - The value to interpolate to.
* @returns {number}
*/
@ -46,6 +26,7 @@ function lerp(frac, rangeStart, rangeEnd) {
*
* @param {number} val - The value to clamp.
* @param {number} min - The minimum value.
* @param {number} max - The max value.
* @returns {number}
*/
function clamp(val, min, max) {
@ -82,22 +63,43 @@ function formatFileSize(num) {
return (neg ? "-" : "") + numStr + " " + unit;
}
/**
* Scale a number value.
*
* @callback NumberScaler
* @param {number} value
* @returns {number}
*/
/**
* Creates numbers that scale exponentially.
*
* @param {number} rangeStart
* @param {number} rangeEnd
*
* @returns {{
* fromFractionToValue: NumberScaler,
* fromValueToFraction: NumberScaler,
* fromFractionToSingleDigitValue: NumberScaler,
* }}
*/
function makeExponentialScale(rangeStart, rangeEnd) {
const startExp = Math.log(rangeStart);
const endExp = Math.log(rangeEnd);
/** @type {NumberScaler} */
const fromFractionToValue = frac =>
Math.exp((1 - frac) * startExp + frac * endExp);
/** @type {NumberScaler} */
const fromValueToFraction = value =>
(Math.log(value) - startExp) / (endExp - startExp);
/** @type {NumberScaler} */
const fromFractionToSingleDigitValue = frac => {
return +fromFractionToValue(frac).toPrecision(1);
};
return {
// Takes a number ranged 0-1 and returns it within the range.
fromFractionToValue,
@ -137,7 +139,7 @@ function scaleRangeWithClamping(
* Use some heuristics to guess at the overhead of the recording settings.
* @param {number} interval
* @param {number} bufferSize
* @param {array} features - List of the selected features.
* @param {string[]} features - List of the selected features.
*/
function calculateOverhead(interval, bufferSize, features) {
const overheadFromSampling =
@ -195,8 +197,9 @@ function calculateOverhead(interval, bufferSize, features) {
* This makes some lists look a little nicer. For example, this turns the list
* ["/Users/foo/code/obj-m-android-opt", "/Users/foo/code/obj-m-android-debug"]
* into the list ["obj-m-android-opt", "obj-m-android-debug"].
* @param {array of string} pathArray The array of absolute paths.
* @returns {array of string} A new array with the described adjustment.
*
* @param {string[]} pathArray The array of absolute paths.
* @returns {string[]} A new array with the described adjustment.
*/
function withCommonPathPrefixRemoved(pathArray) {
if (pathArray.length === 0) {
@ -251,6 +254,5 @@ module.exports = {
makeExponentialScale,
scaleRangeWithClamping,
calculateOverhead,
recordingState,
withCommonPathPrefixRemoved,
};

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

@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
typescript@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==

550
devtools/client/shared/vendor/redux.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,550 @@
/**
* This file is taken from the redux node module. To pull a new version:
*
* 1. `npm install redux` somewhere
* 2. Copy `node_modules/redux/index.d.ts`
*/
/**
* An *action* is a plain object that represents an intention to change the
* state. Actions are the only way to get data into the store. Any data,
* whether from UI events, network callbacks, or other sources such as
* WebSockets needs to eventually be dispatched as actions.
*
* Actions must have a `type` field that indicates the type of action being
* performed. Types can be defined as constants and imported from another
* module. It's better to use strings for `type` than Symbols because strings
* are serializable.
*
* Other than `type`, the structure of an action object is really up to you.
* If you're interested, check out Flux Standard Action for recommendations on
* how actions should be constructed.
*
* @template T the type of the action's `type` tag.
*/
export interface Action<T = any> {
type: T;
}
/**
* An Action type which accepts any other properties.
* This is mainly for the use of the `Reducer` type.
* This is not part of `Action` itself to prevent types that extend `Action` from
* having an index signature.
*/
export interface AnyAction extends Action {
// Allows any extra properties to be defined in an action.
[extraProps: string]: any;
}
/* reducers */
/**
* A *reducer* (also called a *reducing function*) is a function that accepts
* an accumulation and a value and returns a new accumulation. They are used
* to reduce a collection of values down to a single value
*
* Reducers are not unique to Reduxthey are a fundamental concept in
* functional programming. Even most non-functional languages, like
* JavaScript, have a built-in API for reducing. In JavaScript, it's
* `Array.prototype.reduce()`.
*
* In Redux, the accumulated value is the state object, and the values being
* accumulated are actions. Reducers calculate a new state given the previous
* state and an action. They must be *pure functions*functions that return
* the exact same output for given inputs. They should also be free of
* side-effects. This is what enables exciting features like hot reloading and
* time travel.
*
* Reducers are the most important concept in Redux.
*
* *Do not put API calls into reducers.*
*
* @template S The type of state consumed and produced by this reducer.
* @template A The type of actions the reducer can potentially respond to.
*/
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S;
/**
* Object whose values correspond to different reducer functions.
*
* @template A The type of actions the reducers can potentially respond to.
*/
export type ReducersMapObject<S = any, A extends Action = Action> = {
[K in keyof S]: Reducer<S[K], A>
};
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @template S Combined state object type.
*
* @param reducers An object whose values correspond to different reducer
* functions that need to be combined into one. One handy way to obtain it
* is to use ES6 `import * as reducers` syntax. The reducers may never
* return undefined for any action. Instead, they should return their
* initial state if the state passed to them was undefined, and the current
* state for any unrecognized action.
*
* @returns A reducer function that invokes every reducer inside the passed
* object, and builds a state object with the same shape.
*/
export function combineReducers<S>(
reducers: ReducersMapObject<S, any>
): Reducer<S>;
export function combineReducers<S, A extends Action = AnyAction>(
reducers: ReducersMapObject<S, A>
): Reducer<S, A>;
/* store */
/**
* A *dispatching function* (or simply *dispatch function*) is a function that
* accepts an action or an async action; it then may or may not dispatch one
* or more actions to the store.
*
* We must distinguish between dispatching functions in general and the base
* `dispatch` function provided by the store instance without any middleware.
*
* The base dispatch function *always* synchronously sends an action to the
* store's reducer, along with the previous state returned by the store, to
* calculate a new state. It expects actions to be plain objects ready to be
* consumed by the reducer.
*
* Middleware wraps the base dispatch function. It allows the dispatch
* function to handle async actions in addition to actions. Middleware may
* transform, delay, ignore, or otherwise interpret actions or async actions
* before passing them to the next middleware.
*
* @template A The type of things (actions or otherwise) which may be
* dispatched.
*/
export interface Dispatch<A extends Action = AnyAction> {
<T extends A>(action: T): T;
}
/**
* Function to remove listener added by `Store.subscribe()`.
*/
export interface Unsubscribe {
(): void;
}
/**
* A store is an object that holds the application's state tree.
* There should only be a single store in a Redux app, as the composition
* happens on the reducer level.
*
* @template S The type of state held by this store.
* @template A the type of actions which may be dispatched by this store.
*/
export interface Store<S = any, A extends Action = AnyAction> {
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will be
* considered the **next** state of the tree, and the change listeners will
* be notified.
*
* The base implementation only supports plain object actions. If you want
* to dispatch a Promise, an Observable, a thunk, or something else, you
* need to wrap your store creating function into the corresponding
* middleware. For example, see the documentation for the `redux-thunk`
* package. Even the middleware will eventually dispatch plain object
* actions using this method.
*
* @param action A plain object representing what changed. It is a good
* idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must
* have a `type` property which may not be `undefined`. It is a good idea
* to use string constants for action types.
*
* @returns For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
dispatch: Dispatch<A>;
/**
* Reads the state tree managed by the store.
*
* @returns The current state tree of your application.
*/
getState(): S;
/**
* Adds a change listener. It will be called any time an action is
* dispatched, and some part of the state tree may potentially have changed.
* You may then call `getState()` to read the current state tree inside the
* callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked,
* this will not have any effect on the `dispatch()` that is currently in
* progress. However, the next `dispatch()` call, whether nested or not,
* will use a more recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all states changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param listener A callback to be invoked on every dispatch.
* @returns A function to remove this change listener.
*/
subscribe(listener: () => void): Unsubscribe;
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param nextReducer The reducer for the store to use instead.
*/
replaceReducer(nextReducer: Reducer<S, A>): void;
}
export type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
};
/**
* A store creator is a function that creates a Redux store. Like with
* dispatching function, we must distinguish the base store creator,
* `createStore(reducer, preloadedState)` exported from the Redux package, from
* store creators that are returned from the store enhancers.
*
* @template S The type of state to be held by the store.
* @template A The type of actions which may be dispatched.
* @template Ext Store extension that is mixed in to the Store type.
* @template StateExt State extension that is mixed into the state type.
*/
export interface StoreCreator {
<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S & StateExt, A> & Ext;
<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
preloadedState?: DeepPartial<S>,
enhancer?: StoreEnhancer<Ext>
): Store<S & StateExt, A> & Ext;
}
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several
* reducers
* into a single reducer function by using `combineReducers`.
*
* @template S State object type.
*
* @param reducer A function that returns the next state tree, given the
* current state tree and the action to handle.
*
* @param [preloadedState] The initial state. You may optionally specify it to
* hydrate the state from the server in universal apps, or to restore a
* previously serialized user session. If you use `combineReducers` to
* produce the root reducer function, this must be an object with the same
* shape as `combineReducers` keys.
*
* @param [enhancer] The store enhancer. You may optionally specify it to
* enhance the store with third-party capabilities such as middleware, time
* travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns A Redux store that lets you read the state, dispatch actions and
* subscribe to changes.
*/
export const createStore: StoreCreator;
/**
* A store enhancer is a higher-order function that composes a store creator
* to return a new, enhanced store creator. This is similar to middleware in
* that it allows you to alter the store interface in a composable way.
*
* Store enhancers are much the same concept as higher-order components in
* React, which are also occasionally called component enhancers.
*
* Because a store is not an instance, but rather a plain-object collection of
* functions, copies can be easily created and modified without mutating the
* original store. There is an example in `compose` documentation
* demonstrating that.
*
* Most likely you'll never write a store enhancer, but you may use the one
* provided by the developer tools. It is what makes time travel possible
* without the app being aware it is happening. Amusingly, the Redux
* middleware implementation is itself a store enhancer.
*
* @template Ext Store extension that is mixed into the Store type.
* @template StateExt State extension that is mixed into the state type.
*/
export type StoreEnhancer<Ext = {}, StateExt = {}> = (
next: StoreEnhancerStoreCreator
) => StoreEnhancerStoreCreator<Ext, StateExt>;
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = {}> = <
S = any,
A extends Action = AnyAction
>(
reducer: Reducer<S, A>,
preloadedState?: DeepPartial<S>
) => Store<S & StateExt, A> & Ext;
/* middleware */
export interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
dispatch: D;
getState(): S;
}
/**
* A middleware is a higher-order function that composes a dispatch function
* to return a new dispatch function. It often turns async actions into
* actions.
*
* Middleware is composable using function composition. It is useful for
* logging actions, performing side effects like routing, or turning an
* asynchronous API call into a series of synchronous actions.
*
* @template DispatchExt Extra Dispatch signature added by this middleware.
* @template S The type of the state supported by this middleware.
* @template D The type of Dispatch of the store where this middleware is
* installed.
*/
export interface Middleware<
DispatchExt = {},
S = any,
D extends Dispatch = Dispatch
> {
(api: MiddlewareAPI<D, S>): (
next: Dispatch<AnyAction>
) => (action: any) => any;
}
/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as
* expressing asynchronous actions in a concise manner, or logging every
* action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState`
* functions as named arguments.
*
* @param middlewares The middleware chain to be applied.
* @returns A store enhancer applying the middleware.
*
* @template Ext Dispatch signature added by a middleware.
* @template S The type of the state supported by a middleware.
*/
export function applyMiddleware(): StoreEnhancer;
export function applyMiddleware<Ext1, S>(
middleware1: Middleware<Ext1, S, any>
): StoreEnhancer<{ dispatch: Ext1 }>;
export function applyMiddleware<Ext1, Ext2, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 }>;
export function applyMiddleware<Ext1, Ext2, Ext3, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>,
middleware3: Middleware<Ext3, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 }>;
export function applyMiddleware<Ext1, Ext2, Ext3, Ext4, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>,
middleware3: Middleware<Ext3, S, any>,
middleware4: Middleware<Ext4, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 }>;
export function applyMiddleware<Ext1, Ext2, Ext3, Ext4, Ext5, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>,
middleware3: Middleware<Ext3, S, any>,
middleware4: Middleware<Ext4, S, any>,
middleware5: Middleware<Ext5, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 & Ext5 }>;
export function applyMiddleware<Ext, S = any>(
...middlewares: Middleware<any, S, any>[]
): StoreEnhancer<{ dispatch: Ext }>;
/* action creators */
/**
* An *action creator* is, quite simply, a function that creates an action. Do
* not confuse the two termsagain, an action is a payload of information, and
* an action creator is a factory that creates an action.
*
* Calling an action creator only produces an action, but does not dispatch
* it. You need to call the store's `dispatch` function to actually cause the
* mutation. Sometimes we say *bound action creators* to mean functions that
* call an action creator and immediately dispatch its result to a specific
* store instance.
*
* If an action creator needs to read the current state, perform an API call,
* or cause a side effect, like a routing transition, it should return an
* async action instead of an action.
*
* @template A Returned action type.
*/
export interface ActionCreator<A> {
(...args: any[]): A;
}
/**
* Object whose values are action creator functions.
*/
export interface ActionCreatorsMapObject<A = any> {
[key: string]: ActionCreator<A>;
}
/**
* Turns an object whose values are action creators, into an object with the
* same keys, but with every function wrapped into a `dispatch` call so they
* may be invoked directly. This is just a convenience method, as you can call
* `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
*
* For convenience, you can also pass a single function as the first argument,
* and get a function in return.
*
* @param actionCreator An object whose values are action creator functions.
* One handy way to obtain it is to use ES6 `import * as` syntax. You may
* also pass a single function.
*
* @param dispatch The `dispatch` function available on your Redux store.
*
* @returns The object mimicking the original object, but with every action
* creator wrapped into the `dispatch` call. If you passed a function as
* `actionCreator`, the return value will also be a single function.
*/
export function bindActionCreators<A, C extends ActionCreator<A>>(
actionCreator: C,
dispatch: Dispatch
): C;
export function bindActionCreators<
A extends ActionCreator<any>,
B extends ActionCreator<any>
>(actionCreator: A, dispatch: Dispatch): B;
export function bindActionCreators<A, M extends ActionCreatorsMapObject<A>>(
actionCreators: M,
dispatch: Dispatch
): M;
export function bindActionCreators<
M extends ActionCreatorsMapObject<any>,
N extends ActionCreatorsMapObject<any>
>(actionCreators: M, dispatch: Dispatch): N;
/* compose */
type Func0<R> = () => R;
type Func1<T1, R> = (a1: T1) => R;
type Func2<T1, T2, R> = (a1: T1, a2: T2) => R;
type Func3<T1, T2, T3, R> = (a1: T1, a2: T2, a3: T3, ...args: any[]) => R;
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for the
* resulting composite function.
*
* @param funcs The functions to compose.
* @returns R function obtained by composing the argument functions from right
* to left. For example, `compose(f, g, h)` is identical to doing
* `(...args) => f(g(h(...args)))`.
*/
export function compose(): <R>(a: R) => R;
export function compose<F extends Function>(f: F): F;
/* two functions */
export function compose<A, R>(f1: (b: A) => R, f2: Func0<A>): Func0<R>;
export function compose<A, T1, R>(
f1: (b: A) => R,
f2: Func1<T1, A>
): Func1<T1, R>;
export function compose<A, T1, T2, R>(
f1: (b: A) => R,
f2: Func2<T1, T2, A>
): Func2<T1, T2, R>;
export function compose<A, T1, T2, T3, R>(
f1: (b: A) => R,
f2: Func3<T1, T2, T3, A>
): Func3<T1, T2, T3, R>;
/* three functions */
export function compose<A, B, R>(
f1: (b: B) => R,
f2: (a: A) => B,
f3: Func0<A>
): Func0<R>;
export function compose<A, B, T1, R>(
f1: (b: B) => R,
f2: (a: A) => B,
f3: Func1<T1, A>
): Func1<T1, R>;
export function compose<A, B, T1, T2, R>(
f1: (b: B) => R,
f2: (a: A) => B,
f3: Func2<T1, T2, A>
): Func2<T1, T2, R>;
export function compose<A, B, T1, T2, T3, R>(
f1: (b: B) => R,
f2: (a: A) => B,
f3: Func3<T1, T2, T3, A>
): Func3<T1, T2, T3, R>;
/* four functions */
export function compose<A, B, C, R>(
f1: (b: C) => R,
f2: (a: B) => C,
f3: (a: A) => B,
f4: Func0<A>
): Func0<R>;
export function compose<A, B, C, T1, R>(
f1: (b: C) => R,
f2: (a: B) => C,
f3: (a: A) => B,
f4: Func1<T1, A>
): Func1<T1, R>;
export function compose<A, B, C, T1, T2, R>(
f1: (b: C) => R,
f2: (a: B) => C,
f3: (a: A) => B,
f4: Func2<T1, T2, A>
): Func2<T1, T2, R>;
export function compose<A, B, C, T1, T2, T3, R>(
f1: (b: C) => R,
f2: (a: B) => C,
f3: (a: A) => B,
f4: Func3<T1, T2, T3, A>
): Func3<T1, T2, T3, R>;
/* rest */
export function compose<R>(
f1: (b: any) => R,
...funcs: Function[]
): (...args: any[]) => R;
export function compose<R>(...funcs: Function[]): (...args: any[]) => R;

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

@ -124,7 +124,7 @@ TEST(PlainTextSerializer, PreformatFlowedQuotes)
test.AssignLiteral(
"<html><body>"
"<span style=\"white-space: pre-wrap;\">"
"<span style=\"white-space: pre-wrap;\" _moz_quote=\"true\">"
"&gt; Firefox Firefox Firefox Firefox <br>"
"&gt; Firefox Firefox Firefox Firefox<br>"
"&gt;<br>"

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

@ -0,0 +1,24 @@
// |jit-test| skip-if: !this.hasOwnProperty("TypedObject")
function f(obj, x, y) {
// The assignment mustn't use the same registers as |x| or |y| for temporaries.
obj.foo = y;
// Ensure the assignment didn't clobber any registers used by either |x| or |y|.
assertEq(x, 0.1);
assertEq(y, 0.2);
}
// Different struct types to ensure we emit a SetProp IC.
var objects = [
new new TypedObject.StructType({foo: TypedObject.float64}),
new new TypedObject.StructType({foo: TypedObject.float32}),
];
// Load from an array to ensure we don't mark the values as constant.
var xs = [0.1, 0.1];
var ys = [0.2, 0.2];
for (var i = 0; i < 100; ++i) {
f(objects[i & 1], xs[i & 1], ys[i & 1]);
}

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

@ -0,0 +1,11 @@
var err = 0;
for (let x of [0, Object.create(null)]) {
try {
(new Int8Array)[1] = x;
} catch (e) {
err += 1;
}
}
assertEq(err, 1);

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

@ -0,0 +1,15 @@
// Different typed array types to ensure we emit a SetProp IC.
var xs = [
new Float32Array(10),
new Float64Array(10),
];
for (var i = 0; i < 100; ++i) {
var ta = xs[i & 1];
// Store with constant rhs.
ta[0] = 0.1;
}
assertEq(xs[0][0], Math.fround(0.1));
assertEq(xs[1][0], 0.1);

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

@ -0,0 +1,17 @@
// Different typed array types to ensure we emit a SetProp IC.
var xs = [
new Float32Array(10),
new Float64Array(10),
];
for (var i = 0; i < 100; ++i) {
var ta = xs[i & 1];
var v = +ta[0];
// Store with payload-register rhs.
ta[0] = ~v;
}
assertEq(xs[0][0], 0);
assertEq(xs[1][0], 0);

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

@ -0,0 +1,20 @@
// Different typed array types to ensure we emit a SetProp IC.
var xs = [
new Float32Array(10),
new Float64Array(10),
];
function f(ta) {
for (var k = 0;;) {
// Store with payload-stack rhs.
ta[k] = k;
break;
}
}
for (var i = 0; i < 100; ++i) {
f(xs[i & 1]);
}
assertEq(xs[0][0], 0);
assertEq(xs[1][0], 0);

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

@ -0,0 +1,38 @@
var g = newGlobal({newCompartment: true});
var dbg = new Debugger(g);
g.eval(`
function f() {
// |this| in arrow-functions refers to the |this| binding in outer functions.
// So when |frame.eval("this")| is executed, the outer |this| binding should
// be returned, unless it has been optimised out.
(() => {})();
// Ensure a |this| binding is created for |f|.
return this;
}
`);
var errors = [];
function enterFrame(frame) {
// Disable the handler so we don't call it recursively through |frame.eval|.
dbg.onEnterFrame = undefined;
// Store the error when resolving |this| was unsuccessful.
var r = frame.eval("this");
if (r.throw) {
errors.push(r.throw);
}
// Re-enable the handler.
dbg.onEnterFrame = enterFrame;
};
dbg.onEnterFrame = enterFrame;
g.f();
assertEq(errors.length, 1);
assertEq(errors[0].unsafeDereference().toString(),
"Error: variable `this' has been optimized out");

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

@ -1081,30 +1081,6 @@ bool BaselineCacheIRCompiler::emitStoreTypedObjectReferenceProperty() {
return true;
}
bool BaselineCacheIRCompiler::emitStoreTypedObjectScalarProperty() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
Register obj = allocator.useRegister(masm, reader.objOperandId());
Address offsetAddr = stubAddress(reader.stubOffset());
TypedThingLayout layout = reader.typedThingLayout();
Scalar::Type type = reader.scalarType();
ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
AutoScratchRegister scratch1(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
// Compute the address being written to.
LoadTypedThingData(masm, layout, obj, scratch1);
masm.addPtr(offsetAddr, scratch1);
Address dest(scratch1, 0);
StoreToTypedArray(cx_, masm, type, val, dest, scratch2, failure->label());
return true;
}
bool BaselineCacheIRCompiler::emitStoreDenseElement() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
ObjOperandId objId = reader.objOperandId();
@ -1445,57 +1421,6 @@ bool BaselineCacheIRCompiler::emitArrayPush() {
return true;
}
bool BaselineCacheIRCompiler::emitStoreTypedElement() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
Register obj = allocator.useRegister(masm, reader.objOperandId());
Register index = allocator.useRegister(masm, reader.int32OperandId());
ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
TypedThingLayout layout = reader.typedThingLayout();
Scalar::Type type = reader.scalarType();
bool handleOOB = reader.readBool();
AutoScratchRegister scratch1(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
// Bounds check.
Label done;
LoadTypedThingLength(masm, layout, obj, scratch1);
// Unfortunately we don't have more registers available on x86, so use
// InvalidReg and emit slightly slower code on x86.
Register spectreTemp = InvalidReg;
masm.spectreBoundsCheck32(index, scratch1, spectreTemp,
handleOOB ? &done : failure->label());
// Load the elements vector.
LoadTypedThingData(masm, layout, obj, scratch1);
BaseIndex dest(scratch1, index, ScaleFromElemWidth(Scalar::byteSize(type)));
// Use ICStubReg as second scratch register. TODO: consider doing the RHS
// type check/conversion as a separate IR instruction so we can simplify
// this.
Register scratch2 = ICStubReg;
masm.push(scratch2);
Label fail;
StoreToTypedArray(cx_, masm, type, val, dest, scratch2, &fail);
masm.pop(scratch2);
masm.jump(&done);
masm.bind(&fail);
masm.pop(scratch2);
masm.jump(failure->label());
masm.bind(&done);
return true;
}
bool BaselineCacheIRCompiler::emitCallNativeSetter() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
Register obj = allocator.useRegister(masm, reader.objOperandId());

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

@ -2249,74 +2249,6 @@ bool FallbackICCodeCompiler::emit_SetElem() {
return tailCallVM<Fn, DoSetElemFallback>(masm);
}
template <typename T>
void StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
const ValueOperand& value, const T& dest,
Register scratch, Label* failure) {
Label done;
if (type == Scalar::Float32 || type == Scalar::Float64) {
masm.ensureDouble(value, FloatReg0, failure);
if (type == Scalar::Float32) {
ScratchFloat32Scope fpscratch(masm);
masm.convertDoubleToFloat32(FloatReg0, fpscratch);
masm.storeToTypedFloatArray(type, fpscratch, dest);
} else {
masm.storeToTypedFloatArray(type, FloatReg0, dest);
}
} else if (type == Scalar::Uint8Clamped) {
Label notInt32;
masm.branchTestInt32(Assembler::NotEqual, value, &notInt32);
masm.unboxInt32(value, scratch);
masm.clampIntToUint8(scratch);
Label clamped;
masm.bind(&clamped);
masm.storeToTypedIntArray(type, scratch, dest);
masm.jump(&done);
// If the value is a double, clamp to uint8 and jump back.
// Else, jump to failure.
masm.bind(&notInt32);
masm.branchTestDouble(Assembler::NotEqual, value, failure);
masm.unboxDouble(value, FloatReg0);
masm.clampDoubleToUint8(FloatReg0, scratch);
masm.jump(&clamped);
} else if (type == Scalar::BigInt64 || type == Scalar::BigUint64) {
// FIXME: https://bugzil.la/1536703
masm.jump(failure);
} else {
Label notInt32;
masm.branchTestInt32(Assembler::NotEqual, value, &notInt32);
masm.unboxInt32(value, scratch);
Label isInt32;
masm.bind(&isInt32);
masm.storeToTypedIntArray(type, scratch, dest);
masm.jump(&done);
// If the value is a double, truncate and jump back.
// Else, jump to failure.
masm.bind(&notInt32);
masm.branchTestDouble(Assembler::NotEqual, value, failure);
masm.unboxDouble(value, FloatReg0);
masm.branchTruncateDoubleMaybeModUint32(FloatReg0, scratch, failure);
masm.jump(&isInt32);
}
masm.bind(&done);
}
template void StoreToTypedArray(JSContext* cx, MacroAssembler& masm,
Scalar::Type type, const ValueOperand& value,
const Address& dest, Register scratch,
Label* failure);
template void StoreToTypedArray(JSContext* cx, MacroAssembler& masm,
Scalar::Type type, const ValueOperand& value,
const BaseIndex& dest, Register scratch,
Label* failure);
//
// In_Fallback
//

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

@ -1833,14 +1833,6 @@ inline bool IsCacheableDOMProxy(JSObject* obj) {
struct IonOsrTempData;
// Write an arbitrary value to a typed array or typed object address at dest.
// If the value could not be converted to the appropriate format, jump to
// failure.
template <typename T>
void StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
const ValueOperand& value, const T& dest,
Register scratch, Label* failure);
extern MOZ_MUST_USE bool TypeMonitorResult(JSContext* cx,
ICMonitoredFallbackStub* stub,
BaselineFrame* frame,

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

@ -3411,8 +3411,39 @@ AttachDecision SetPropIRGenerator::tryAttachTypedObjectProperty(
// Scalar types can always be stored without a type update stub.
if (fieldDescr->is<ScalarTypeDescr>()) {
Scalar::Type type = fieldDescr->as<ScalarTypeDescr>().type();
Maybe<OperandId> rhsValId;
switch (type) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
rhsValId.emplace(writer.guardToInt32ModUint32(rhsId));
break;
case Scalar::Float32:
case Scalar::Float64:
rhsValId.emplace(writer.guardIsNumber(rhsId));
break;
case Scalar::Uint8Clamped:
rhsValId.emplace(writer.guardToUint8Clamped(rhsId));
break;
case Scalar::BigInt64:
case Scalar::BigUint64:
// FIXME: https://bugzil.la/1536703
return AttachDecision::NoAction;
case Scalar::MaxTypedArrayViewType:
case Scalar::Int64:
MOZ_CRASH("Unsupported TypedArray type");
}
writer.storeTypedObjectScalarProperty(objId, fieldOffset, layout, type,
rhsId);
*rhsValId);
writer.returnFromIC();
trackAttached("TypedObject");
@ -3889,12 +3920,6 @@ AttachDecision SetPropIRGenerator::tryAttachSetTypedElement(
return AttachDecision::NoAction;
}
// bigIntArray[index] = rhsVal_ will throw as the RHS is a number.
if (obj->is<TypedArrayObject>() &&
Scalar::isBigIntType(obj->as<TypedArrayObject>().type())) {
return AttachDecision::NoAction;
}
bool handleOutOfBounds = false;
if (obj->is<TypedArrayObject>()) {
handleOutOfBounds = (index >= obj->as<TypedArrayObject>().length());
@ -3915,6 +3940,11 @@ AttachDecision SetPropIRGenerator::tryAttachSetTypedElement(
Scalar::Type elementType = TypedThingElementType(obj);
TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
// bigIntArray[index] = rhsVal_ will throw as the RHS is a number.
if (Scalar::isBigIntType(elementType)) {
return AttachDecision::NoAction;
}
if (IsPrimitiveArrayTypedObject(obj)) {
writer.guardNoDetachedTypedObjects();
writer.guardGroupForLayout(objId, obj->group());
@ -3922,7 +3952,34 @@ AttachDecision SetPropIRGenerator::tryAttachSetTypedElement(
writer.guardShapeForClass(objId, obj->as<TypedArrayObject>().shape());
}
writer.storeTypedElement(objId, indexId, rhsId, layout, elementType,
Maybe<OperandId> rhsValId;
switch (elementType) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
rhsValId.emplace(writer.guardToInt32ModUint32(rhsId));
break;
case Scalar::Float32:
case Scalar::Float64:
rhsValId.emplace(writer.guardIsNumber(rhsId));
break;
case Scalar::Uint8Clamped:
rhsValId.emplace(writer.guardToUint8Clamped(rhsId));
break;
case Scalar::BigInt64:
case Scalar::BigUint64:
case Scalar::MaxTypedArrayViewType:
case Scalar::Int64:
MOZ_CRASH("Unsupported TypedArray type");
}
writer.storeTypedElement(objId, layout, elementType, indexId, *rhsValId,
handleOutOfBounds);
writer.returnFromIC();

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

@ -218,6 +218,8 @@ extern const uint32_t ArgLengths[];
_(GuardIsNumber, Id) \
_(GuardToInt32, Id, Id) \
_(GuardToInt32Index, Id, Id) \
_(GuardToInt32ModUint32, Id, Id) \
_(GuardToUint8Clamped, Id, Id) \
_(GuardType, Id, Byte) \
_(GuardShape, Id, Field) \
_(GuardGroup, Id, Field) \
@ -292,7 +294,7 @@ extern const uint32_t ArgLengths[];
_(StoreDenseElementHole, Id, Id, Id, Byte) \
_(ArrayPush, Id, Id) \
_(ArrayJoinResult, Id) \
_(StoreTypedElement, Id, Id, Id, Byte, Byte, Byte) \
_(StoreTypedElement, Id, Byte, Byte, Id, Id, Byte) \
_(CallNativeSetter, Id, Id, Field) \
_(CallScriptedSetter, Id, Field, Id, Byte) \
_(CallSetArrayLength, Id, Byte, Id) \
@ -850,6 +852,20 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
return res;
}
Int32OperandId guardToInt32ModUint32(ValOperandId val) {
Int32OperandId res(nextOperandId_++);
writeOpWithOperandId(CacheOp::GuardToInt32ModUint32, val);
writeOperandId(res);
return res;
}
Int32OperandId guardToUint8Clamped(ValOperandId val) {
Int32OperandId res(nextOperandId_++);
writeOpWithOperandId(CacheOp::GuardToUint8Clamped, val);
writeOperandId(res);
return res;
}
NumberOperandId guardIsNumber(ValOperandId val) {
writeOpWithOperandId(CacheOp::GuardIsNumber, val);
return NumberOperandId(val.id());
@ -1319,7 +1335,7 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
void storeTypedObjectScalarProperty(ObjOperandId obj, uint32_t offset,
TypedThingLayout layout,
Scalar::Type type, ValOperandId rhs) {
Scalar::Type type, OperandId rhs) {
writeOpWithOperandId(CacheOp::StoreTypedObjectScalarProperty, obj);
addStubField(offset, StubField::Type::RawWord);
buffer_.writeByte(uint32_t(layout));
@ -1334,14 +1350,14 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
writeOperandId(rhs);
}
void storeTypedElement(ObjOperandId obj, Int32OperandId index,
ValOperandId rhs, TypedThingLayout layout,
Scalar::Type elementType, bool handleOOB) {
void storeTypedElement(ObjOperandId obj, TypedThingLayout layout,
Scalar::Type elementType, Int32OperandId index,
OperandId rhs, bool handleOOB) {
writeOpWithOperandId(CacheOp::StoreTypedElement, obj);
writeOperandId(index);
writeOperandId(rhs);
buffer_.writeByte(uint32_t(layout));
buffer_.writeByte(uint32_t(elementType));
writeOperandId(index);
writeOperandId(rhs);
buffer_.writeByte(uint32_t(handleOOB));
}

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

@ -7,6 +7,7 @@
#include "jit/CacheIRCompiler.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/FunctionTypeTraits.h"
#include "mozilla/ScopeExit.h"
#include <utility>
@ -18,6 +19,7 @@
#include "jit/SharedICHelpers.h"
#include "jit/SharedICRegisters.h"
#include "proxy/Proxy.h"
#include "vm/ArrayBufferObject.h"
#include "vm/GeneratorObject.h"
#include "builtin/Boolean-inl.h"
@ -131,9 +133,32 @@ void CacheRegisterAllocator::ensureDoubleRegister(MacroAssembler& masm,
return;
}
case OperandLocation::Constant:
case OperandLocation::PayloadStack:
case OperandLocation::PayloadReg:
case OperandLocation::Constant: {
MOZ_ASSERT(loc.constant().isNumber(),
"Caller must ensure the operand is a number value");
masm.loadConstantDouble(loc.constant().toNumber(), dest);
return;
}
case OperandLocation::PayloadReg: {
// Doubles can't be stored in payload registers, so this must be an int32.
MOZ_ASSERT(loc.payloadType() == JSVAL_TYPE_INT32,
"Caller must ensure the operand is a number value");
masm.convertInt32ToDouble(loc.payloadReg(), dest);
return;
}
case OperandLocation::PayloadStack: {
// Doubles can't be stored in payload registers, so this must be an int32.
MOZ_ASSERT(loc.payloadType() == JSVAL_TYPE_INT32,
"Caller must ensure the operand is a number value");
MOZ_ASSERT(loc.payloadStack() <= stackPushed_);
masm.convertInt32ToDouble(
Address(masm.getStackPointer(), stackPushed_ - loc.payloadStack()),
dest);
return;
}
case OperandLocation::Uninitialized:
MOZ_CRASH("Unhandled operand type in ensureDoubleRegister");
return;
@ -1526,6 +1551,68 @@ bool CacheIRCompiler::emitGuardToInt32() {
return true;
}
// Infallible |emitDouble| emitters can use this implementation to avoid
// generating extra clean-up instructions to restore the scratch float register.
// To select this function simply omit the |Label* fail| parameter for the
// emitter lambda function.
template <typename EmitDouble>
static typename std::enable_if_t<
mozilla::FunctionTypeTraits<EmitDouble>::arity == 1, void>
EmitGuardDouble(CacheIRCompiler* compiler, MacroAssembler& masm,
ValueOperand input, FailurePath* failure,
EmitDouble emitDouble) {
AutoScratchFloatRegister floatReg(compiler);
masm.unboxDouble(input, floatReg);
emitDouble(floatReg.get());
}
template <typename EmitDouble>
static typename std::enable_if_t<
mozilla::FunctionTypeTraits<EmitDouble>::arity == 2, void>
EmitGuardDouble(CacheIRCompiler* compiler, MacroAssembler& masm,
ValueOperand input, FailurePath* failure,
EmitDouble emitDouble) {
AutoScratchFloatRegister floatReg(compiler, failure);
masm.unboxDouble(input, floatReg);
emitDouble(floatReg.get(), floatReg.failure());
}
template <typename EmitInt32, typename EmitDouble>
static void EmitGuardInt32OrDouble(CacheIRCompiler* compiler,
MacroAssembler& masm, ValueOperand input,
Register output, FailurePath* failure,
EmitInt32 emitInt32, EmitDouble emitDouble) {
Label done;
{
ScratchTagScope tag(masm, input);
masm.splitTagForTest(input, tag);
Label notInt32;
masm.branchTestInt32(Assembler::NotEqual, tag, &notInt32);
{
ScratchTagScopeRelease _(&tag);
masm.unboxInt32(input, output);
emitInt32();
masm.jump(&done);
}
masm.bind(&notInt32);
masm.branchTestDouble(Assembler::NotEqual, tag, failure->label());
{
ScratchTagScopeRelease _(&tag);
EmitGuardDouble(compiler, masm, input, failure, emitDouble);
}
}
masm.bind(&done);
}
bool CacheIRCompiler::emitGuardToInt32Index() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
ValOperandId inputId = reader.valOperandId();
@ -1544,36 +1631,88 @@ bool CacheIRCompiler::emitGuardToInt32Index() {
return false;
}
Label notInt32, done;
masm.branchTestInt32(Assembler::NotEqual, input, &notInt32);
masm.unboxInt32(input, output);
masm.jump(&done);
EmitGuardInt32OrDouble(
this, masm, input, output, failure,
[]() {
// No-op if the value is already an int32.
},
[&](FloatRegister floatReg, Label* fail) {
// ToPropertyKey(-0.0) is "0", so we can truncate -0.0 to 0 here.
masm.convertDoubleToInt32(floatReg, output, fail, false);
});
masm.bind(&notInt32);
return true;
}
masm.branchTestDouble(Assembler::NotEqual, input, failure->label());
bool CacheIRCompiler::emitGuardToInt32ModUint32() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
ValOperandId inputId = reader.valOperandId();
Register output = allocator.defineRegister(masm, reader.int32OperandId());
// If we're compiling a Baseline IC, FloatReg0 is always available.
Label failurePopReg;
if (mode_ != Mode::Baseline) {
masm.push(FloatReg0);
if (allocator.knownType(inputId) == JSVAL_TYPE_INT32) {
ConstantOrRegister input = allocator.useConstantOrRegister(masm, inputId);
if (input.constant()) {
masm.move32(Imm32(input.value().toInt32()), output);
} else {
MOZ_ASSERT(input.reg().type() == MIRType::Int32);
masm.move32(input.reg().typedReg().gpr(), output);
}
return true;
}
masm.unboxDouble(input, FloatReg0);
// ToPropertyKey(-0.0) is "0", so we can truncate -0.0 to 0 here.
masm.convertDoubleToInt32(
FloatReg0, output,
(mode_ == Mode::Baseline) ? failure->label() : &failurePopReg, false);
if (mode_ != Mode::Baseline) {
masm.pop(FloatReg0);
masm.jump(&done);
ValueOperand input = allocator.useValueRegister(masm, inputId);
masm.bind(&failurePopReg);
masm.pop(FloatReg0);
masm.jump(failure->label());
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
masm.bind(&done);
EmitGuardInt32OrDouble(
this, masm, input, output, failure,
[]() {
// No-op if the value is already an int32.
},
[&](FloatRegister floatReg, Label* fail) {
masm.branchTruncateDoubleMaybeModUint32(floatReg, output, fail);
});
return true;
}
bool CacheIRCompiler::emitGuardToUint8Clamped() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
ValOperandId inputId = reader.valOperandId();
Register output = allocator.defineRegister(masm, reader.int32OperandId());
if (allocator.knownType(inputId) == JSVAL_TYPE_INT32) {
ConstantOrRegister input = allocator.useConstantOrRegister(masm, inputId);
if (input.constant()) {
masm.move32(Imm32(ClampDoubleToUint8(input.value().toInt32())), output);
} else {
MOZ_ASSERT(input.reg().type() == MIRType::Int32);
masm.move32(input.reg().typedReg().gpr(), output);
masm.clampIntToUint8(output);
}
return true;
}
ValueOperand input = allocator.useValueRegister(masm, inputId);
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
EmitGuardInt32OrDouble(
this, masm, input, output, failure,
[&]() {
// |output| holds the unboxed int32 value.
masm.clampIntToUint8(output);
},
[&](FloatRegister floatReg) {
masm.clampDoubleToUint8(floatReg, output);
});
return true;
}
@ -2510,28 +2649,12 @@ bool CacheIRCompiler::emitDoubleNegationResult() {
return false;
}
// If we're compiling a Baseline IC, FloatReg0 is always available.
Label failurePopReg, done;
if (mode_ != Mode::Baseline) {
masm.push(FloatReg0);
}
AutoScratchFloatRegister floatReg(this, failure);
masm.ensureDouble(
val, FloatReg0,
(mode_ != Mode::Baseline) ? &failurePopReg : failure->label());
masm.negateDouble(FloatReg0);
masm.boxDouble(FloatReg0, output.valueReg(), FloatReg0);
masm.ensureDouble(val, floatReg, floatReg.failure());
masm.negateDouble(floatReg);
masm.boxDouble(floatReg, output.valueReg(), floatReg);
if (mode_ != Mode::Baseline) {
masm.pop(FloatReg0);
masm.jump(&done);
masm.bind(&failurePopReg);
masm.pop(FloatReg0);
masm.jump(failure->label());
}
masm.bind(&done);
return true;
}
@ -2544,36 +2667,20 @@ bool CacheIRCompiler::emitDoubleIncDecResult(bool isInc) {
return false;
}
// If we're compiling a Baseline IC, FloatReg0 is always available.
Label failurePopReg, done;
if (mode_ != Mode::Baseline) {
masm.push(FloatReg0);
}
AutoScratchFloatRegister floatReg(this, failure);
masm.ensureDouble(
val, FloatReg0,
(mode_ != Mode::Baseline) ? &failurePopReg : failure->label());
masm.ensureDouble(val, floatReg, floatReg.failure());
{
ScratchDoubleScope fpscratch(masm);
masm.loadConstantDouble(1.0, fpscratch);
if (isInc) {
masm.addDouble(fpscratch, FloatReg0);
masm.addDouble(fpscratch, floatReg);
} else {
masm.subDouble(fpscratch, FloatReg0);
masm.subDouble(fpscratch, floatReg);
}
}
masm.boxDouble(FloatReg0, output.valueReg(), FloatReg0);
masm.boxDouble(floatReg, output.valueReg(), floatReg);
if (mode_ != Mode::Baseline) {
masm.pop(FloatReg0);
masm.jump(&done);
masm.bind(&failurePopReg);
masm.pop(FloatReg0);
masm.jump(failure->label());
}
masm.bind(&done);
return true;
}
@ -2593,37 +2700,35 @@ bool CacheIRCompiler::emitTruncateDoubleToUInt32() {
Label int32, done;
masm.branchTestInt32(Assembler::Equal, val, &int32);
Label doneTruncate, truncateABICall;
if (mode_ != Mode::Baseline) {
masm.push(FloatReg0);
{
Label doneTruncate, truncateABICall;
AutoScratchFloatRegister floatReg(this);
masm.unboxDouble(val, floatReg);
masm.branchTruncateDoubleMaybeModUint32(floatReg, res, &truncateABICall);
masm.jump(&doneTruncate);
masm.bind(&truncateABICall);
LiveRegisterSet save(GeneralRegisterSet::Volatile(),
liveVolatileFloatRegs());
save.takeUnchecked(floatReg);
// Bug 1451976
save.takeUnchecked(floatReg.get().asSingle());
masm.PushRegsInMask(save);
masm.setupUnalignedABICall(res);
masm.passABIArg(floatReg, MoveOp::DOUBLE);
masm.callWithABI(BitwiseCast<void*, int32_t (*)(double)>(JS::ToInt32),
MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckOther);
masm.storeCallInt32Result(res);
LiveRegisterSet ignore;
ignore.add(res);
masm.PopRegsInMaskIgnore(save, ignore);
masm.bind(&doneTruncate);
}
masm.unboxDouble(val, FloatReg0);
masm.branchTruncateDoubleMaybeModUint32(FloatReg0, res, &truncateABICall);
masm.jump(&doneTruncate);
masm.bind(&truncateABICall);
LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
save.takeUnchecked(FloatReg0);
// Bug 1451976
save.takeUnchecked(FloatReg0.asSingle());
masm.PushRegsInMask(save);
masm.setupUnalignedABICall(res);
masm.passABIArg(FloatReg0, MoveOp::DOUBLE);
masm.callWithABI(BitwiseCast<void*, int32_t (*)(double)>(JS::ToInt32),
MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckOther);
masm.storeCallInt32Result(res);
LiveRegisterSet ignore;
ignore.add(res);
masm.PopRegsInMaskIgnore(save, ignore);
masm.bind(&doneTruncate);
if (mode_ != Mode::Baseline) {
masm.pop(FloatReg0);
}
masm.jump(&done);
masm.bind(&int32);
@ -2841,7 +2946,7 @@ bool CacheIRCompiler::emitGuardIndexGreaterThanDenseInitLength() {
Register obj = allocator.useRegister(masm, reader.objOperandId());
Register index = allocator.useRegister(masm, reader.int32OperandId());
AutoScratchRegister scratch(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
@ -2854,7 +2959,7 @@ bool CacheIRCompiler::emitGuardIndexGreaterThanDenseInitLength() {
// Ensure index >= initLength.
Label outOfBounds;
Address capacity(scratch, ObjectElements::offsetOfInitializedLength());
masm.spectreBoundsCheck32(index, capacity, scratch2, &outOfBounds);
masm.spectreBoundsCheck32(index, capacity, spectreScratch, &outOfBounds);
masm.jump(failure->label());
masm.bind(&outOfBounds);
@ -2866,7 +2971,7 @@ bool CacheIRCompiler::emitGuardIndexGreaterThanDenseCapacity() {
Register obj = allocator.useRegister(masm, reader.objOperandId());
Register index = allocator.useRegister(masm, reader.int32OperandId());
AutoScratchRegister scratch(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
@ -2879,7 +2984,7 @@ bool CacheIRCompiler::emitGuardIndexGreaterThanDenseCapacity() {
// Ensure index >= capacity.
Label outOfBounds;
Address capacity(scratch, ObjectElements::offsetOfCapacity());
masm.spectreBoundsCheck32(index, capacity, scratch2, &outOfBounds);
masm.spectreBoundsCheck32(index, capacity, spectreScratch, &outOfBounds);
masm.jump(failure->label());
masm.bind(&outOfBounds);
@ -2891,7 +2996,7 @@ bool CacheIRCompiler::emitGuardIndexGreaterThanArrayLength() {
Register obj = allocator.useRegister(masm, reader.objOperandId());
Register index = allocator.useRegister(masm, reader.int32OperandId());
AutoScratchRegister scratch(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
@ -2904,7 +3009,7 @@ bool CacheIRCompiler::emitGuardIndexGreaterThanArrayLength() {
// Ensure index >= length;
Label outOfBounds;
Address length(scratch, ObjectElements::offsetOfLength());
masm.spectreBoundsCheck32(index, length, scratch2, &outOfBounds);
masm.spectreBoundsCheck32(index, length, spectreScratch, &outOfBounds);
masm.jump(failure->label());
masm.bind(&outOfBounds);
return true;
@ -2915,7 +3020,7 @@ bool CacheIRCompiler::emitGuardIndexIsValidUpdateOrAdd() {
Register obj = allocator.useRegister(masm, reader.objOperandId());
Register index = allocator.useRegister(masm, reader.int32OperandId());
AutoScratchRegister scratch(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
@ -2935,7 +3040,7 @@ bool CacheIRCompiler::emitGuardIndexIsValidUpdateOrAdd() {
// Otherwise, ensure index is in bounds.
Address length(scratch, ObjectElements::offsetOfLength());
masm.spectreBoundsCheck32(index, length, scratch2,
masm.spectreBoundsCheck32(index, length, spectreScratch,
/* failure = */ failure->label());
masm.bind(&success);
return true;
@ -3291,6 +3396,75 @@ bool CacheIRCompiler::emitArrayJoinResult() {
return true;
}
bool CacheIRCompiler::emitStoreTypedElement() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
Register obj = allocator.useRegister(masm, reader.objOperandId());
TypedThingLayout layout = reader.typedThingLayout();
Scalar::Type type = reader.scalarType();
Register index = allocator.useRegister(masm, reader.int32OperandId());
Maybe<Register> valInt32;
switch (type) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
case Scalar::Uint8Clamped:
valInt32.emplace(allocator.useRegister(masm, reader.int32OperandId()));
break;
case Scalar::Float32:
case Scalar::Float64:
// Float register must be preserved. The SetProp ICs use the fact that
// baseline has them available, as well as fixed temps on
// LSetPropertyCache.
allocator.ensureDoubleRegister(masm, reader.numberOperandId(), FloatReg0);
break;
case Scalar::BigInt64:
case Scalar::BigUint64:
case Scalar::MaxTypedArrayViewType:
case Scalar::Int64:
MOZ_CRASH("Unsupported TypedArray type");
}
bool handleOOB = reader.readBool();
AutoScratchRegister scratch1(allocator, masm);
AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
// Bounds check.
Label done;
LoadTypedThingLength(masm, layout, obj, scratch1);
masm.spectreBoundsCheck32(index, scratch1, spectreScratch,
handleOOB ? &done : failure->label());
// Load the elements vector.
LoadTypedThingData(masm, layout, obj, scratch1);
BaseIndex dest(scratch1, index, ScaleFromElemWidth(Scalar::byteSize(type)));
if (type == Scalar::Float32) {
ScratchFloat32Scope fpscratch(masm);
masm.convertDoubleToFloat32(FloatReg0, fpscratch);
masm.storeToTypedFloatArray(type, fpscratch, dest);
} else if (type == Scalar::Float64) {
masm.storeToTypedFloatArray(type, FloatReg0, dest);
} else {
masm.storeToTypedIntArray(type, *valInt32, dest);
}
masm.bind(&done);
return true;
}
bool CacheIRCompiler::emitLoadTypedElementResult() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
AutoOutputRegister output(*this);
@ -3353,6 +3527,58 @@ bool CacheIRCompiler::emitLoadTypedElementResult() {
return true;
}
bool CacheIRCompiler::emitStoreTypedObjectScalarProperty() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
Register obj = allocator.useRegister(masm, reader.objOperandId());
StubFieldOffset offset(reader.stubOffset(), StubField::Type::RawWord);
TypedThingLayout layout = reader.typedThingLayout();
Scalar::Type type = reader.scalarType();
Maybe<Register> valInt32;
switch (type) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
case Scalar::Uint8Clamped:
valInt32.emplace(allocator.useRegister(masm, reader.int32OperandId()));
break;
case Scalar::Float32:
case Scalar::Float64:
// Float register must be preserved. The SetProp ICs use the fact that
// baseline has them available, as well as fixed temps on
// LSetPropertyCache.
allocator.ensureDoubleRegister(masm, reader.numberOperandId(), FloatReg0);
break;
case Scalar::BigInt64:
case Scalar::BigUint64:
case Scalar::MaxTypedArrayViewType:
case Scalar::Int64:
MOZ_CRASH("Unsupported TypedArray type");
}
AutoScratchRegister scratch(allocator, masm);
// Compute the address being written to.
LoadTypedThingData(masm, layout, obj, scratch);
Address dest = emitAddressFromStubField(offset, scratch);
if (type == Scalar::Float32) {
ScratchFloat32Scope fpscratch(masm);
masm.convertDoubleToFloat32(FloatReg0, fpscratch);
masm.storeToTypedFloatArray(type, fpscratch, dest);
} else if (type == Scalar::Float64) {
masm.storeToTypedFloatArray(type, FloatReg0, dest);
} else {
masm.storeToTypedIntArray(type, *valInt32, dest);
}
return true;
}
void CacheIRCompiler::emitLoadTypedObjectResultShared(
const Address& fieldAddr, Register scratch, uint32_t typeDescr,
const AutoOutputRegister& output) {
@ -3496,25 +3722,19 @@ bool CacheIRCompiler::emitLoadDoubleTruthyResult() {
AutoOutputRegister output(*this);
ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
Label ifFalse, done, failurePopReg;
AutoScratchFloatRegister floatReg(this);
// If we're compiling a Baseline IC, FloatReg0 is always available.
if (mode_ != Mode::Baseline) {
masm.push(FloatReg0);
}
Label ifFalse, done;
masm.unboxDouble(val, FloatReg0);
masm.unboxDouble(val, floatReg);
masm.branchTestDoubleTruthy(false, FloatReg0, &ifFalse);
masm.branchTestDoubleTruthy(false, floatReg, &ifFalse);
masm.moveValue(BooleanValue(true), output.valueReg());
masm.jump(&done);
masm.bind(&ifFalse);
masm.moveValue(BooleanValue(false), output.valueReg());
if (mode_ != Mode::Baseline) {
masm.pop(FloatReg0);
}
masm.bind(&done);
return true;
}
@ -4031,8 +4251,7 @@ void CacheIRCompiler::emitLoadStubFieldConstant(StubFieldOffset val,
masm.movePtr(ImmGCPtr(objectStubField(val.getOffset())), dest);
break;
case StubField::Type::RawWord:
masm.move32(
Imm32(readStubWord(val.getOffset(), StubField::Type::RawWord)), dest);
masm.move32(Imm32(int32StubField(val.getOffset())), dest);
break;
default:
MOZ_CRASH("Unhandled stub field constant type");
@ -4057,6 +4276,21 @@ void CacheIRCompiler::emitLoadStubField(StubFieldOffset val, Register dest) {
}
}
Address CacheIRCompiler::emitAddressFromStubField(StubFieldOffset val,
Register base) {
JitSpew(JitSpew_Codegen, __FUNCTION__);
MOZ_ASSERT(val.getStubFieldType() == StubField::Type::RawWord);
if (stubFieldPolicy_ == StubFieldPolicy::Constant) {
int32_t offset = int32StubField(val.getOffset());
return Address(base, offset);
}
Address offsetAddr(ICStubReg, stubDataOffset_ + val.getOffset());
masm.addPtr(offsetAddr, base);
return Address(base, 0);
}
bool CacheIRCompiler::emitLoadInstanceOfObjectResult() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
AutoOutputRegister output(*this);
@ -4567,3 +4801,46 @@ AutoCallVM::~AutoCallVM() {
MOZ_ASSERT(compiler_->mode_ == CacheIRCompiler::Mode::Baseline);
stubFrame_->leave(masm_);
}
AutoScratchFloatRegister::AutoScratchFloatRegister(CacheIRCompiler* compiler,
FailurePath* failure)
: compiler_(compiler), failure_(failure) {
// If we're compiling a Baseline IC, FloatReg0 is always available.
if (!compiler_->isBaseline()) {
MacroAssembler& masm = compiler_->masm;
masm.push(FloatReg0);
}
if (failure_) {
failure_->setHasAutoScratchFloatRegister();
}
}
AutoScratchFloatRegister::~AutoScratchFloatRegister() {
if (failure_) {
failure_->clearHasAutoScratchFloatRegister();
}
if (!compiler_->isBaseline()) {
MacroAssembler& masm = compiler_->masm;
masm.pop(FloatReg0);
if (failure_) {
Label done;
masm.jump(&done);
masm.bind(&failurePopReg_);
masm.pop(FloatReg0);
masm.jump(failure_->label());
masm.bind(&done);
}
}
}
Label* AutoScratchFloatRegister::failure() {
MOZ_ASSERT(failure_);
if (!compiler_->isBaseline()) {
return &failurePopReg_;
}
return failure_->labelUnchecked();
}

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

@ -10,6 +10,8 @@
#include "mozilla/Maybe.h"
#include "jit/CacheIR.h"
#include "jit/JitOptions.h"
#include "jit/SharedICRegisters.h"
namespace js {
namespace jit {
@ -33,6 +35,8 @@ class IonCacheIRCompiler;
_(GuardIsNumber) \
_(GuardToInt32) \
_(GuardToInt32Index) \
_(GuardToInt32ModUint32) \
_(GuardToUint8Clamped) \
_(GuardType) \
_(GuardClass) \
_(GuardGroupHasUnanalyzedNewScript) \
@ -120,6 +124,8 @@ class IonCacheIRCompiler;
_(CompareDoubleResult) \
_(CompareObjectUndefinedNullResult) \
_(ArrayJoinResult) \
_(StoreTypedElement) \
_(StoreTypedObjectScalarProperty) \
_(CallPrintString) \
_(Breakpoint) \
_(MegamorphicLoadSlotResult) \
@ -650,6 +656,31 @@ class MOZ_RAII AutoScratchRegister {
operator Register() const { return reg_; }
};
// On x86, spectreBoundsCheck32 can emit better code if it has a scratch
// register and index masking is enabled.
class MOZ_RAII AutoSpectreBoundsScratchRegister {
mozilla::Maybe<AutoScratchRegister> scratch_;
Register reg_ = InvalidReg;
AutoSpectreBoundsScratchRegister(const AutoSpectreBoundsScratchRegister&) =
delete;
void operator=(const AutoSpectreBoundsScratchRegister&) = delete;
public:
AutoSpectreBoundsScratchRegister(CacheRegisterAllocator& alloc,
MacroAssembler& masm) {
#ifdef JS_CODEGEN_X86
if (JitOptions.spectreIndexMasking) {
scratch_.emplace(alloc, masm);
reg_ = scratch_->get();
}
#endif
}
Register get() const { return reg_; }
operator Register() const { return reg_; }
};
// The FailurePath class stores everything we need to generate a failure path
// at the end of the IC code. The failure path restores the input registers, if
// needed, and jumps to the next stub.
@ -658,6 +689,11 @@ class FailurePath {
SpilledRegisterVector spilledRegs_;
NonAssertingLabel label_;
uint32_t stackPushed_;
#ifdef DEBUG
// Flag to ensure FailurePath::label() isn't taken while there's a scratch
// float register which still needs to be restored.
bool hasAutoScratchFloatRegister_ = false;
#endif
public:
FailurePath() = default;
@ -668,7 +704,11 @@ class FailurePath {
label_(other.label_),
stackPushed_(other.stackPushed_) {}
Label* label() { return &label_; }
Label* labelUnchecked() { return &label_; }
Label* label() {
MOZ_ASSERT(!hasAutoScratchFloatRegister_);
return labelUnchecked();
}
void setStackPushed(uint32_t i) { stackPushed_ = i; }
uint32_t stackPushed() const { return stackPushed_; }
@ -688,6 +728,20 @@ class FailurePath {
// If canShareFailurePath(other) returns true, the same machine code will
// be emitted for two failure paths, so we can share them.
bool canShareFailurePath(const FailurePath& other) const;
void setHasAutoScratchFloatRegister() {
#ifdef DEBUG
MOZ_ASSERT(!hasAutoScratchFloatRegister_);
hasAutoScratchFloatRegister_ = true;
#endif
}
void clearHasAutoScratchFloatRegister() {
#ifdef DEBUG
MOZ_ASSERT(hasAutoScratchFloatRegister_);
hasAutoScratchFloatRegister_ = false;
#endif
}
};
/**
@ -716,6 +770,7 @@ class MOZ_RAII CacheIRCompiler {
friend class AutoStubFrame;
friend class AutoSaveLiveRegisters;
friend class AutoCallVM;
friend class AutoScratchFloatRegister;
enum class Mode { Baseline, Ion };
@ -849,6 +904,7 @@ class MOZ_RAII CacheIRCompiler {
void emitLoadStubField(StubFieldOffset val, Register dest);
void emitLoadStubFieldConstant(StubFieldOffset val, Register dest);
Address emitAddressFromStubField(StubFieldOffset val, Register base);
uintptr_t readStubWord(uint32_t offset, StubField::Type type) {
MOZ_ASSERT(stubFieldPolicy_ == StubFieldPolicy::Constant);
@ -1076,6 +1132,35 @@ class MOZ_RAII AutoCallVM {
~AutoCallVM();
};
// RAII class to allocate FloatReg0 as a scratch register and release it when
// we're done with it. The previous contents of FloatReg0 may be spilled on the
// stack and, if necessary, are restored when the destructor runs.
//
// When FailurePath is passed to the constructor, FailurePath::label() must not
// be used during the life time of the AutoScratchFloatRegister. Instead use
// AutoScratchFloatRegister::failure().
class MOZ_RAII AutoScratchFloatRegister {
Label failurePopReg_{};
CacheIRCompiler* compiler_;
FailurePath* failure_;
AutoScratchFloatRegister(const AutoScratchFloatRegister&) = delete;
void operator=(const AutoScratchFloatRegister&) = delete;
public:
explicit AutoScratchFloatRegister(CacheIRCompiler* compiler)
: AutoScratchFloatRegister(compiler, nullptr) {}
AutoScratchFloatRegister(CacheIRCompiler* compiler, FailurePath* failure);
~AutoScratchFloatRegister();
Label* failure();
FloatRegister get() const { return FloatReg0; }
operator FloatRegister() const { return FloatReg0; }
};
// See the 'Sharing Baseline stub code' comment in CacheIR.h for a description
// of this class.
class CacheIRStubInfo {

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

@ -11207,7 +11207,6 @@ void CodeGenerator::addGetPropertyCache(
void CodeGenerator::addSetPropertyCache(
LInstruction* ins, LiveRegisterSet liveRegs, Register objReg, Register temp,
FloatRegister tempDouble, FloatRegister tempF32,
const ConstantOrRegister& id, const ConstantOrRegister& value, bool strict,
bool needsPostBarrier, bool needsTypeBarrier, bool guardHoles) {
CacheKind kind = CacheKind::SetElem;
@ -11218,9 +11217,8 @@ void CodeGenerator::addSetPropertyCache(
kind = CacheKind::SetProp;
}
}
IonSetPropertyIC cache(kind, liveRegs, objReg, temp, tempDouble, tempF32, id,
value, strict, needsPostBarrier, needsTypeBarrier,
guardHoles);
IonSetPropertyIC cache(kind, liveRegs, objReg, temp, id, value, strict,
needsPostBarrier, needsTypeBarrier, guardHoles);
addIC(ins, allocateIC(cache));
}
@ -11398,17 +11396,14 @@ void CodeGenerator::visitSetPropertyCache(LSetPropertyCache* ins) {
LiveRegisterSet liveRegs = ins->safepoint()->liveRegs();
Register objReg = ToRegister(ins->getOperand(0));
Register temp = ToRegister(ins->temp());
FloatRegister tempDouble = ToTempFloatRegisterOrInvalid(ins->tempDouble());
FloatRegister tempF32 = ToTempFloatRegisterOrInvalid(ins->tempFloat32());
ConstantOrRegister id = toConstantOrRegister(ins, LSetPropertyCache::Id,
ins->mir()->idval()->type());
ConstantOrRegister value = toConstantOrRegister(ins, LSetPropertyCache::Value,
ins->mir()->value()->type());
addSetPropertyCache(ins, liveRegs, objReg, temp, tempDouble, tempF32, id,
value, ins->mir()->strict(),
ins->mir()->needsPostBarrier(),
addSetPropertyCache(ins, liveRegs, objReg, temp, id, value,
ins->mir()->strict(), ins->mir()->needsPostBarrier(),
ins->mir()->needsTypeBarrier(), ins->mir()->guardHoles());
}

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

@ -243,7 +243,6 @@ class CodeGenerator final : public CodeGeneratorSpecific {
GetPropertyResultFlags flags);
void addSetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs,
Register objReg, Register temp,
FloatRegister tempDouble, FloatRegister tempF32,
const ConstantOrRegister& id,
const ConstantOrRegister& value, bool strict,
bool needsPostBarrier, bool needsTypeBarrier,

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

@ -1567,29 +1567,6 @@ bool IonCacheIRCompiler::emitStoreTypedObjectReferenceProperty() {
return true;
}
bool IonCacheIRCompiler::emitStoreTypedObjectScalarProperty() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
Register obj = allocator.useRegister(masm, reader.objOperandId());
int32_t offset = int32StubField(reader.stubOffset());
TypedThingLayout layout = reader.typedThingLayout();
Scalar::Type type = reader.scalarType();
ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
AutoScratchRegister scratch1(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
// Compute the address being written to.
LoadTypedThingData(masm, layout, obj, scratch1);
Address dest(scratch1, offset);
StoreToTypedArray(cx_, masm, type, val, dest, scratch2, failure->label());
return true;
}
static void EmitStoreDenseElement(MacroAssembler& masm,
const ConstantOrRegister& value,
Register elements,
@ -1666,7 +1643,7 @@ bool IonCacheIRCompiler::emitStoreDenseElement() {
allocator.useConstantOrRegister(masm, reader.valOperandId());
AutoScratchRegister scratch1(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
@ -1683,7 +1660,8 @@ bool IonCacheIRCompiler::emitStoreDenseElement() {
// Bounds check.
Address initLength(scratch1, ObjectElements::offsetOfInitializedLength());
masm.spectreBoundsCheck32(index, initLength, scratch2, failure->label());
masm.spectreBoundsCheck32(index, initLength, spectreScratch,
failure->label());
// Hole check.
BaseObjectElementIndex element(scratch1, index);
@ -1717,7 +1695,7 @@ bool IonCacheIRCompiler::emitStoreDenseElementHole() {
reader.readBool();
AutoScratchRegister scratch1(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
@ -1736,8 +1714,7 @@ bool IonCacheIRCompiler::emitStoreDenseElementHole() {
BaseObjectElementIndex element(scratch1, index);
Label inBounds, outOfBounds;
Register spectreTemp = scratch2;
masm.spectreBoundsCheck32(index, initLength, spectreTemp, &outOfBounds);
masm.spectreBoundsCheck32(index, initLength, spectreScratch, &outOfBounds);
masm.jump(&inBounds);
masm.bind(&outOfBounds);
@ -1747,7 +1724,7 @@ bool IonCacheIRCompiler::emitStoreDenseElementHole() {
// need to allocate more elements.
Label capacityOk, allocElement;
Address capacity(scratch1, ObjectElements::offsetOfCapacity());
masm.spectreBoundsCheck32(index, capacity, spectreTemp, &allocElement);
masm.spectreBoundsCheck32(index, capacity, spectreScratch, &allocElement);
masm.jump(&capacityOk);
// Check for non-writable array length. We only have to do this if
@ -1809,76 +1786,6 @@ bool IonCacheIRCompiler::emitArrayPush() {
return false;
}
bool IonCacheIRCompiler::emitStoreTypedElement() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
Register obj = allocator.useRegister(masm, reader.objOperandId());
Register index = allocator.useRegister(masm, reader.int32OperandId());
ConstantOrRegister val =
allocator.useConstantOrRegister(masm, reader.valOperandId());
TypedThingLayout layout = reader.typedThingLayout();
Scalar::Type arrayType = reader.scalarType();
bool handleOOB = reader.readBool();
AutoScratchRegister scratch1(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
// Bounds check.
Label done;
LoadTypedThingLength(masm, layout, obj, scratch1);
masm.spectreBoundsCheck32(index, scratch1, scratch2,
handleOOB ? &done : failure->label());
// Load the elements vector.
LoadTypedThingData(masm, layout, obj, scratch1);
BaseIndex dest(scratch1, index,
ScaleFromElemWidth(Scalar::byteSize(arrayType)));
FloatRegister maybeTempDouble = ic_->asSetPropertyIC()->maybeTempDouble();
FloatRegister maybeTempFloat32 = ic_->asSetPropertyIC()->maybeTempFloat32();
MOZ_ASSERT(maybeTempDouble != InvalidFloatReg);
MOZ_ASSERT_IF(jit::hasUnaliasedDouble(), maybeTempFloat32 != InvalidFloatReg);
if (arrayType == Scalar::Float32) {
FloatRegister tempFloat =
hasUnaliasedDouble() ? maybeTempFloat32 : maybeTempDouble;
if (!masm.convertConstantOrRegisterToFloat(cx_, val, tempFloat,
failure->label())) {
return false;
}
masm.storeToTypedFloatArray(arrayType, tempFloat, dest);
} else if (arrayType == Scalar::Float64) {
if (!masm.convertConstantOrRegisterToDouble(cx_, val, maybeTempDouble,
failure->label())) {
return false;
}
masm.storeToTypedFloatArray(arrayType, maybeTempDouble, dest);
} else {
Register valueToStore = scratch2;
if (arrayType == Scalar::Uint8Clamped) {
if (!masm.clampConstantOrRegisterToUint8(
cx_, val, maybeTempDouble, valueToStore, failure->label())) {
return false;
}
} else {
if (!masm.truncateConstantOrRegisterToInt32(
cx_, val, maybeTempDouble, valueToStore, failure->label())) {
return false;
}
}
masm.storeToTypedIntArray(arrayType, valueToStore, dest);
}
masm.bind(&done);
return true;
}
bool IonCacheIRCompiler::emitCallNativeSetter() {
JitSpew(JitSpew_Codegen, __FUNCTION__);
AutoSaveLiveRegisters save(*this);

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