зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central a=merge
This commit is contained in:
Коммит
faadf02d25
|
@ -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,
|
||||
|
|
|
@ -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==
|
|
@ -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 Redux—they 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 terms—again, 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\">"
|
||||
"> Firefox Firefox Firefox Firefox <br>"
|
||||
"> Firefox Firefox Firefox Firefox<br>"
|
||||
"><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, ¬Int32);
|
||||
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(¬Int32);
|
||||
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, ¬Int32);
|
||||
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(¬Int32);
|
||||
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, ¬Int32);
|
||||
{
|
||||
ScratchTagScopeRelease _(&tag);
|
||||
|
||||
masm.unboxInt32(input, output);
|
||||
emitInt32();
|
||||
|
||||
masm.jump(&done);
|
||||
}
|
||||
masm.bind(¬Int32);
|
||||
|
||||
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, ¬Int32);
|
||||
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(¬Int32);
|
||||
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);
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче