mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 21:00:50 +00:00
Merge mozilla-central to inbound. a=merge on a CLOSED TREE
This commit is contained in:
commit
0ffa0aefd9
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -54,7 +54,7 @@ version = "0.2.3"
|
||||
dependencies = [
|
||||
"bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -90,7 +90,7 @@ version = "0.2.2"
|
||||
dependencies = [
|
||||
"audioipc 0.2.3",
|
||||
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -407,10 +407,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cubeb"
|
||||
version = "0.5.0"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cubeb-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -418,16 +418,16 @@ name = "cubeb-backend"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cubeb-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cubeb-core"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -442,7 +442,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cubeb-sys"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -740,7 +740,7 @@ dependencies = [
|
||||
"audioipc-server 0.2.2",
|
||||
"cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-pulse 0.2.0",
|
||||
"cubeb-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_c 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_glue 0.1.0",
|
||||
"geckoservo 0.0.1",
|
||||
@ -2264,10 +2264,10 @@ dependencies = [
|
||||
"checksum cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "079adec4af52bb5275eadd004292028c79eb3c5f5b4ee8086a36d4197032f6df"
|
||||
"checksum cstr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b6557bdb1dc9647eae1cf7f5601b14cd45fc3c7ccf2df618387416fe542da6ea"
|
||||
"checksum cstr-macros 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9f316203d1ea36f4f18316822806f6999aa3dc5ed1adf51e35b77e3b3933d78"
|
||||
"checksum cubeb 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7923bed2d5a1a64ba0c3e8b6badc360508ba488d1f2f59f16a688802e755bb85"
|
||||
"checksum cubeb 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8a3502aafa1bf95c524f65d2ba46d8741700c6a8a9543ea52c6da3d8b69a2896"
|
||||
"checksum cubeb-backend 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcac95519416d9ec814db2dc40e6293e7da25b906023d93f48b87f0587ab138"
|
||||
"checksum cubeb-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "169d9a36f5daa60f9c4597905132aef056cf62693addd4a3421492853ccad565"
|
||||
"checksum cubeb-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e405ad4dff2c1a7cbfa6998e5925e8ceafe900feeacfcad35a3e3790bea0f2aa"
|
||||
"checksum cubeb-core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37f7b20f757a4e4b6aa28863236551bff77682dc6db192eba15af615492b5445"
|
||||
"checksum cubeb-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "653b9e245d35dbe2a2da7c4586275cee75ff656ddeb02d4a73b4afdfa6d67502"
|
||||
"checksum darling 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d3effd06d4057f275cb7858889f4952920bab78dd8ff0f6e7dfe0c8d2e67ed89"
|
||||
"checksum darling_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "167dd3e235c2f1da16a635c282630452cdf49191eb05711de1bcd1d3d5068c00"
|
||||
"checksum darling_macro 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c53edaba455f6073a10c27c72440860eb3f60444f8c8660a391032eeae744d82"
|
||||
|
@ -18,8 +18,6 @@
|
||||
|
||||
<!DOCTYPE page [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
<!ENTITY % preferencesDTD SYSTEM
|
||||
"chrome://browser/locale/preferences/preferences.dtd">
|
||||
<!ENTITY % selectBookmarkDTD SYSTEM
|
||||
"chrome://browser/locale/preferences/selectBookmark.dtd">
|
||||
<!ENTITY % languagesDTD SYSTEM "chrome://browser/locale/preferences/languages.dtd">
|
||||
@ -45,7 +43,6 @@
|
||||
<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
|
||||
%aboutDialogDTD;
|
||||
%brandDTD;
|
||||
%preferencesDTD;
|
||||
%selectBookmarkDTD;
|
||||
%languagesDTD;
|
||||
%fontDTD;
|
||||
|
@ -118,7 +118,7 @@ var gSyncPane = {
|
||||
|
||||
// Use cached values while we wait for the up-to-date values
|
||||
let cachedComputerName = Services.prefs.getCharPref("services.sync.client.name", "");
|
||||
document.querySelector(".fxaEmailAddress").value = username;
|
||||
document.getElementById("fxaEmailAddress").textContent = username;
|
||||
this._populateComputerName(cachedComputerName);
|
||||
this.page = FXA_PAGE_LOGGED_IN;
|
||||
},
|
||||
@ -259,7 +259,7 @@ var gSyncPane = {
|
||||
.wrappedJSObject;
|
||||
|
||||
let displayNameLabel = document.getElementById("fxaDisplayName");
|
||||
let fxaEmailAddressLabels = document.querySelectorAll(".fxaEmailAddress");
|
||||
let fxaEmailAddressLabels = document.querySelectorAll(".l10nArgsEmailAddress");
|
||||
displayNameLabel.hidden = true;
|
||||
|
||||
// determine the fxa status...
|
||||
@ -288,8 +288,11 @@ var gSyncPane = {
|
||||
syncReady = true;
|
||||
}
|
||||
fxaEmailAddressLabels.forEach((label) => {
|
||||
label.value = state.email;
|
||||
let l10nAttrs = document.l10n.getAttributes(label);
|
||||
document.l10n.setAttributes(l10nAttrs.id, {email: state.email});
|
||||
});
|
||||
document.getElementById("fxaEmailAddress").textContent = state.email;
|
||||
|
||||
this._populateComputerName(Weave.Service.clientsEngine.localName);
|
||||
let engines = document.getElementById("fxaSyncEngines");
|
||||
for (let checkbox of engines.querySelectorAll("checkbox")) {
|
||||
|
@ -11,7 +11,7 @@
|
||||
class="subcategory"
|
||||
hidden="true"
|
||||
data-category="paneSync">
|
||||
<label class="header-name" flex="1">&paneSync1.title;</label>
|
||||
<label class="header-name" flex="1" data-l10n-id="pane-sync-title" />
|
||||
</hbox>
|
||||
|
||||
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true"
|
||||
@ -19,8 +19,8 @@
|
||||
<groupbox id="noFxaAccount">
|
||||
<hbox>
|
||||
<vbox flex="1">
|
||||
<caption><label id="noFxaCaption">&signedOut.caption;</label></caption>
|
||||
<description id="noFxaDescription" flex="1">&signedOut.description;</description>
|
||||
<caption><label id="noFxaCaption" data-l10n-id="sync-signedout-caption"/></caption>
|
||||
<description id="noFxaDescription" flex="1" data-l10n-id="sync-signedout-description"/>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<image class="fxaSyncIllustration"/>
|
||||
@ -33,17 +33,16 @@
|
||||
<vbox flex="1">
|
||||
<hbox align="center" flex="1">
|
||||
<hbox align="center" flex="1">
|
||||
<caption><label id="signedOutAccountBoxTitle">&signedOut.accountBox.title;</label></caption>
|
||||
<caption><label id="signedOutAccountBoxTitle" data-l10n-id="sync-signedout-account-title"/></caption>
|
||||
</hbox>
|
||||
<button id="noFxaSignIn"
|
||||
class="accessory-button"
|
||||
label="&signedOut.accountBox.signin2;"
|
||||
accesskey="&signedOut.accountBox.signin2.accesskey;"/>
|
||||
data-l10n-id="sync-signedout-account-signin"/>
|
||||
</hbox>
|
||||
<hbox align="center" flex="1">
|
||||
<html:a id="noFxaSignUp"
|
||||
class="openLink"
|
||||
accesskey="&signedOut.accountBox.create2.accesskey;">&signedOut.accountBox.create2;</html:a>
|
||||
data-l10n-id="sync-signedout-account-create" />
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
@ -64,7 +63,7 @@
|
||||
<hbox>
|
||||
<vbox id="fxaContentWrapper" flex="1">
|
||||
<groupbox id="fxaGroup">
|
||||
<caption class="search-header" hidden="true"><label>&paneSync1.title;</label></caption>
|
||||
<caption class="search-header" hidden="true"><label data-l10n-id="pane-sync-title"/></caption>
|
||||
|
||||
<deck id="fxaLoginStatus" flex="1">
|
||||
|
||||
@ -74,20 +73,19 @@
|
||||
role="button"
|
||||
onclick="gSyncPane.openChangeProfileImage(event);"
|
||||
onkeypress="gSyncPane.openChangeProfileImage(event);"
|
||||
tooltiptext="&profilePicture.tooltip;"/>
|
||||
data-l10n-id="sync-profile-picture"/>
|
||||
<vbox flex="1" pack="center">
|
||||
<hbox flex="1" align="baseline">
|
||||
<caption><label id="fxaDisplayName" hidden="true"/></caption>
|
||||
<label class="fxaEmailAddress" flex="1" crop="end"/>
|
||||
<label id="fxaEmailAddress" flex="1" crop="end"/>
|
||||
<button id="fxaUnlinkButton"
|
||||
class="accessory-button"
|
||||
label="&disconnect3.label;"
|
||||
accesskey="&disconnect3.accesskey;"/>
|
||||
data-l10n-id="sync-disconnect"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<html:a id="verifiedManage" class="openLink"
|
||||
accesskey="&verifiedManage.accesskey;"
|
||||
onkeypress="gSyncPane.openManageFirefoxAccount(event);">&verifiedManage.label;</html:a>
|
||||
data-l10n-id="sync-manage-account"
|
||||
onkeypress="gSyncPane.openManageFirefoxAccount(event);"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
@ -100,15 +98,14 @@
|
||||
<vbox flex="1" pack="center">
|
||||
<hbox>
|
||||
<image class="fxaLoginRejectedWarning"/>
|
||||
<description flex="1">
|
||||
&signedInUnverified.beforename.label;
|
||||
<label class="fxaEmailAddress"/>
|
||||
&signedInUnverified.aftername.label;
|
||||
</description>
|
||||
<description flex="1"
|
||||
class="l10nArgsEmailAddress"
|
||||
data-l10n-id="sync-signedin-unverified"
|
||||
data-l10n-args='{"email": ""}'/>
|
||||
</hbox>
|
||||
<hbox class="fxaAccountBoxButtons">
|
||||
<button id="verifyFxaAccount" label="&resendVerification.label;" accesskey="&resendVerification.accesskey;"></button>
|
||||
<button id="unverifiedUnlinkFxaAccount" label="&removeAccount.label;" accesskey="&removeAccount.accesskey;"></button>
|
||||
<button id="verifyFxaAccount" data-l10n-id="sync-resend-verification"/>
|
||||
<button id="unverifiedUnlinkFxaAccount" data-l10n-id="sync-remove-account"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
@ -121,63 +118,42 @@
|
||||
<vbox flex="1" pack="center">
|
||||
<hbox>
|
||||
<image class="fxaLoginRejectedWarning"/>
|
||||
<description flex="1">
|
||||
&signedInLoginFailure.beforename.label;
|
||||
<label class="fxaEmailAddress"/>
|
||||
&signedInLoginFailure.aftername.label;
|
||||
</description>
|
||||
<description flex="1"
|
||||
class="l10nArgsEmailAddress"
|
||||
data-l10n-id="sync-signedin-login-failure"
|
||||
data-l10n-args='{"email": ""}'/>
|
||||
</hbox>
|
||||
<hbox class="fxaAccountBoxButtons">
|
||||
<button id="rejectReSignIn" label="&signIn.label;" accesskey="&signIn.accesskey;"></button>
|
||||
<button id="rejectUnlinkFxaAccount" label="&removeAccount.label;" accesskey="&removeAccount.accesskey;"></button>
|
||||
<button id="rejectReSignIn" data-l10n-id="sync-sign-in"/>
|
||||
<button id="rejectUnlinkFxaAccount" data-l10n-id="sync-remove-account"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</deck>
|
||||
</groupbox>
|
||||
<groupbox id="syncOptions">
|
||||
<caption><label>&signedIn.settings.label;</label></caption>
|
||||
<description>&signedIn.settings.description;</description>
|
||||
<caption><label data-l10n-id="sync-signedin-settings-header"/></caption>
|
||||
<description data-l10n-id="sync-signedin-settings-desc"/>
|
||||
<hbox id="fxaSyncEngines">
|
||||
<vbox flex="1">
|
||||
<!-- by design, no tooltip for bookmarks or history -->
|
||||
<checkbox label="&engine.bookmarks.label;"
|
||||
accesskey="&engine.bookmarks.accesskey;"
|
||||
<checkbox data-l10n-id="sync-engine-bookmarks"
|
||||
preference="engine.bookmarks"/>
|
||||
<checkbox label="&engine.history.label;"
|
||||
accesskey="&engine.history.accesskey;"
|
||||
<checkbox data-l10n-id="sync-engine-history"
|
||||
preference="engine.history"/>
|
||||
<checkbox label="&engine.tabs.label2;"
|
||||
tooltiptext="&engine.tabs.title;"
|
||||
accesskey="&engine.tabs.accesskey;"
|
||||
<checkbox data-l10n-id="sync-engine-tabs"
|
||||
preference="engine.tabs"/>
|
||||
<checkbox label="&engine.logins.label;"
|
||||
tooltiptext="&engine.logins.title;"
|
||||
accesskey="&engine.logins.accesskey;"
|
||||
<checkbox data-l10n-id="sync-engine-logins"
|
||||
preference="engine.passwords"/>
|
||||
</vbox>
|
||||
<vbox flex="1">
|
||||
<checkbox label="&engine.addresses.label;"
|
||||
accesskey="&engine.addresses.accesskey;"
|
||||
tooltiptext="&engine.addresses.title;"
|
||||
<checkbox data-l10n-id="sync-engine-addresses"
|
||||
preference="engine.addresses"/>
|
||||
<checkbox label="&engine.creditcards.label;"
|
||||
tooltiptext="&engine.creditcards.title;"
|
||||
accesskey="&engine.creditcards.accesskey;"
|
||||
<checkbox data-l10n-id="sync-engine-creditcards"
|
||||
preference="engine.creditcards"/>
|
||||
<checkbox label="&engine.addons.label;"
|
||||
tooltiptext="&engine.addons.title;"
|
||||
accesskey="&engine.addons.accesskey;"
|
||||
<checkbox data-l10n-id="sync-engine-addons"
|
||||
preference="engine.addons"/>
|
||||
<checkbox
|
||||
#ifdef XP_WIN
|
||||
label="&engine.prefsWin.label;"
|
||||
accesskey="&engine.prefsWin.accesskey;"
|
||||
#else
|
||||
label="&engine.prefs.label;"
|
||||
accesskey="&engine.prefs.accesskey;"
|
||||
#endif
|
||||
tooltiptext="&engine.prefs.title;"
|
||||
<checkbox data-l10n-id="sync-engine-prefs"
|
||||
preference="engine.prefs"/>
|
||||
</vbox>
|
||||
<spacer/>
|
||||
@ -187,38 +163,29 @@
|
||||
</hbox>
|
||||
<groupbox>
|
||||
<caption>
|
||||
<label control="fxaSyncComputerName">
|
||||
&fxaSyncDeviceName.label;
|
||||
</label>
|
||||
<label control="fxaSyncComputerName" data-l10n-id="sync-device-name-header"/>
|
||||
</caption>
|
||||
<hbox id="fxaDeviceName">
|
||||
<textbox id="fxaSyncComputerName" flex="1" disabled="true"/>
|
||||
<button id="fxaChangeDeviceName"
|
||||
label="&changeSyncDeviceName2.label;"
|
||||
accesskey="&changeSyncDeviceName2.accesskey;"/>
|
||||
data-l10n-id="sync-device-name-change"/>
|
||||
<button id="fxaCancelChangeDeviceName"
|
||||
label="&cancelChangeSyncDeviceName.label;"
|
||||
accesskey="&cancelChangeSyncDeviceName.accesskey;"
|
||||
data-l10n-id="sync-device-name-cancel"
|
||||
hidden="true"/>
|
||||
<button id="fxaSaveChangeDeviceName"
|
||||
label="&saveChangeSyncDeviceName.label;"
|
||||
accesskey="&saveChangeSyncDeviceName.accesskey;"
|
||||
data-l10n-id="sync-device-name-save"
|
||||
hidden="true"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
<vbox align="start">
|
||||
<label id="mobilePromo-singledevice"
|
||||
class="text-link fxaMobilePromo">&mobilepromo.singledevice;</label>
|
||||
class="text-link fxaMobilePromo" data-l10n-id="sync-mobilepromo-single"/>
|
||||
<label id="mobilePromo-multidevice"
|
||||
class="text-link fxaMobilePromo">&mobilepromo.multidevice;</label>
|
||||
class="text-link fxaMobilePromo" data-l10n-id="sync-mobilepromo-multi"/>
|
||||
</vbox>
|
||||
<vbox id="tosPP-small" align="start">
|
||||
<label id="tosPP-small-ToS" class="text-link">
|
||||
&prefs.tosLink.label;
|
||||
</label>
|
||||
<label id="tosPP-small-PP" class="text-link">
|
||||
&fxaPrivacyNotice.link.label;
|
||||
</label>
|
||||
<label id="tosPP-small-ToS" class="text-link" data-l10n-id="sync-tos-link"/>
|
||||
<label id="tosPP-small-PP" class="text-link" data-l10n-id="sync-fxa-privacy-notice"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</deck>
|
||||
|
@ -7,3 +7,7 @@
|
||||
# “Sync” can be localized, “Firefox” must be treated as a brand,
|
||||
# and kept in English.
|
||||
-sync-brand-name = Firefox Sync
|
||||
|
||||
# “Account” can be localized, “Firefox” must be treated as a brand,
|
||||
# and kept in English.
|
||||
-fxaccount-brand-name = Firefox Account
|
||||
|
@ -471,6 +471,113 @@ containers-preferences-button =
|
||||
containers-remove-button =
|
||||
.label = Remove
|
||||
|
||||
## Sync Section - Signed out
|
||||
|
||||
sync-signedout-caption = Take Your Web With You
|
||||
sync-signedout-description = Synchronize your bookmarks, history, tabs, passwords, add-ons, and preferences across all your devices.
|
||||
|
||||
sync-signedout-account-title = Connect with a { -fxaccount-brand-name }
|
||||
sync-signedout-account-create = Don’t have an account? Get started
|
||||
.accesskey = c
|
||||
|
||||
sync-signedout-account-signin =
|
||||
.label = Sign In…
|
||||
.accesskey = I
|
||||
|
||||
## Sync Section - Signed in
|
||||
|
||||
sync-profile-picture =
|
||||
.tooltiptext = Change profile picture
|
||||
|
||||
sync-disconnect =
|
||||
.label = Disconnect…
|
||||
.accesskey = D
|
||||
|
||||
sync-manage-account = Manage account
|
||||
.accesskey = o
|
||||
|
||||
sync-signedin-unverified = { $email } is not verified.
|
||||
sync-signedin-login-failure = Please sign in to reconnect { $email }
|
||||
|
||||
sync-resend-verification =
|
||||
.label = Resend Verification
|
||||
.accesskey = d
|
||||
|
||||
sync-remove-account =
|
||||
.label = Remove Account
|
||||
.accesskey = R
|
||||
|
||||
sync-sign-in =
|
||||
.label = Sign in
|
||||
.accesskey = g
|
||||
|
||||
sync-signedin-settings-header = Sync Settings
|
||||
sync-signedin-settings-desc = Choose what to synchronize on your devices using { -brand-short-name }
|
||||
|
||||
sync-engine-bookmarks =
|
||||
.label = Bookmarks
|
||||
.accesskey = m
|
||||
|
||||
sync-engine-history =
|
||||
.label = History
|
||||
.accesskey = r
|
||||
|
||||
sync-engine-tabs =
|
||||
.label = Open tabs
|
||||
.tooltiptext = A list of what’s open on all synced devices
|
||||
.accesskey = t
|
||||
|
||||
sync-engine-logins =
|
||||
.label = Logins
|
||||
.tooltiptext = Usernames and passwords you’ve saved
|
||||
.accesskey = L
|
||||
|
||||
sync-engine-addresses =
|
||||
.label = Addresses
|
||||
.tooltiptext = Postal addresses you’ve saved (desktop only)
|
||||
.accesskey = e
|
||||
|
||||
sync-engine-creditcards =
|
||||
.label = Credit cards
|
||||
.tooltiptext = Names, numbers and expiry dates (desktop only)
|
||||
.accesskey = C
|
||||
|
||||
sync-engine-addons =
|
||||
.label = Add-ons
|
||||
.tooltiptext = Extensions and themes for Firefox desktop
|
||||
.accesskey = A
|
||||
|
||||
sync-engine-prefs =
|
||||
.label =
|
||||
{ PLATFORM() ->
|
||||
[windows] Options
|
||||
*[other] Preferences
|
||||
}
|
||||
.tooltiptext = General, Privacy, and Security settings you’ve changed
|
||||
.accesskey = s
|
||||
|
||||
sync-device-name-header = Device Name
|
||||
|
||||
sync-device-name-change =
|
||||
.label = Change Device Name…
|
||||
.accesskey = h
|
||||
|
||||
sync-device-name-cancel =
|
||||
.label = Cancel
|
||||
.accesskey = n
|
||||
|
||||
sync-device-name-save =
|
||||
.label = Save
|
||||
.accesskey = v
|
||||
|
||||
sync-mobilepromo-single = Connect another device
|
||||
|
||||
sync-mobilepromo-multi = Manage devices
|
||||
|
||||
sync-tos-link = Terms of Service
|
||||
|
||||
sync-fxa-privacy-notice = Privacy Notice
|
||||
|
||||
## Privacy Section
|
||||
|
||||
privacy-header = Browser Privacy
|
||||
|
@ -1,6 +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/. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE (paneSync1.title): This should match syncBrand.fxAccount.label in ../syncBrand.dtd -->
|
||||
<!ENTITY paneSync1.title "Firefox Account">
|
@ -2,90 +2,6 @@
|
||||
- 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/. -->
|
||||
|
||||
<!-- The page shown when logged in... -->
|
||||
|
||||
<!ENTITY engine.bookmarks.label "Bookmarks">
|
||||
<!ENTITY engine.bookmarks.accesskey "m">
|
||||
<!ENTITY engine.tabs.label2 "Open tabs">
|
||||
<!ENTITY engine.tabs.title "A list of what’s open on all synced devices">
|
||||
<!ENTITY engine.tabs.accesskey "T">
|
||||
<!ENTITY engine.history.label "History">
|
||||
<!ENTITY engine.history.accesskey "r">
|
||||
<!ENTITY engine.logins.label "Logins">
|
||||
<!ENTITY engine.logins.title "Usernames and passwords you’ve saved">
|
||||
<!ENTITY engine.logins.accesskey "L">
|
||||
<!-- On Windows we use the term "Options" to describe settings, but
|
||||
on Linux and Mac OS X we use "Preferences" - carry that distinction
|
||||
over into this string, used as the checkbox which indicates if prefs are synced
|
||||
-->
|
||||
<!ENTITY engine.prefsWin.label "Options">
|
||||
<!ENTITY engine.prefsWin.accesskey "S">
|
||||
<!ENTITY engine.prefs.label "Preferences">
|
||||
<!ENTITY engine.prefs.accesskey "S">
|
||||
<!ENTITY engine.prefs.title "General, Privacy, and Security settings you’ve changed">
|
||||
<!ENTITY engine.addons.label "Add-ons">
|
||||
<!ENTITY engine.addons.title "Extensions and themes for Firefox desktop">
|
||||
<!ENTITY engine.addons.accesskey "A">
|
||||
<!ENTITY engine.addresses.label "Addresses">
|
||||
<!ENTITY engine.addresses.title "Postal addresses you’ve saved (desktop only)">
|
||||
<!ENTITY engine.addresses.accesskey "e">
|
||||
<!ENTITY engine.creditcards.label "Credit cards">
|
||||
<!ENTITY engine.creditcards.title "Names, numbers and expiry dates (desktop only)">
|
||||
<!ENTITY engine.creditcards.accesskey "C">
|
||||
|
||||
<!-- Device Settings -->
|
||||
<!ENTITY fxaSyncDeviceName.label "Device Name">
|
||||
<!ENTITY changeSyncDeviceName2.label "Change Device Name…">
|
||||
<!ENTITY changeSyncDeviceName2.accesskey "h">
|
||||
<!ENTITY cancelChangeSyncDeviceName.label "Cancel">
|
||||
<!ENTITY cancelChangeSyncDeviceName.accesskey "n">
|
||||
<!ENTITY saveChangeSyncDeviceName.label "Save">
|
||||
<!ENTITY saveChangeSyncDeviceName.accesskey "v">
|
||||
|
||||
<!-- Footer stuff -->
|
||||
<!ENTITY prefs.tosLink.label "Terms of Service">
|
||||
<!ENTITY fxaPrivacyNotice.link.label "Privacy Notice">
|
||||
|
||||
<!-- LOCALIZATION NOTE (signedInUnverified.beforename.label,
|
||||
signedInUnverified.aftername.label): these two string are used respectively
|
||||
before and after the account email address. Localizers can use one of them, or
|
||||
both, to better adapt this sentence to their language.
|
||||
-->
|
||||
<!ENTITY signedInUnverified.beforename.label "">
|
||||
<!ENTITY signedInUnverified.aftername.label "is not verified.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (signedInLoginFailure.beforename.label,
|
||||
signedInLoginFailure.aftername.label): these two string are used respectively
|
||||
before and after the account email address. Localizers can use one of them, or
|
||||
both, to better adapt this sentence to their language.
|
||||
-->
|
||||
<!ENTITY signedInLoginFailure.beforename.label "Please sign in to reconnect">
|
||||
<!ENTITY signedInLoginFailure.aftername.label "">
|
||||
|
||||
<!ENTITY notSignedIn.label "You are not signed in.">
|
||||
<!ENTITY signIn.label "Sign in">
|
||||
<!ENTITY signIn.accesskey "g">
|
||||
<!ENTITY profilePicture.tooltip "Change profile picture">
|
||||
<!ENTITY verifiedManage.label "Manage account">
|
||||
<!ENTITY verifiedManage.accesskey "o">
|
||||
<!ENTITY disconnect3.label "Disconnect…">
|
||||
<!ENTITY disconnect3.accesskey "D">
|
||||
<!ENTITY resendVerification.label "Resend Verification">
|
||||
<!ENTITY resendVerification.accesskey "d">
|
||||
<!ENTITY removeAccount.label "Remove Account">
|
||||
<!ENTITY removeAccount.accesskey "R">
|
||||
|
||||
<!ENTITY signedOut.caption "Take Your Web With You">
|
||||
<!ENTITY signedOut.description "Synchronize your bookmarks, history, tabs, passwords, add-ons, and preferences across all your devices.">
|
||||
<!ENTITY signedOut.accountBox.title "Connect with a &syncBrand.fxAccount.label;">
|
||||
<!ENTITY signedOut.accountBox.create2 "Don’t have an account? Get started">
|
||||
<!ENTITY signedOut.accountBox.create2.accesskey "C">
|
||||
<!ENTITY signedOut.accountBox.signin2 "Sign In…">
|
||||
<!ENTITY signedOut.accountBox.signin2.accesskey "I">
|
||||
|
||||
<!ENTITY signedIn.settings.label "Sync Settings">
|
||||
<!ENTITY signedIn.settings.description "Choose what to synchronize on your devices using &brandShortName;.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (mobilePromo3.*): the following strings will be used to
|
||||
create a single sentence with active links.
|
||||
The resulting sentence in English is: "Download Firefox for
|
||||
@ -101,6 +17,3 @@ both, to better adapt this sentence to their language.
|
||||
<!ENTITY mobilePromo3.iOSLink "iOS">
|
||||
|
||||
<!ENTITY mobilePromo3.end " to sync with your mobile device.">
|
||||
|
||||
<!ENTITY mobilepromo.singledevice "Connect another device">
|
||||
<!ENTITY mobilepromo.multidevice "Manage devices">
|
||||
|
@ -70,7 +70,6 @@
|
||||
locale/browser/preferences/languages.dtd (%chrome/browser/preferences/languages.dtd)
|
||||
locale/browser/preferences/main.dtd (%chrome/browser/preferences/main.dtd)
|
||||
locale/browser/preferences/permissions.dtd (%chrome/browser/preferences/permissions.dtd)
|
||||
locale/browser/preferences/preferences.dtd (%chrome/browser/preferences/preferences.dtd)
|
||||
locale/browser/preferences/preferences.properties (%chrome/browser/preferences/preferences.properties)
|
||||
locale/browser/preferences/privacy.dtd (%chrome/browser/preferences/privacy.dtd)
|
||||
locale/browser/preferences/security.dtd (%chrome/browser/preferences/security.dtd)
|
||||
|
@ -604,10 +604,6 @@ button > hbox > label {
|
||||
margin-inline-end: 10px !important;
|
||||
}
|
||||
|
||||
.fxaEmailAddress {
|
||||
margin-inline-end: 8px !important;
|
||||
}
|
||||
|
||||
.fxaLoginRejectedWarning {
|
||||
list-style-image: url(chrome://browser/skin/warning.svg);
|
||||
margin-inline-start: 4px;
|
||||
|
@ -1083,12 +1083,9 @@ public:
|
||||
bool VisitNamedDecl(NamedDecl *D) {
|
||||
SourceLocation Loc = D->getLocation();
|
||||
|
||||
if (isa<EnumConstantDecl>(D) && SM.isMacroBodyExpansion(Loc)) {
|
||||
// for enum constants generated by macro expansion, update location
|
||||
// to point to the expansion location as that is more useful. We might
|
||||
// want to do this for more token types but until we have good regression
|
||||
// testing for the Indexer it's best to be as conservative and explicit
|
||||
// as possible with the changes.
|
||||
// If the token is from a macro expansion and the expansion location
|
||||
// is interesting, use that instead as it tends to be more useful.
|
||||
if (SM.isMacroBodyExpansion(Loc)) {
|
||||
Loc = SM.getFileLoc(Loc);
|
||||
}
|
||||
|
||||
|
@ -130,6 +130,7 @@ support-files =
|
||||
|
||||
[browser_dbg-asm.js]
|
||||
[browser_dbg-async-stepping.js]
|
||||
[browser_dbg-babel-breakpoint-console.js]
|
||||
[browser_dbg-babel-scopes.js]
|
||||
[browser_dbg-babel-stepping.js]
|
||||
[browser_dbg-babel-preview.js]
|
||||
|
@ -0,0 +1,95 @@
|
||||
|
||||
async function evalInConsoleAtPoint(dbg, fixture, { line, column }, statements) {
|
||||
const { selectors: { getBreakpoint, getBreakpoints }, getState } = dbg;
|
||||
|
||||
const filename = `fixtures/${fixture}/input.js`;
|
||||
await waitForSources(dbg, filename);
|
||||
|
||||
ok(true, "Original sources exist");
|
||||
const source = findSource(dbg, filename);
|
||||
|
||||
await selectSource(dbg, source);
|
||||
|
||||
// Test that breakpoint is not off by a line.
|
||||
await addBreakpoint(dbg, source, line);
|
||||
|
||||
is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
|
||||
ok(
|
||||
getBreakpoint(getState(), { sourceId: source.id, line, column }),
|
||||
"Breakpoint has correct line"
|
||||
);
|
||||
|
||||
const fnName = fixture.replace(/-([a-z])/g, (s, c) => c.toUpperCase());
|
||||
|
||||
const invokeResult = invokeInTab(fnName);
|
||||
|
||||
let invokeFailed = await Promise.race([
|
||||
waitForPaused(dbg),
|
||||
invokeResult.then(() => new Promise(() => {}), () => true)
|
||||
]);
|
||||
|
||||
if (invokeFailed) {
|
||||
return invokeResult;
|
||||
}
|
||||
|
||||
assertPausedLocation(dbg);
|
||||
|
||||
await assertConsoleEval(dbg, statements);
|
||||
|
||||
await removeBreakpoint(dbg, source.id, line, column);
|
||||
|
||||
is(getBreakpoints(getState()).size, 0, "Breakpoint reverted");
|
||||
|
||||
await resume(dbg);
|
||||
|
||||
// If the invoke errored later somehow, capture here so the error is reported nicely.
|
||||
await invokeResult;
|
||||
|
||||
ok(true, `Ran tests for ${fixture} at line ${line} column ${column}`);
|
||||
}
|
||||
|
||||
async function assertConsoleEval(dbg, statements) {
|
||||
const jsterm = (await dbg.toolbox.selectTool("webconsole")).hud.jsterm;
|
||||
|
||||
for (const [index, statement] of statements.entries()) {
|
||||
await dbg.client.evaluate(`
|
||||
window.TEST_RESULT = false;
|
||||
`);
|
||||
await jsterm.execute(`
|
||||
TEST_RESULT = ${statement};
|
||||
`);
|
||||
|
||||
const result = await dbg.client.evaluate(`window.TEST_RESULT`);
|
||||
is(result.result, true, `'${statement}' evaluates to true`);
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.debugger.features.map-scopes", true);
|
||||
|
||||
const dbg = await initDebugger("doc-babel.html");
|
||||
|
||||
await evalInConsoleAtPoint(dbg, "eval-source-maps", { line: 14, column: 4 }, [
|
||||
"one === 1",
|
||||
"two === 4",
|
||||
"three === 5",
|
||||
]);
|
||||
|
||||
await evalInConsoleAtPoint(dbg, "imported-bindings", { line: 20, column: 2 }, [
|
||||
`aDefault === "a-default"`,
|
||||
`anAliased === "an-original"`,
|
||||
`aNamed === "a-named"`,
|
||||
`aDefault2 === "a-default2"`,
|
||||
`anAliased2 === "an-original2"`,
|
||||
`aNamed2 === "a-named2"`,
|
||||
`aDefault3 === "a-default3"`,
|
||||
`anAliased3 === "an-original3"`,
|
||||
`aNamed3 === "a-named3"`,
|
||||
]);
|
||||
|
||||
await evalInConsoleAtPoint(dbg, "shadowed-vars", { line: 18, column: 6 }, [
|
||||
`aVar === "var3"`,
|
||||
`aLet === "let3"`,
|
||||
`aConst === "const3"`,
|
||||
]);
|
||||
});
|
@ -131,6 +131,12 @@ DebuggerPanel.prototype = {
|
||||
|
||||
// DebuggerPanel API
|
||||
|
||||
getMappedExpression(expression) {
|
||||
// No-op implementation since this feature doesn't exist in the older
|
||||
// debugger implementation.
|
||||
return expression;
|
||||
},
|
||||
|
||||
isPaused() {
|
||||
let framesController = this.panelWin.DebuggerController.StackFrames;
|
||||
let thread = framesController.activeThread;
|
||||
|
@ -83,10 +83,10 @@ const TEST_DATA_INNER = [
|
||||
["u", "style=\"background:unset", 19, 23, true],
|
||||
["r", "style=\"background:url", 20, 21, false],
|
||||
["l", "style=\"background:url", 21, 21, false],
|
||||
["(", "style=\"background:url(", 22, 22, false],
|
||||
["'", "style=\"background:url('", 23, 23, false],
|
||||
["1", "style=\"background:url('1", 24, 24, false],
|
||||
["'", "style=\"background:url('1'", 25, 25, false],
|
||||
["(", "style=\"background:url()", 22, 22, false],
|
||||
["'", "style=\"background:url(')", 23, 23, false],
|
||||
["1", "style=\"background:url('1)", 24, 24, false],
|
||||
["'", "style=\"background:url('1')", 25, 25, false],
|
||||
[")", "style=\"background:url('1')", 26, 26, false],
|
||||
[";", "style=\"background:url('1');", 27, 27, false],
|
||||
[" ", "style=\"background:url('1'); ", 28, 28, false],
|
||||
|
@ -16,7 +16,7 @@
|
||||
* in view of the tree map.
|
||||
*/
|
||||
const { debounce } = require("devtools/shared/debounce");
|
||||
const EventEmitter = require("devtools/shared/old-event-emitter");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const FULLSCREEN_STYLE = {
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
const { debounce } = require("devtools/shared/debounce");
|
||||
const { lerp } = require("devtools/client/memory/utils");
|
||||
const EventEmitter = require("devtools/shared/old-event-emitter");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const LERP_SPEED = 0.5;
|
||||
const ZOOM_SPEED = 0.01;
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/old-event-emitter");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { MemoryFront } = require("devtools/shared/fronts/memory");
|
||||
const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
|
||||
|
||||
|
@ -26,6 +26,8 @@
|
||||
const Services = require("Services");
|
||||
const focusManager = Services.focus;
|
||||
const {KeyCodes} = require("devtools/client/shared/keycodes");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { findMostRelevantCssPropertyIndex } = require("./suggestion-picker");
|
||||
|
||||
loader.lazyRequireGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm", true);
|
||||
|
||||
@ -44,8 +46,10 @@ const MAX_POPUP_ENTRIES = 500;
|
||||
const FOCUS_FORWARD = focusManager.MOVEFOCUS_FORWARD;
|
||||
const FOCUS_BACKWARD = focusManager.MOVEFOCUS_BACKWARD;
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { findMostRelevantCssPropertyIndex } = require("./suggestion-picker");
|
||||
const WORD_REGEXP = /\w/;
|
||||
const isWordChar = function(str) {
|
||||
return str && WORD_REGEXP.test(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to check if the provided key matches one of the expected keys.
|
||||
@ -1051,6 +1055,10 @@ InplaceEditor.prototype = {
|
||||
let key = event.keyCode;
|
||||
let input = this.input;
|
||||
|
||||
// We want to autoclose some characters, remember the pressed key in order to process
|
||||
// it later on in maybeSuggestionCompletion().
|
||||
this._pressedKey = event.key;
|
||||
|
||||
let multilineNavigation = !this._isSingleLine() &&
|
||||
isKeyIn(key, "UP", "DOWN", "LEFT", "RIGHT");
|
||||
let isPlainText = this.contentType == CONTENT_TYPES.PLAIN_TEXT;
|
||||
@ -1293,6 +1301,7 @@ InplaceEditor.prototype = {
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
|
||||
let preTimeoutQuery = this.input.value;
|
||||
|
||||
// Since we are calling this method from a keypress event handler, the
|
||||
@ -1477,12 +1486,58 @@ InplaceEditor.prototype = {
|
||||
} else {
|
||||
this._hideAutocompletePopup();
|
||||
}
|
||||
|
||||
this._autocloseParenthesis();
|
||||
|
||||
// This emit is mainly for the purpose of making the test flow simpler.
|
||||
this.emit("after-suggest");
|
||||
this._doValidation();
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically add closing parenthesis and skip closing parenthesis when needed.
|
||||
*/
|
||||
_autocloseParenthesis: function() {
|
||||
// Split the current value at the cursor index to rebuild the string.
|
||||
let parts = this._splitStringAt(this.input.value, this.input.selectionStart);
|
||||
|
||||
// Lookup the character following the caret to know if the string should be modified.
|
||||
let nextChar = parts[1][0];
|
||||
|
||||
// Autocomplete closing parenthesis if the last key pressed was "(" and the next
|
||||
// character is not a "word" character.
|
||||
if (this._pressedKey == "(" && !isWordChar(nextChar)) {
|
||||
this._updateValue(parts[0] + ")" + parts[1]);
|
||||
}
|
||||
|
||||
// Skip inserting ")" if the next character is already a ")" (note that we actually
|
||||
// insert and remove the extra ")" here, as the input has already been modified).
|
||||
if (this._pressedKey == ")" && nextChar == ")") {
|
||||
this._updateValue(parts[0] + parts[1].substring(1));
|
||||
}
|
||||
|
||||
this._pressedKey = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the current value of the input while preserving the caret position.
|
||||
*/
|
||||
_updateValue: function(str) {
|
||||
let start = this.input.selectionStart;
|
||||
this.input.value = str;
|
||||
this.input.setSelectionRange(start, start);
|
||||
this._updateSize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Split the provided string at the provided index. Returns an array of two strings.
|
||||
* _splitStringAt("1234567", 3) will return ["123", "4567"]
|
||||
*/
|
||||
_splitStringAt: function(str, index) {
|
||||
return [str.substring(0, index), str.substring(index, str.length)];
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the current input is displaying more than one line of text.
|
||||
*
|
||||
|
@ -147,6 +147,7 @@ skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
|
||||
[browser_html_tooltip_xul-wrapper.js]
|
||||
[browser_inplace-editor-01.js]
|
||||
[browser_inplace-editor-02.js]
|
||||
[browser_inplace-editor_autoclose_parentheses.js]
|
||||
[browser_inplace-editor_autocomplete_01.js]
|
||||
[browser_inplace-editor_autocomplete_02.js]
|
||||
[browser_inplace-editor_autocomplete_offset.js]
|
||||
|
@ -0,0 +1,73 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_inplace_editor.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
|
||||
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
|
||||
loadHelperScript("helper_inplace_editor.js");
|
||||
|
||||
// Test the inplace-editor closes parentheses automatically.
|
||||
|
||||
// format :
|
||||
// [
|
||||
// what key to press,
|
||||
// expected input box value after keypress,
|
||||
// selected suggestion index (-1 if popup is hidden),
|
||||
// number of suggestions in the popup (0 if popup is hidden),
|
||||
// ]
|
||||
const testData = [
|
||||
["u", "u", -1, 0],
|
||||
["r", "ur", -1, 0],
|
||||
["l", "url", -1, 0],
|
||||
["(", "url()", -1, 0],
|
||||
["v", "url(v)", -1, 0],
|
||||
["a", "url(va)", -1, 0],
|
||||
["r", "url(var)", -1, 0],
|
||||
["(", "url(var())", -1, 0],
|
||||
["-", "url(var(-))", -1, 0],
|
||||
["-", "url(var(--))", -1, 0],
|
||||
["a", "url(var(--a))", -1, 0],
|
||||
[")", "url(var(--a))", -1, 0],
|
||||
[")", "url(var(--a))", -1, 0],
|
||||
];
|
||||
|
||||
add_task(async function() {
|
||||
await addTab("data:text/html;charset=utf-8," +
|
||||
"inplace editor parentheses autoclose");
|
||||
let [host, win, doc] = await createHost();
|
||||
|
||||
let xulDocument = win.top.document;
|
||||
let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
|
||||
await new Promise(resolve => {
|
||||
createInplaceEditorAndClick({
|
||||
start: runPropertyAutocompletionTest,
|
||||
contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
|
||||
property: {
|
||||
name: "background-image"
|
||||
},
|
||||
cssVariables: new Map(),
|
||||
done: resolve,
|
||||
popup: popup
|
||||
}, doc);
|
||||
});
|
||||
|
||||
popup.destroy();
|
||||
host.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
let runPropertyAutocompletionTest = async function(editor) {
|
||||
info("Starting to test for css property completion");
|
||||
|
||||
// No need to test autocompletion here, return an empty array.
|
||||
editor._getCSSValuesForPropertyName = () => [];
|
||||
|
||||
for (let data of testData) {
|
||||
await testCompletion(data, editor);
|
||||
}
|
||||
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
|
||||
};
|
@ -27,16 +27,16 @@ const testData = [
|
||||
["v", "v", -1, 0, null],
|
||||
["a", "va", -1, 0, null],
|
||||
["r", "var", -1, 0, null],
|
||||
["(", "var(", -1, 0, null],
|
||||
["-", "var(--abc", 0, 4, "blue"],
|
||||
["VK_BACK_SPACE", "var(-", -1, 0, null],
|
||||
["-", "var(--abc", 0, 4, "blue"],
|
||||
["VK_DOWN", "var(--def", 1, 4, "red"],
|
||||
["VK_DOWN", "var(--ghi", 2, 4, "green"],
|
||||
["VK_DOWN", "var(--jkl", 3, 4, "yellow"],
|
||||
["VK_DOWN", "var(--abc", 0, 4, "blue"],
|
||||
["VK_DOWN", "var(--def", 1, 4, "red"],
|
||||
["VK_LEFT", "var(--def", -1, 0, null],
|
||||
["(", "var()", -1, 0, null],
|
||||
["-", "var(--abc)", 0, 4, "blue"],
|
||||
["VK_BACK_SPACE", "var(-)", -1, 0, null],
|
||||
["-", "var(--abc)", 0, 4, "blue"],
|
||||
["VK_DOWN", "var(--def)", 1, 4, "red"],
|
||||
["VK_DOWN", "var(--ghi)", 2, 4, "green"],
|
||||
["VK_DOWN", "var(--jkl)", 3, 4, "yellow"],
|
||||
["VK_DOWN", "var(--abc)", 0, 4, "blue"],
|
||||
["VK_DOWN", "var(--def)", 1, 4, "red"],
|
||||
["VK_LEFT", "var(--def)", -1, 0, null],
|
||||
];
|
||||
|
||||
const CSS_VARIABLES = [
|
||||
|
@ -482,6 +482,20 @@ WebConsole.prototype = {
|
||||
return panel.getFrames();
|
||||
},
|
||||
|
||||
async getMappedExpression(expression) {
|
||||
let toolbox = gDevTools.getToolbox(this.target);
|
||||
if (!toolbox) {
|
||||
return expression;
|
||||
}
|
||||
let panel = toolbox.getPanel("jsdebugger");
|
||||
|
||||
if (!panel) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
return panel.getMappedExpression(expression);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the current selection from the Inspector, if such a selection
|
||||
* exists. This is used to pass the ID of the selected actor to the Web
|
||||
|
@ -436,7 +436,7 @@ JSTerm.prototype = {
|
||||
* @returns Promise
|
||||
* Resolves with the message once the result is displayed.
|
||||
*/
|
||||
execute: function(executeString, callback) {
|
||||
execute: async function(executeString, callback) {
|
||||
let deferred = defer();
|
||||
let resultCallback;
|
||||
if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
|
||||
@ -456,6 +456,21 @@ JSTerm.prototype = {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Append a new value in the history of executed code, or overwrite the most
|
||||
// recent entry. The most recent entry may contain the last edited input
|
||||
// value that was not evaluated yet.
|
||||
this.history[this.historyIndex++] = executeString;
|
||||
this.historyPlaceHolder = this.history.length;
|
||||
|
||||
if (this.history.length > this.inputHistoryCount) {
|
||||
this.history.splice(0, this.history.length - this.inputHistoryCount);
|
||||
this.historyIndex = this.historyPlaceHolder = this.history.length;
|
||||
}
|
||||
this.storeHistory();
|
||||
WebConsoleUtils.usageCount++;
|
||||
this.setInputValue("");
|
||||
this.clearCompletion();
|
||||
|
||||
let selectedNodeActor = null;
|
||||
let inspectorSelection = this.hud.owner.getInspectorSelection();
|
||||
if (inspectorSelection && inspectorSelection.nodeFront) {
|
||||
@ -482,22 +497,9 @@ JSTerm.prototype = {
|
||||
selectedNodeActor: selectedNodeActor,
|
||||
};
|
||||
|
||||
this.requestEvaluation(executeString, options).then(onResult, onResult);
|
||||
const mappedString = await this.hud.owner.getMappedExpression(executeString);
|
||||
this.requestEvaluation(mappedString, options).then(onResult, onResult);
|
||||
|
||||
// Append a new value in the history of executed code, or overwrite the most
|
||||
// recent entry. The most recent entry may contain the last edited input
|
||||
// value that was not evaluated yet.
|
||||
this.history[this.historyIndex++] = executeString;
|
||||
this.historyPlaceHolder = this.history.length;
|
||||
|
||||
if (this.history.length > this.inputHistoryCount) {
|
||||
this.history.splice(0, this.history.length - this.inputHistoryCount);
|
||||
this.historyIndex = this.historyPlaceHolder = this.history.length;
|
||||
}
|
||||
this.storeHistory();
|
||||
WebConsoleUtils.usageCount++;
|
||||
this.setInputValue("");
|
||||
this.clearCompletion();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
|
@ -13,10 +13,10 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
|
||||
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
testEditedInputHistory(hud);
|
||||
await testEditedInputHistory(hud);
|
||||
});
|
||||
|
||||
function testEditedInputHistory(hud) {
|
||||
async function testEditedInputHistory(hud) {
|
||||
let jsterm = hud.jsterm;
|
||||
let inputNode = jsterm.inputNode;
|
||||
|
||||
@ -30,7 +30,7 @@ function testEditedInputHistory(hud) {
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
is(jsterm.getInputValue(), '"first item"', "null test history down");
|
||||
|
||||
jsterm.execute();
|
||||
await jsterm.execute();
|
||||
is(jsterm.getInputValue(), "", "cleared input line after submit");
|
||||
|
||||
jsterm.setInputValue('"editing input 1"');
|
||||
@ -41,7 +41,7 @@ function testEditedInputHistory(hud) {
|
||||
"test history down restores in-progress input");
|
||||
|
||||
jsterm.setInputValue('"second item"');
|
||||
jsterm.execute();
|
||||
await jsterm.execute();
|
||||
jsterm.setInputValue('"editing input 2"');
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
is(jsterm.getInputValue(), '"second item"', "test history up");
|
||||
|
@ -23,7 +23,7 @@ add_task(async function() {
|
||||
|
||||
testSingleLineInputNavNoHistory(jsterm);
|
||||
testMultiLineInputNavNoHistory(jsterm);
|
||||
testNavWithHistory(jsterm);
|
||||
await testNavWithHistory(jsterm);
|
||||
});
|
||||
|
||||
function testSingleLineInputNavNoHistory(jsterm) {
|
||||
@ -151,7 +151,7 @@ function testMultiLineInputNavNoHistory(jsterm) {
|
||||
}
|
||||
}
|
||||
|
||||
function testNavWithHistory(jsterm) {
|
||||
async function testNavWithHistory(jsterm) {
|
||||
let inputNode = jsterm.inputNode;
|
||||
|
||||
// NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
|
||||
@ -165,7 +165,7 @@ function testNavWithHistory(jsterm) {
|
||||
// submit to history
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
jsterm.setInputValue(values[i]);
|
||||
jsterm.execute();
|
||||
await jsterm.execute();
|
||||
}
|
||||
is(inputNode.selectionStart, 0, "caret location at start of empty line");
|
||||
|
||||
|
@ -20,7 +20,7 @@ add_task(async function() {
|
||||
ok(false, "popup shown");
|
||||
};
|
||||
|
||||
jsterm.execute(`window.foobarBug660806 = {
|
||||
await jsterm.execute(`window.foobarBug660806 = {
|
||||
'location': 'value0',
|
||||
'locationbar': 'value1'
|
||||
}`);
|
||||
@ -36,6 +36,9 @@ add_task(async function() {
|
||||
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
|
||||
// Wait for the execution to complete and clear the value.
|
||||
await waitFor(() => !jsterm.lastInputValue);
|
||||
|
||||
let onSetInputValue = jsterm.once("set-input-value");
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
await onSetInputValue;
|
||||
|
@ -49,7 +49,7 @@ add_task(async function() {
|
||||
// Set input value separately from execute so UP arrow accurately navigates
|
||||
// history.
|
||||
hud3.jsterm.setInputValue('"hello from third tab"');
|
||||
hud3.jsterm.execute();
|
||||
await hud3.jsterm.execute();
|
||||
|
||||
is(JSON.stringify(hud1.jsterm.history),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
|
@ -61,6 +61,8 @@ add_task(async function() {
|
||||
hud.jsterm.setInputValue(input);
|
||||
EventUtils.synthesizeKey("VK_RETURN", { shiftKey });
|
||||
|
||||
await waitFor(() => !hud.jsterm.getInputValue());
|
||||
|
||||
let inputValue = hud.jsterm.getInputValue();
|
||||
is(inputNode.selectionStart, 0, "selection starts/ends at 0");
|
||||
is(inputNode.selectionEnd, 0, "selection starts/ends at 0");
|
||||
|
@ -28,7 +28,7 @@ add_task(async function() {
|
||||
info("Execute each test value in the console");
|
||||
for (let value of TEST_VALUES) {
|
||||
jsterm.setInputValue(value);
|
||||
jsterm.execute();
|
||||
await jsterm.execute();
|
||||
}
|
||||
|
||||
performTests(jsterm);
|
||||
|
@ -53,7 +53,7 @@ add_task(function* () {
|
||||
// Set input value separately from execute so UP arrow accurately navigates
|
||||
// history.
|
||||
hud3.jsterm.setInputValue('"hello from third tab"');
|
||||
hud3.jsterm.execute();
|
||||
yield hud3.jsterm.execute();
|
||||
|
||||
is(JSON.stringify(hud1.jsterm.history),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
@ -98,7 +98,7 @@ function* populateInputHistory(hud) {
|
||||
// Set input value separately from execute so UP arrow accurately navigates
|
||||
// history.
|
||||
jsterm.setInputValue(i);
|
||||
jsterm.execute();
|
||||
yield jsterm.execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ function test() {
|
||||
yield toolbox.selectTool("webconsole");
|
||||
|
||||
// This is the meat of the test: evaluate the optimized out variable.
|
||||
hud.jsterm.execute("upvar");
|
||||
yield hud.jsterm.execute("upvar");
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
|
@ -15,13 +15,13 @@ add_task(function* () {
|
||||
|
||||
let hud = yield openConsole();
|
||||
|
||||
setup(hud);
|
||||
yield setup(hud);
|
||||
performTests();
|
||||
|
||||
jsterm = inputNode = values = null;
|
||||
});
|
||||
|
||||
function setup(HUD) {
|
||||
function* setup(HUD) {
|
||||
jsterm = HUD.jsterm;
|
||||
inputNode = jsterm.inputNode;
|
||||
|
||||
@ -35,7 +35,7 @@ function setup(HUD) {
|
||||
// Execute each of the values;
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
jsterm.setInputValue(values[i]);
|
||||
jsterm.execute();
|
||||
yield jsterm.execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,12 +17,12 @@ add_task(function* () {
|
||||
|
||||
let hud = yield openConsole();
|
||||
|
||||
doTests(hud);
|
||||
yield doTests(hud);
|
||||
|
||||
jsterm = inputNode = null;
|
||||
});
|
||||
|
||||
function doTests(HUD) {
|
||||
function* doTests(HUD) {
|
||||
jsterm = HUD.jsterm;
|
||||
inputNode = jsterm.inputNode;
|
||||
ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
|
||||
@ -31,7 +31,7 @@ function doTests(HUD) {
|
||||
|
||||
testSingleLineInputNavNoHistory();
|
||||
testMultiLineInputNavNoHistory();
|
||||
testNavWithHistory();
|
||||
yield testNavWithHistory();
|
||||
}
|
||||
|
||||
function testSingleLineInputNavNoHistory() {
|
||||
@ -157,7 +157,7 @@ function testMultiLineInputNavNoHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
function testNavWithHistory() {
|
||||
function* testNavWithHistory() {
|
||||
// NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
|
||||
// caret placed _within_ single line input
|
||||
let values = ['"single line input"',
|
||||
@ -167,7 +167,7 @@ function testNavWithHistory() {
|
||||
// submit to history
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
jsterm.setInputValue(values[i]);
|
||||
jsterm.execute();
|
||||
yield jsterm.execute();
|
||||
}
|
||||
is(inputNode.selectionStart, 0, "caret location at start of empty line");
|
||||
|
||||
|
@ -11,15 +11,15 @@
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
|
||||
|
||||
add_task(function* () {
|
||||
yield loadTab(TEST_URI);
|
||||
add_task(async function () {
|
||||
await loadTab(TEST_URI);
|
||||
|
||||
let hud = yield openConsole();
|
||||
let hud = await openConsole();
|
||||
|
||||
testEditedInputHistory(hud);
|
||||
await testEditedInputHistory(hud);
|
||||
});
|
||||
|
||||
function testEditedInputHistory(HUD) {
|
||||
async function testEditedInputHistory(HUD) {
|
||||
let jsterm = HUD.jsterm;
|
||||
let inputNode = jsterm.inputNode;
|
||||
ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
|
||||
@ -32,7 +32,7 @@ function testEditedInputHistory(HUD) {
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
is(jsterm.getInputValue(), '"first item"', "null test history down");
|
||||
|
||||
jsterm.execute();
|
||||
await jsterm.execute();
|
||||
is(jsterm.getInputValue(), "", "cleared input line after submit");
|
||||
|
||||
jsterm.setInputValue('"editing input 1"');
|
||||
@ -43,7 +43,7 @@ function testEditedInputHistory(HUD) {
|
||||
"test history down restores in-progress input");
|
||||
|
||||
jsterm.setInputValue('"second item"');
|
||||
jsterm.execute();
|
||||
await jsterm.execute();
|
||||
jsterm.setInputValue('"editing input 2"');
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
is(jsterm.getInputValue(), '"second item"', "test history up");
|
||||
|
@ -61,6 +61,9 @@ add_task(function* () {
|
||||
for (let test of SHOULD_EXECUTE) {
|
||||
hud.jsterm.setInputValue(test.input);
|
||||
EventUtils.synthesizeKey("VK_RETURN", { shiftKey: test.shiftKey });
|
||||
|
||||
yield waitFor(() => !hud.jsterm.getInputValue());
|
||||
|
||||
let inputValue = hud.jsterm.getInputValue();
|
||||
is(inputNode.selectionStart, 0, "selection starts/ends at 0");
|
||||
is(inputNode.selectionEnd, 0, "selection starts/ends at 0");
|
||||
|
@ -129,6 +129,25 @@ function afterAllTabsLoaded(callback, win) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a predicate to return a result.
|
||||
*
|
||||
* @param function condition
|
||||
* Invoked once in a while until it returns a truthy value. This should be an
|
||||
* idempotent function, since we have to run it a second time after it returns
|
||||
* true in order to return the value.
|
||||
* @param string message [optional]
|
||||
* A message to output if the condition fails.
|
||||
* @param number interval [optional]
|
||||
* How often the predicate is invoked, in milliseconds.
|
||||
* @return object
|
||||
* A promise that is resolved with the result of the condition.
|
||||
*/
|
||||
async function waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
|
||||
await BrowserTestUtils.waitForCondition(condition, message, interval, maxTries);
|
||||
return condition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a log entry exists in the HUD output node.
|
||||
*
|
||||
|
85
devtools/server/actors/array-buffer.js
Normal file
85
devtools/server/actors/array-buffer.js
Normal file
@ -0,0 +1,85 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Creates an actor for the specified ArrayBuffer.
|
||||
*
|
||||
* @param buffer ArrayBuffer
|
||||
* The buffer.
|
||||
*/
|
||||
function ArrayBufferActor(buffer) {
|
||||
this.buffer = buffer;
|
||||
this.bufferLength = buffer.byteLength;
|
||||
}
|
||||
|
||||
ArrayBufferActor.prototype = {
|
||||
actorPrefix: "arrayBuffer",
|
||||
|
||||
rawValue: function() {
|
||||
return this.buffer;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
},
|
||||
|
||||
grip() {
|
||||
return {
|
||||
"type": "arrayBuffer",
|
||||
"length": this.bufferLength,
|
||||
"actor": this.actorID
|
||||
};
|
||||
},
|
||||
|
||||
onSlice({start, count}) {
|
||||
let slice = new Uint8Array(this.buffer, start, count);
|
||||
let parts = [], offset = 0;
|
||||
const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
|
||||
while (offset + PortionSize < count) {
|
||||
parts.push(btoa(
|
||||
String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
|
||||
offset += PortionSize;
|
||||
}
|
||||
parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
|
||||
return {
|
||||
"from": this.actorID,
|
||||
"encoded": parts.join(""),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
ArrayBufferActor.prototype.requestTypes = {
|
||||
"slice": ArrayBufferActor.prototype.onSlice,
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a grip for the given ArrayBuffer.
|
||||
*
|
||||
* @param buffer ArrayBuffer
|
||||
* The ArrayBuffer we are creating a grip for.
|
||||
* @param pool ActorPool
|
||||
* The actor pool where the new actor will be added.
|
||||
*/
|
||||
function arrayBufferGrip(buffer, pool) {
|
||||
if (!pool.arrayBufferActors) {
|
||||
pool.arrayBufferActors = new WeakMap();
|
||||
}
|
||||
|
||||
if (pool.arrayBufferActors.has(buffer)) {
|
||||
return pool.arrayBufferActors.get(buffer).grip();
|
||||
}
|
||||
|
||||
let actor = new ArrayBufferActor(buffer);
|
||||
pool.addActor(actor);
|
||||
pool.arrayBufferActors.set(buffer, actor);
|
||||
return actor.grip();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ArrayBufferActor,
|
||||
arrayBufferGrip,
|
||||
};
|
@ -8,7 +8,7 @@
|
||||
/* global Debugger */
|
||||
|
||||
const { ActorClassWithSpec } = require("devtools/shared/protocol");
|
||||
const { createValueGrip } = require("devtools/server/actors/object");
|
||||
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
||||
const { environmentSpec } = require("devtools/shared/specs/environment");
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@
|
||||
"use strict";
|
||||
|
||||
const { ActorPool } = require("devtools/server/actors/common");
|
||||
const { createValueGrip } = require("devtools/server/actors/object");
|
||||
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
||||
const { ActorClassWithSpec } = require("devtools/shared/protocol");
|
||||
const { frameSpec } = require("devtools/shared/specs/frame");
|
||||
|
||||
|
@ -9,6 +9,7 @@ DIRS += [
|
||||
'emulation',
|
||||
'highlighters',
|
||||
'inspector',
|
||||
'object',
|
||||
'utils',
|
||||
'webconsole',
|
||||
'worker',
|
||||
@ -21,6 +22,7 @@ DevToolsModules(
|
||||
'addon.js',
|
||||
'addons.js',
|
||||
'animation.js',
|
||||
'array-buffer.js',
|
||||
'breakpoint.js',
|
||||
'call-watcher.js',
|
||||
'canvas.js',
|
||||
|
File diff suppressed because it is too large
Load Diff
115
devtools/server/actors/object/long-string.js
Normal file
115
devtools/server/actors/object/long-string.js
Normal file
@ -0,0 +1,115 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
|
||||
/**
|
||||
* Creates an actor for the specified "very long" string. "Very long" is specified
|
||||
* at the server's discretion.
|
||||
* There is a newer, protocol.js based LongString actor in
|
||||
* devtools/server/actors/string.js and if you can you should use this one.
|
||||
*
|
||||
* @param string String
|
||||
* The string.
|
||||
*/
|
||||
function LongStringActor(string) {
|
||||
this.string = string;
|
||||
this.stringLength = string.length;
|
||||
}
|
||||
|
||||
LongStringActor.prototype = {
|
||||
actorPrefix: "longString",
|
||||
|
||||
rawValue: function() {
|
||||
return this.string;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
// Because longStringActors is not a weak map, we won't automatically leave
|
||||
// it so we need to manually leave on destroy so that we don't leak
|
||||
// memory.
|
||||
this._releaseActor();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a grip for this actor for returning in a protocol message.
|
||||
*/
|
||||
grip: function() {
|
||||
return {
|
||||
"type": "longString",
|
||||
"initial": this.string.substring(
|
||||
0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
|
||||
"length": this.stringLength,
|
||||
"actor": this.actorID
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a request to extract part of this actor's string.
|
||||
*
|
||||
* @param request object
|
||||
* The protocol request object.
|
||||
*/
|
||||
onSubstring: function(request) {
|
||||
return {
|
||||
"from": this.actorID,
|
||||
"substring": this.string.substring(request.start, request.end)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a request to release this LongStringActor instance.
|
||||
*/
|
||||
onRelease: function() {
|
||||
// TODO: also check if registeredPool === threadActor.threadLifetimePool
|
||||
// when the web console moves away from manually releasing pause-scoped
|
||||
// actors.
|
||||
this._releaseActor();
|
||||
this.registeredPool.removeActor(this);
|
||||
return {};
|
||||
},
|
||||
|
||||
_releaseActor: function() {
|
||||
if (this.registeredPool && this.registeredPool.longStringActors) {
|
||||
delete this.registeredPool.longStringActors[this.string];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LongStringActor.prototype.requestTypes = {
|
||||
"substring": LongStringActor.prototype.onSubstring,
|
||||
"release": LongStringActor.prototype.onRelease
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a grip for the given string.
|
||||
*
|
||||
* @param str String
|
||||
* The string we are creating a grip for.
|
||||
* @param pool ActorPool
|
||||
* The actor pool where the new actor will be added.
|
||||
*/
|
||||
function longStringGrip(str, pool) {
|
||||
if (!pool.longStringActors) {
|
||||
pool.longStringActors = {};
|
||||
}
|
||||
|
||||
if (pool.longStringActors.hasOwnProperty(str)) {
|
||||
return pool.longStringActors[str].grip();
|
||||
}
|
||||
|
||||
let actor = new LongStringActor(str);
|
||||
pool.addActor(actor);
|
||||
pool.longStringActors[str] = actor;
|
||||
return actor.grip();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
LongStringActor,
|
||||
longStringGrip,
|
||||
};
|
13
devtools/server/actors/object/moz.build
Normal file
13
devtools/server/actors/object/moz.build
Normal file
@ -0,0 +1,13 @@
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'long-string.js',
|
||||
'previewers.js',
|
||||
'property-iterator.js',
|
||||
'stringifiers.js',
|
||||
'symbol-iterator.js',
|
||||
'symbol.js',
|
||||
'utils.js',
|
||||
)
|
819
devtools/server/actors/object/previewers.js
Normal file
819
devtools/server/actors/object/previewers.js
Normal file
@ -0,0 +1,819 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cu, Ci } = require("chrome");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
loader.lazyRequireGetter(this, "ObjectUtils", "devtools/server/actors/object/utils");
|
||||
loader.lazyRequireGetter(this, "PropertyIterators", "devtools/server/actors/object/property-iterator");
|
||||
|
||||
// Number of items to preview in objects, arrays, maps, sets, lists,
|
||||
// collections, etc.
|
||||
const OBJECT_PREVIEW_MAX_ITEMS = 10;
|
||||
|
||||
/**
|
||||
* Functions for adding information to ObjectActor grips for the purpose of
|
||||
* having customized output. This object holds arrays mapped by
|
||||
* Debugger.Object.prototype.class.
|
||||
*
|
||||
* In each array you can add functions that take three
|
||||
* arguments:
|
||||
* - the ObjectActor instance and its hooks to make a preview for,
|
||||
* - the grip object being prepared for the client,
|
||||
* - the raw JS object after calling Debugger.Object.unsafeDereference(). This
|
||||
* argument is only provided if the object is safe for reading properties and
|
||||
* executing methods. See DevToolsUtils.isSafeJSObject().
|
||||
*
|
||||
* Functions must return false if they cannot provide preview
|
||||
* information for the debugger object, or true otherwise.
|
||||
*/
|
||||
const previewers = {
|
||||
String: [function(objectActor, grip, rawObj) {
|
||||
return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
|
||||
}],
|
||||
|
||||
Boolean: [function(objectActor, grip, rawObj) {
|
||||
return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
|
||||
}],
|
||||
|
||||
Number: [function(objectActor, grip, rawObj) {
|
||||
return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
|
||||
}],
|
||||
|
||||
Symbol: [function(objectActor, grip, rawObj) {
|
||||
return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
|
||||
}],
|
||||
|
||||
Function: [function({obj, hooks}, grip) {
|
||||
if (obj.name) {
|
||||
grip.name = obj.name;
|
||||
}
|
||||
|
||||
if (obj.displayName) {
|
||||
grip.displayName = obj.displayName.substr(0, 500);
|
||||
}
|
||||
|
||||
if (obj.parameterNames) {
|
||||
grip.parameterNames = obj.parameterNames;
|
||||
}
|
||||
|
||||
// Check if the developer has added a de-facto standard displayName
|
||||
// property for us to use.
|
||||
let userDisplayName;
|
||||
try {
|
||||
userDisplayName = obj.getOwnPropertyDescriptor("displayName");
|
||||
} catch (e) {
|
||||
// The above can throw "permission denied" errors when the debuggee
|
||||
// does not subsume the function's compartment.
|
||||
}
|
||||
|
||||
if (userDisplayName && typeof userDisplayName.value == "string" &&
|
||||
userDisplayName.value) {
|
||||
grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
|
||||
}
|
||||
|
||||
let dbgGlobal = hooks.getGlobalDebugObject();
|
||||
if (dbgGlobal) {
|
||||
let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
|
||||
if (script) {
|
||||
grip.location = {
|
||||
url: script.url,
|
||||
line: script.startLine
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
RegExp: [function({obj, hooks}, grip) {
|
||||
let str = DevToolsUtils.callPropertyOnObject(obj, "toString");
|
||||
if (typeof str != "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.displayString = hooks.createValueGrip(str);
|
||||
return true;
|
||||
}],
|
||||
|
||||
Date: [function({obj, hooks}, grip) {
|
||||
let time = DevToolsUtils.callPropertyOnObject(obj, "getTime");
|
||||
if (typeof time != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
timestamp: hooks.createValueGrip(time),
|
||||
};
|
||||
return true;
|
||||
}],
|
||||
|
||||
Array: [function({obj, hooks}, grip) {
|
||||
let length = ObjectUtils.getArrayLength(obj);
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: length,
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = obj.unsafeDereference();
|
||||
let items = grip.preview.items = [];
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
// Array Xrays filter out various possibly-unsafe properties (like
|
||||
// functions, and claim that the value is undefined instead. This
|
||||
// is generally the right thing for privileged code accessing untrusted
|
||||
// objects, but quite confusing for Object previews. So we manually
|
||||
// override this protection by waiving Xrays on the array, and re-applying
|
||||
// Xrays on any indexed value props that we pull off of it.
|
||||
let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
|
||||
if (desc && !desc.get && !desc.set) {
|
||||
let value = Cu.unwaiveXrays(desc.value);
|
||||
value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, value);
|
||||
items.push(hooks.createValueGrip(value));
|
||||
} else {
|
||||
items.push(null);
|
||||
}
|
||||
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
Set: [function(objectActor, grip) {
|
||||
let size = DevToolsUtils.getProperty(objectActor.obj, "size");
|
||||
if (typeof size != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: size,
|
||||
};
|
||||
|
||||
// Avoid recursive object grips.
|
||||
if (objectActor.hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let items = grip.preview.items = [];
|
||||
for (let item of PropertyIterators.enumSetEntries(objectActor)) {
|
||||
items.push(item);
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
WeakSet: [function(objectActor, grip) {
|
||||
let enumEntries = PropertyIterators.enumWeakSetEntries(objectActor);
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: enumEntries.size
|
||||
};
|
||||
|
||||
// Avoid recursive object grips.
|
||||
if (objectActor.hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let items = grip.preview.items = [];
|
||||
for (let item of enumEntries) {
|
||||
items.push(item);
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
Map: [function(objectActor, grip) {
|
||||
let size = DevToolsUtils.getProperty(objectActor.obj, "size");
|
||||
if (typeof size != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: size,
|
||||
};
|
||||
|
||||
if (objectActor.hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let entries = grip.preview.entries = [];
|
||||
for (let entry of PropertyIterators.enumMapEntries(objectActor)) {
|
||||
entries.push(entry);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
WeakMap: [function(objectActor, grip) {
|
||||
let enumEntries = PropertyIterators.enumWeakMapEntries(objectActor);
|
||||
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: enumEntries.size
|
||||
};
|
||||
|
||||
if (objectActor.hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let entries = grip.preview.entries = [];
|
||||
for (let entry of enumEntries) {
|
||||
entries.push(entry);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
DOMStringMap: [function({obj, hooks}, grip, rawObj) {
|
||||
if (!rawObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let keys = obj.getOwnPropertyNames();
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: keys.length,
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let entries = grip.preview.entries = [];
|
||||
for (let key of keys) {
|
||||
let value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, rawObj[key]);
|
||||
entries.push([key, hooks.createValueGrip(value)]);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
Proxy: [function({obj, hooks}, grip, rawObj) {
|
||||
// The `isProxy` getter of the debuggee object only detects proxies without
|
||||
// security wrappers. If false, the target and handler are not available.
|
||||
let hasTargetAndHandler = obj.isProxy;
|
||||
if (hasTargetAndHandler) {
|
||||
grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
|
||||
grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "Object",
|
||||
ownProperties: Object.create(null),
|
||||
ownPropertiesLength: 2 * hasTargetAndHandler
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasTargetAndHandler) {
|
||||
grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
|
||||
grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic previewer for classes wrapping primitives, like String,
|
||||
* Number and Boolean.
|
||||
*
|
||||
* @param string className
|
||||
* Class name to expect.
|
||||
* @param object classObj
|
||||
* The class to expect, eg. String. The valueOf() method of the class is
|
||||
* invoked on the given object.
|
||||
* @param ObjectActor objectActor
|
||||
* The object actor
|
||||
* @param Object grip
|
||||
* The result grip to fill in
|
||||
* @return Booolean true if the object was handled, false otherwise
|
||||
*/
|
||||
function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
|
||||
let {obj, hooks} = objectActor;
|
||||
|
||||
let v = null;
|
||||
try {
|
||||
v = classObj.prototype.valueOf.call(rawObj);
|
||||
} catch (ex) {
|
||||
// valueOf() can throw if the raw JS object is "misbehaved".
|
||||
return false;
|
||||
}
|
||||
|
||||
if (v === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
|
||||
if (!canHandle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview.wrappedValue =
|
||||
hooks.createValueGrip(ObjectUtils.makeDebuggeeValueIfNeeded(obj, v));
|
||||
return true;
|
||||
}
|
||||
|
||||
function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
|
||||
let {obj, hooks} = objectActor;
|
||||
if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let i = 0, names = [], symbols = [];
|
||||
let preview = grip.preview = {
|
||||
kind: "Object",
|
||||
ownProperties: Object.create(null),
|
||||
ownSymbols: [],
|
||||
};
|
||||
|
||||
try {
|
||||
names = obj.getOwnPropertyNames();
|
||||
symbols = obj.getOwnPropertySymbols();
|
||||
} catch (ex) {
|
||||
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
|
||||
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
|
||||
}
|
||||
preview.ownPropertiesLength = names.length;
|
||||
preview.ownSymbolsLength = symbols.length;
|
||||
|
||||
let length;
|
||||
if (specialStringBehavior) {
|
||||
length = DevToolsUtils.getProperty(obj, "length");
|
||||
if (typeof length != "number") {
|
||||
specialStringBehavior = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let name of names) {
|
||||
if (specialStringBehavior && /^[0-9]+$/.test(name)) {
|
||||
let num = parseInt(name, 10);
|
||||
if (num.toString() === name && num >= 0 && num < length) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let desc = objectActor._propertyDescriptor(name, true);
|
||||
if (!desc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
preview.ownProperties[name] = desc;
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let symbol of symbols) {
|
||||
let descriptor = objectActor._propertyDescriptor(symbol, true);
|
||||
if (!descriptor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
preview.ownSymbols.push(Object.assign({
|
||||
descriptor
|
||||
}, hooks.createValueGrip(symbol)));
|
||||
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
preview.safeGetterValues = objectActor._findSafeGetterValues(
|
||||
Object.keys(preview.ownProperties),
|
||||
OBJECT_PREVIEW_MAX_ITEMS - i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Preview functions that do not rely on the object class.
|
||||
previewers.Object = [
|
||||
function TypedArray({obj, hooks}, grip) {
|
||||
if (!ObjectUtils.isTypedArray(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: ObjectUtils.getArrayLength(obj),
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = obj.unsafeDereference();
|
||||
let global = Cu.getGlobalForObject(DebuggerServer);
|
||||
let classProto = global[obj.class].prototype;
|
||||
// The Xray machinery for TypedArrays denies indexed access on the grounds
|
||||
// that it's slow, and advises callers to do a structured clone instead.
|
||||
let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
|
||||
OBJECT_PREVIEW_MAX_ITEMS), global);
|
||||
let items = grip.preview.items = [];
|
||||
for (let i = 0; i < safeView.length; i++) {
|
||||
items.push(safeView[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function Error({obj, hooks}, grip) {
|
||||
switch (obj.class) {
|
||||
case "Error":
|
||||
case "EvalError":
|
||||
case "RangeError":
|
||||
case "ReferenceError":
|
||||
case "SyntaxError":
|
||||
case "TypeError":
|
||||
case "URIError":
|
||||
let name = DevToolsUtils.getProperty(obj, "name");
|
||||
let msg = DevToolsUtils.getProperty(obj, "message");
|
||||
let stack = DevToolsUtils.getProperty(obj, "stack");
|
||||
let fileName = DevToolsUtils.getProperty(obj, "fileName");
|
||||
let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
|
||||
let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
|
||||
grip.preview = {
|
||||
kind: "Error",
|
||||
name: hooks.createValueGrip(name),
|
||||
message: hooks.createValueGrip(msg),
|
||||
stack: hooks.createValueGrip(stack),
|
||||
fileName: hooks.createValueGrip(fileName),
|
||||
lineNumber: hooks.createValueGrip(lineNumber),
|
||||
columnNumber: hooks.createValueGrip(columnNumber),
|
||||
};
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
function CSSMediaRule({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || obj.class != "CSSMediaRule") {
|
||||
return false;
|
||||
}
|
||||
grip.preview = {
|
||||
kind: "ObjectWithText",
|
||||
text: hooks.createValueGrip(rawObj.conditionText),
|
||||
};
|
||||
return true;
|
||||
},
|
||||
|
||||
function CSSStyleRule({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || obj.class != "CSSStyleRule") {
|
||||
return false;
|
||||
}
|
||||
grip.preview = {
|
||||
kind: "ObjectWithText",
|
||||
text: hooks.createValueGrip(rawObj.selectorText),
|
||||
};
|
||||
return true;
|
||||
},
|
||||
|
||||
function ObjectWithURL({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(obj.class == "CSSImportRule" ||
|
||||
obj.class == "CSSStyleSheet" ||
|
||||
obj.class == "Location" ||
|
||||
rawObj instanceof Ci.nsIDOMWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let url;
|
||||
if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
|
||||
url = rawObj.location.href;
|
||||
} else if (rawObj.href) {
|
||||
url = rawObj.href;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ObjectWithURL",
|
||||
url: hooks.createValueGrip(url),
|
||||
};
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function ArrayLike({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj ||
|
||||
obj.class != "DOMStringList" &&
|
||||
obj.class != "DOMTokenList" &&
|
||||
obj.class != "CSSRuleList" &&
|
||||
obj.class != "MediaList" &&
|
||||
obj.class != "StyleSheetList" &&
|
||||
obj.class != "CSSValueList" &&
|
||||
obj.class != "NamedNodeMap" &&
|
||||
obj.class != "FileList" &&
|
||||
obj.class != "NodeList") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof rawObj.length != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: rawObj.length,
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let items = grip.preview.items = [];
|
||||
|
||||
for (let i = 0; i < rawObj.length &&
|
||||
items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
|
||||
let value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, rawObj[i]);
|
||||
items.push(hooks.createValueGrip(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj ||
|
||||
(obj.class != "CSSStyleDeclaration" &&
|
||||
obj.class != "CSS2Properties")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: rawObj.length,
|
||||
};
|
||||
|
||||
let entries = grip.preview.entries = [];
|
||||
|
||||
for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
|
||||
i < rawObj.length; i++) {
|
||||
let prop = rawObj[i];
|
||||
let value = rawObj.getPropertyValue(prop);
|
||||
entries.push([prop, hooks.createValueGrip(value)]);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function DOMNode({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || obj.class == "Object" || !rawObj ||
|
||||
!(rawObj instanceof Ci.nsIDOMNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let preview = grip.preview = {
|
||||
kind: "DOMNode",
|
||||
nodeType: rawObj.nodeType,
|
||||
nodeName: rawObj.nodeName,
|
||||
isConnected: rawObj.isConnected === true,
|
||||
};
|
||||
|
||||
if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
|
||||
preview.location = hooks.createValueGrip(rawObj.location.href);
|
||||
} else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
|
||||
preview.childNodesLength = rawObj.childNodes.length;
|
||||
|
||||
if (hooks.getGripDepth() < 2) {
|
||||
preview.childNodes = [];
|
||||
for (let node of rawObj.childNodes) {
|
||||
let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
|
||||
preview.childNodes.push(actor);
|
||||
if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (rawObj instanceof Ci.nsIDOMElement) {
|
||||
// For HTML elements (in an HTML document, at least), the nodeName is an
|
||||
// uppercased version of the actual element name. Check for HTML
|
||||
// elements, that is elements in the HTML namespace, and lowercase the
|
||||
// nodeName in that case.
|
||||
if (rawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
|
||||
preview.nodeName = preview.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
// Add preview for DOM element attributes.
|
||||
preview.attributes = {};
|
||||
preview.attributesLength = rawObj.attributes.length;
|
||||
for (let attr of rawObj.attributes) {
|
||||
preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
|
||||
}
|
||||
} else if (obj.class == "Attr") {
|
||||
preview.value = hooks.createValueGrip(rawObj.value);
|
||||
} else if (obj.class == "Text" ||
|
||||
obj.class == "CDATASection" ||
|
||||
obj.class == "Comment") {
|
||||
preview.textContent = hooks.createValueGrip(rawObj.textContent);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function DOMEvent({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let preview = grip.preview = {
|
||||
kind: "DOMEvent",
|
||||
type: rawObj.type,
|
||||
properties: Object.create(null),
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() < 2) {
|
||||
let target = obj.makeDebuggeeValue(rawObj.target);
|
||||
preview.target = hooks.createValueGrip(target);
|
||||
}
|
||||
|
||||
let props = [];
|
||||
if (obj.class == "MouseEvent" ||
|
||||
obj.class == "DragEvent" ||
|
||||
obj.class == "PointerEvent" ||
|
||||
obj.class == "SimpleGestureEvent" ||
|
||||
obj.class == "WheelEvent") {
|
||||
props.push("buttons", "clientX", "clientY", "layerX", "layerY");
|
||||
} else if (obj.class == "KeyboardEvent") {
|
||||
let modifiers = [];
|
||||
if (rawObj.altKey) {
|
||||
modifiers.push("Alt");
|
||||
}
|
||||
if (rawObj.ctrlKey) {
|
||||
modifiers.push("Control");
|
||||
}
|
||||
if (rawObj.metaKey) {
|
||||
modifiers.push("Meta");
|
||||
}
|
||||
if (rawObj.shiftKey) {
|
||||
modifiers.push("Shift");
|
||||
}
|
||||
preview.eventKind = "key";
|
||||
preview.modifiers = modifiers;
|
||||
|
||||
props.push("key", "charCode", "keyCode");
|
||||
} else if (obj.class == "TransitionEvent") {
|
||||
props.push("propertyName", "pseudoElement");
|
||||
} else if (obj.class == "AnimationEvent") {
|
||||
props.push("animationName", "pseudoElement");
|
||||
} else if (obj.class == "ClipboardEvent") {
|
||||
props.push("clipboardData");
|
||||
}
|
||||
|
||||
// Add event-specific properties.
|
||||
for (let prop of props) {
|
||||
let value = rawObj[prop];
|
||||
if (value && (typeof value == "object" || typeof value == "function")) {
|
||||
// Skip properties pointing to objects.
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
continue;
|
||||
}
|
||||
value = obj.makeDebuggeeValue(value);
|
||||
}
|
||||
preview.properties[prop] = hooks.createValueGrip(value);
|
||||
}
|
||||
|
||||
// Add any properties we find on the event object.
|
||||
if (!props.length) {
|
||||
let i = 0;
|
||||
for (let prop in rawObj) {
|
||||
let value = rawObj[prop];
|
||||
if (prop == "target" || prop == "type" || value === null ||
|
||||
typeof value == "function") {
|
||||
continue;
|
||||
}
|
||||
if (value && typeof value == "object") {
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
continue;
|
||||
}
|
||||
value = obj.makeDebuggeeValue(value);
|
||||
}
|
||||
preview.properties[prop] = hooks.createValueGrip(value);
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function DOMException({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "DOMException",
|
||||
name: hooks.createValueGrip(rawObj.name),
|
||||
message: hooks.createValueGrip(rawObj.message),
|
||||
code: hooks.createValueGrip(rawObj.code),
|
||||
result: hooks.createValueGrip(rawObj.result),
|
||||
filename: hooks.createValueGrip(rawObj.filename),
|
||||
lineNumber: hooks.createValueGrip(rawObj.lineNumber),
|
||||
columnNumber: hooks.createValueGrip(rawObj.columnNumber),
|
||||
};
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function PseudoArray({obj, hooks}, grip, rawObj) {
|
||||
// An object is considered a pseudo-array if all the following apply:
|
||||
// - All its properties are array indices except, optionally, a "length" property.
|
||||
// - At least it has the "0" array index.
|
||||
// - The array indices are consecutive.
|
||||
// - The value of "length", if present, is the number of array indices.
|
||||
|
||||
let keys;
|
||||
try {
|
||||
keys = obj.getOwnPropertyNames();
|
||||
} catch (err) {
|
||||
// The above can throw when the debuggee does not subsume the object's
|
||||
// compartment, or for some WrappedNatives like Cu.Sandbox.
|
||||
return false;
|
||||
}
|
||||
let {length} = keys;
|
||||
if (length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Array indices should be sorted at the beginning, from smallest to largest.
|
||||
// Other properties should be at the end, so check if the last one is "length".
|
||||
if (keys[length - 1] === "length") {
|
||||
--length;
|
||||
if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the last key is the array index expected at that position.
|
||||
let lastKey = keys[length - 1];
|
||||
if (!ObjectUtils.isArrayIndex(lastKey) || +lastKey !== length - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: length,
|
||||
};
|
||||
|
||||
// Avoid recursive object grips.
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let items = grip.preview.items = [];
|
||||
let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
|
||||
|
||||
for (let i = 0; i < numItems; ++i) {
|
||||
let desc = obj.getOwnPropertyDescriptor(i);
|
||||
if (desc && "value" in desc) {
|
||||
items.push(hooks.createValueGrip(desc.value));
|
||||
} else {
|
||||
items.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function Object(objectActor, grip, rawObj) {
|
||||
return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = previewers;
|
417
devtools/server/actors/object/property-iterator.js
Normal file
417
devtools/server/actors/object/property-iterator.js
Normal file
@ -0,0 +1,417 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
loader.lazyRequireGetter(this, "ChromeUtils");
|
||||
loader.lazyRequireGetter(this, "ObjectUtils", "devtools/server/actors/object/utils");
|
||||
|
||||
/**
|
||||
* Creates an actor to iterate over an object's property names and values.
|
||||
*
|
||||
* @param objectActor ObjectActor
|
||||
* The object actor.
|
||||
* @param options Object
|
||||
* A dictionary object with various boolean attributes:
|
||||
* - enumEntries Boolean
|
||||
* If true, enumerates the entries of a Map or Set object
|
||||
* instead of enumerating properties.
|
||||
* - ignoreIndexedProperties Boolean
|
||||
* If true, filters out Array items.
|
||||
* e.g. properties names between `0` and `object.length`.
|
||||
* - ignoreNonIndexedProperties Boolean
|
||||
* If true, filters out items that aren't array items
|
||||
* e.g. properties names that are not a number between `0`
|
||||
* and `object.length`.
|
||||
* - sort Boolean
|
||||
* If true, the iterator will sort the properties by name
|
||||
* before dispatching them.
|
||||
* - query String
|
||||
* If non-empty, will filter the properties by names and values
|
||||
* containing this query string. The match is not case-sensitive.
|
||||
* Regarding value filtering it just compare to the stringification
|
||||
* of the property value.
|
||||
*/
|
||||
function PropertyIteratorActor(objectActor, options) {
|
||||
if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
|
||||
this.iterator = {
|
||||
size: 0,
|
||||
propertyName: index => undefined,
|
||||
propertyDescription: index => undefined,
|
||||
};
|
||||
} else if (options.enumEntries) {
|
||||
let cls = objectActor.obj.class;
|
||||
if (cls == "Map") {
|
||||
this.iterator = enumMapEntries(objectActor);
|
||||
} else if (cls == "WeakMap") {
|
||||
this.iterator = enumWeakMapEntries(objectActor);
|
||||
} else if (cls == "Set") {
|
||||
this.iterator = enumSetEntries(objectActor);
|
||||
} else if (cls == "WeakSet") {
|
||||
this.iterator = enumWeakSetEntries(objectActor);
|
||||
} else {
|
||||
throw new Error("Unsupported class to enumerate entries from: " + cls);
|
||||
}
|
||||
} else if (
|
||||
ObjectUtils.isArray(objectActor.obj)
|
||||
&& options.ignoreNonIndexedProperties
|
||||
&& !options.query
|
||||
) {
|
||||
this.iterator = enumArrayProperties(objectActor, options);
|
||||
} else {
|
||||
this.iterator = enumObjectProperties(objectActor, options);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyIteratorActor.prototype = {
|
||||
actorPrefix: "propertyIterator",
|
||||
|
||||
grip() {
|
||||
return {
|
||||
type: this.actorPrefix,
|
||||
actor: this.actorID,
|
||||
count: this.iterator.size
|
||||
};
|
||||
},
|
||||
|
||||
names({ indexes }) {
|
||||
let list = [];
|
||||
for (let idx of indexes) {
|
||||
list.push(this.iterator.propertyName(idx));
|
||||
}
|
||||
return {
|
||||
names: indexes
|
||||
};
|
||||
},
|
||||
|
||||
slice({ start, count }) {
|
||||
let ownProperties = Object.create(null);
|
||||
for (let i = start, m = start + count; i < m; i++) {
|
||||
let name = this.iterator.propertyName(i);
|
||||
ownProperties[name] = this.iterator.propertyDescription(i);
|
||||
}
|
||||
return {
|
||||
ownProperties
|
||||
};
|
||||
},
|
||||
|
||||
all() {
|
||||
return this.slice({ start: 0, count: this.iterator.size });
|
||||
}
|
||||
};
|
||||
|
||||
PropertyIteratorActor.prototype.requestTypes = {
|
||||
"names": PropertyIteratorActor.prototype.names,
|
||||
"slice": PropertyIteratorActor.prototype.slice,
|
||||
"all": PropertyIteratorActor.prototype.all,
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to create a grip from a Map/Set entry
|
||||
*/
|
||||
function gripFromEntry({ obj, hooks }, entry) {
|
||||
return hooks.createValueGrip(
|
||||
ObjectUtils.makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
|
||||
}
|
||||
|
||||
function enumArrayProperties(objectActor, options) {
|
||||
return {
|
||||
size: ObjectUtils.getArrayLength(objectActor.obj),
|
||||
propertyName(index) {
|
||||
return index;
|
||||
},
|
||||
propertyDescription(index) {
|
||||
return objectActor._propertyDescriptor(index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function enumObjectProperties(objectActor, options) {
|
||||
let names = [];
|
||||
try {
|
||||
names = objectActor.obj.getOwnPropertyNames();
|
||||
} catch (ex) {
|
||||
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
|
||||
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
|
||||
}
|
||||
|
||||
if (options.ignoreNonIndexedProperties || options.ignoreIndexedProperties) {
|
||||
let length = DevToolsUtils.getProperty(objectActor.obj, "length");
|
||||
let sliceIndex;
|
||||
|
||||
const isLengthTrustworthy = isUint32(length)
|
||||
&& (!length || ObjectUtils.isArrayIndex(names[length - 1]))
|
||||
&& !ObjectUtils.isArrayIndex(names[length]);
|
||||
|
||||
if (!isLengthTrustworthy) {
|
||||
// The length property may not reflect what the object looks like, let's find
|
||||
// where indexed properties end.
|
||||
|
||||
if (!ObjectUtils.isArrayIndex(names[0])) {
|
||||
// If the first item is not a number, this means there is no indexed properties
|
||||
// in this object.
|
||||
sliceIndex = 0;
|
||||
} else {
|
||||
sliceIndex = names.length;
|
||||
while (sliceIndex > 0) {
|
||||
if (ObjectUtils.isArrayIndex(names[sliceIndex - 1])) {
|
||||
break;
|
||||
}
|
||||
sliceIndex--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sliceIndex = length;
|
||||
}
|
||||
|
||||
// It appears that getOwnPropertyNames always returns indexed properties
|
||||
// first, so we can safely slice `names` for/against indexed properties.
|
||||
// We do such clever operation to optimize very large array inspection,
|
||||
// like webaudio buffers.
|
||||
if (options.ignoreIndexedProperties) {
|
||||
// Keep items after `sliceIndex` index
|
||||
names = names.slice(sliceIndex);
|
||||
} else if (options.ignoreNonIndexedProperties) {
|
||||
// Keep `sliceIndex` first items
|
||||
names.length = sliceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
|
||||
let safeGetterNames = Object.keys(safeGetterValues);
|
||||
// Merge the safe getter values into the existing properties list.
|
||||
for (let name of safeGetterNames) {
|
||||
if (!names.includes(name)) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.query) {
|
||||
let { query } = options;
|
||||
query = query.toLowerCase();
|
||||
names = names.filter(name => {
|
||||
// Filter on attribute names
|
||||
if (name.toLowerCase().includes(query)) {
|
||||
return true;
|
||||
}
|
||||
// and then on attribute values
|
||||
let desc;
|
||||
try {
|
||||
desc = objectActor.obj.getOwnPropertyDescriptor(name);
|
||||
} catch (e) {
|
||||
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
|
||||
// allowed (bug 560072).
|
||||
}
|
||||
if (desc && desc.value &&
|
||||
String(desc.value).includes(query)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (options.sort) {
|
||||
names.sort();
|
||||
}
|
||||
|
||||
return {
|
||||
size: names.length,
|
||||
propertyName(index) {
|
||||
return names[index];
|
||||
},
|
||||
propertyDescription(index) {
|
||||
let name = names[index];
|
||||
let desc = objectActor._propertyDescriptor(name);
|
||||
if (!desc) {
|
||||
desc = safeGetterValues[name];
|
||||
} else if (name in safeGetterValues) {
|
||||
// Merge the safe getter values into the existing properties list.
|
||||
let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
|
||||
desc.getterValue = getterValue;
|
||||
desc.getterPrototypeLevel = getterPrototypeLevel;
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function enumMapEntries(objectActor) {
|
||||
// Iterating over a Map via .entries goes through various intermediate
|
||||
// objects - an Iterator object, then a 2-element Array object, then the
|
||||
// actual values we care about. We don't have Xrays to Iterator objects,
|
||||
// so we get Opaque wrappers for them. And even though we have Xrays to
|
||||
// Arrays, the semantics often deny access to the entires based on the
|
||||
// nature of the values. So we need waive Xrays for the iterator object
|
||||
// and the tupes, and then re-apply them on the underlying values until
|
||||
// we fix bug 1023984.
|
||||
//
|
||||
// Even then though, we might want to continue waiving Xrays here for the
|
||||
// same reason we do so for Arrays above - this filtering behavior is likely
|
||||
// to be more confusing than beneficial in the case of Object previews.
|
||||
let raw = objectActor.obj.unsafeDereference();
|
||||
|
||||
let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
|
||||
return {
|
||||
[Symbol.iterator]: function* () {
|
||||
for (let key of keys) {
|
||||
let value = Map.prototype.get.call(raw, key);
|
||||
yield [ key, value ].map(val => gripFromEntry(objectActor, val));
|
||||
}
|
||||
},
|
||||
size: keys.length,
|
||||
propertyName(index) {
|
||||
return index;
|
||||
},
|
||||
propertyDescription(index) {
|
||||
let key = keys[index];
|
||||
let val = Map.prototype.get.call(raw, key);
|
||||
return {
|
||||
enumerable: true,
|
||||
value: {
|
||||
type: "mapEntry",
|
||||
preview: {
|
||||
key: gripFromEntry(objectActor, key),
|
||||
value: gripFromEntry(objectActor, val)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function enumWeakMapEntries(objectActor) {
|
||||
// We currently lack XrayWrappers for WeakMap, so when we iterate over
|
||||
// the values, the temporary iterator objects get created in the target
|
||||
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
||||
// Xraying those temporary objects, and filtering access to |it.value|
|
||||
// based on whether or not it's Xrayable and/or callable, which breaks
|
||||
// the for/of iteration.
|
||||
//
|
||||
// This code is designed to handle untrusted objects, so we can safely
|
||||
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
||||
// make sure we handle the resulting objects carefully.
|
||||
let raw = objectActor.obj.unsafeDereference();
|
||||
let keys = Cu.waiveXrays(
|
||||
ChromeUtils.nondeterministicGetWeakMapKeys(raw));
|
||||
|
||||
return {
|
||||
[Symbol.iterator]: function* () {
|
||||
for (let key of keys) {
|
||||
let value = WeakMap.prototype.get.call(raw, key);
|
||||
yield [ key, value ].map(val => gripFromEntry(objectActor, val));
|
||||
}
|
||||
},
|
||||
size: keys.length,
|
||||
propertyName(index) {
|
||||
return index;
|
||||
},
|
||||
propertyDescription(index) {
|
||||
let key = keys[index];
|
||||
let val = WeakMap.prototype.get.call(raw, key);
|
||||
return {
|
||||
enumerable: true,
|
||||
value: {
|
||||
type: "mapEntry",
|
||||
preview: {
|
||||
key: gripFromEntry(objectActor, key),
|
||||
value: gripFromEntry(objectActor, val)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function enumSetEntries(objectActor) {
|
||||
// We currently lack XrayWrappers for Set, so when we iterate over
|
||||
// the values, the temporary iterator objects get created in the target
|
||||
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
||||
// Xraying those temporary objects, and filtering access to |it.value|
|
||||
// based on whether or not it's Xrayable and/or callable, which breaks
|
||||
// the for/of iteration.
|
||||
//
|
||||
// This code is designed to handle untrusted objects, so we can safely
|
||||
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
||||
// make sure we handle the resulting objects carefully.
|
||||
let raw = objectActor.obj.unsafeDereference();
|
||||
let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
|
||||
|
||||
return {
|
||||
[Symbol.iterator]: function* () {
|
||||
for (let item of values) {
|
||||
yield gripFromEntry(objectActor, item);
|
||||
}
|
||||
},
|
||||
size: values.length,
|
||||
propertyName(index) {
|
||||
return index;
|
||||
},
|
||||
propertyDescription(index) {
|
||||
let val = values[index];
|
||||
return {
|
||||
enumerable: true,
|
||||
value: gripFromEntry(objectActor, val)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function enumWeakSetEntries(objectActor) {
|
||||
// We currently lack XrayWrappers for WeakSet, so when we iterate over
|
||||
// the values, the temporary iterator objects get created in the target
|
||||
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
||||
// Xraying those temporary objects, and filtering access to |it.value|
|
||||
// based on whether or not it's Xrayable and/or callable, which breaks
|
||||
// the for/of iteration.
|
||||
//
|
||||
// This code is designed to handle untrusted objects, so we can safely
|
||||
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
||||
// make sure we handle the resulting objects carefully.
|
||||
let raw = objectActor.obj.unsafeDereference();
|
||||
let keys = Cu.waiveXrays(
|
||||
ChromeUtils.nondeterministicGetWeakSetKeys(raw));
|
||||
|
||||
return {
|
||||
[Symbol.iterator]: function* () {
|
||||
for (let item of keys) {
|
||||
yield gripFromEntry(objectActor, item);
|
||||
}
|
||||
},
|
||||
size: keys.length,
|
||||
propertyName(index) {
|
||||
return index;
|
||||
},
|
||||
propertyDescription(index) {
|
||||
let val = keys[index];
|
||||
return {
|
||||
enumerable: true,
|
||||
value: gripFromEntry(objectActor, val)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the parameter can be stored as a 32-bit unsigned integer.
|
||||
* If so, it will be suitable for use as the length of an array object.
|
||||
*
|
||||
* @param num Number
|
||||
* The number to test.
|
||||
* @return Boolean
|
||||
*/
|
||||
function isUint32(num) {
|
||||
return num >>> 0 === num;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PropertyIteratorActor,
|
||||
enumMapEntries,
|
||||
enumSetEntries,
|
||||
enumWeakMapEntries,
|
||||
enumWeakSetEntries,
|
||||
};
|
187
devtools/server/actors/object/stringifiers.js
Normal file
187
devtools/server/actors/object/stringifiers.js
Normal file
@ -0,0 +1,187 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
loader.lazyRequireGetter(this, "ObjectUtils", "devtools/server/actors/object/utils");
|
||||
|
||||
/**
|
||||
* Stringify a Debugger.Object based on its class.
|
||||
*
|
||||
* @param Debugger.Object obj
|
||||
* The object to stringify.
|
||||
* @return String
|
||||
* The stringification for the object.
|
||||
*/
|
||||
function stringify(obj) {
|
||||
if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
|
||||
if (DevToolsUtils.isCPOW(obj)) {
|
||||
return "<cpow>";
|
||||
}
|
||||
let unwrapped = DevToolsUtils.unwrap(obj);
|
||||
if (unwrapped === undefined) {
|
||||
return "<invisibleToDebugger>";
|
||||
} else if (unwrapped.isProxy) {
|
||||
return "<proxy>";
|
||||
}
|
||||
// The following line should not be reached. It's there just in case somebody
|
||||
// modifies isSafeDebuggerObject to return false for additional kinds of objects.
|
||||
return "[object " + obj.class + "]";
|
||||
} else if (obj.class == "DeadObject") {
|
||||
return "<dead object>";
|
||||
}
|
||||
|
||||
const stringifier = stringifiers[obj.class] || stringifiers.Object;
|
||||
|
||||
try {
|
||||
return stringifier(obj);
|
||||
} catch (e) {
|
||||
DevToolsUtils.reportException("stringify", e);
|
||||
return "<failed to stringify object>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a function that can safely stringify Debugger.Objects of a given
|
||||
* builtin type.
|
||||
*
|
||||
* @param Function ctor
|
||||
* The builtin class constructor.
|
||||
* @return Function
|
||||
* The stringifier for the class.
|
||||
*/
|
||||
function createBuiltinStringifier(ctor) {
|
||||
return obj => {
|
||||
try {
|
||||
return ctor.prototype.toString.call(obj.unsafeDereference());
|
||||
} catch (err) {
|
||||
// The debuggee will see a "Function" class if the object is callable and
|
||||
// its compartment is not subsumed. The above will throw if it's not really
|
||||
// a function, e.g. if it's a callable proxy.
|
||||
return "[object " + obj.class + "]";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify a Debugger.Object-wrapped Error instance.
|
||||
*
|
||||
* @param Debugger.Object obj
|
||||
* The object to stringify.
|
||||
* @return String
|
||||
* The stringification of the object.
|
||||
*/
|
||||
function errorStringify(obj) {
|
||||
let name = DevToolsUtils.getProperty(obj, "name");
|
||||
if (name === "" || name === undefined) {
|
||||
name = obj.class;
|
||||
} else if (isObject(name)) {
|
||||
name = stringify(name);
|
||||
}
|
||||
|
||||
let message = DevToolsUtils.getProperty(obj, "message");
|
||||
if (isObject(message)) {
|
||||
message = stringify(message);
|
||||
}
|
||||
|
||||
if (message === "" || message === undefined) {
|
||||
return name;
|
||||
}
|
||||
return name + ": " + message;
|
||||
}
|
||||
|
||||
// Used to prevent infinite recursion when an array is found inside itself.
|
||||
var seen = null;
|
||||
|
||||
var stringifiers = {
|
||||
Error: errorStringify,
|
||||
EvalError: errorStringify,
|
||||
RangeError: errorStringify,
|
||||
ReferenceError: errorStringify,
|
||||
SyntaxError: errorStringify,
|
||||
TypeError: errorStringify,
|
||||
URIError: errorStringify,
|
||||
Boolean: createBuiltinStringifier(Boolean),
|
||||
Function: createBuiltinStringifier(Function),
|
||||
Number: createBuiltinStringifier(Number),
|
||||
RegExp: createBuiltinStringifier(RegExp),
|
||||
String: createBuiltinStringifier(String),
|
||||
Object: obj => "[object " + obj.class + "]",
|
||||
Array: obj => {
|
||||
// If we're at the top level then we need to create the Set for tracking
|
||||
// previously stringified arrays.
|
||||
const topLevel = !seen;
|
||||
if (topLevel) {
|
||||
seen = new Set();
|
||||
} else if (seen.has(obj)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
seen.add(obj);
|
||||
|
||||
const len = ObjectUtils.getArrayLength(obj);
|
||||
let string = "";
|
||||
|
||||
// Array.length is always a non-negative safe integer.
|
||||
for (let i = 0; i < len; i++) {
|
||||
const desc = obj.getOwnPropertyDescriptor(i);
|
||||
if (desc) {
|
||||
const { value } = desc;
|
||||
if (value != null) {
|
||||
string += isObject(value) ? stringify(value) : value;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < len - 1) {
|
||||
string += ",";
|
||||
}
|
||||
}
|
||||
|
||||
if (topLevel) {
|
||||
seen = null;
|
||||
}
|
||||
|
||||
return string;
|
||||
},
|
||||
DOMException: obj => {
|
||||
const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
|
||||
const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
|
||||
const code = DevToolsUtils.getProperty(obj, "code");
|
||||
const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
|
||||
|
||||
return '[Exception... "' + message + '" ' +
|
||||
'code: "' + code + '" ' +
|
||||
'nsresult: "0x' + result + " (" + name + ')"]';
|
||||
},
|
||||
Promise: obj => {
|
||||
const { state, value, reason } = ObjectUtils.getPromiseState(obj);
|
||||
let statePreview = state;
|
||||
if (state != "pending") {
|
||||
const settledValue = state === "fulfilled" ? value : reason;
|
||||
statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
|
||||
? stringify(settledValue)
|
||||
: settledValue);
|
||||
}
|
||||
return "Promise (" + statePreview + ")";
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a given value is non-primitive.
|
||||
*
|
||||
* @param Any value
|
||||
* The value to test.
|
||||
* @return Boolean
|
||||
* Whether the value is non-primitive.
|
||||
*/
|
||||
function isObject(value) {
|
||||
const type = typeof value;
|
||||
return type == "object" ? value !== null : type == "function";
|
||||
}
|
||||
|
||||
module.exports = stringify;
|
70
devtools/server/actors/object/symbol-iterator.js
Normal file
70
devtools/server/actors/object/symbol-iterator.js
Normal file
@ -0,0 +1,70 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
/**
|
||||
* Creates an actor to iterate over an object's symbols.
|
||||
*
|
||||
* @param objectActor ObjectActor
|
||||
* The object actor.
|
||||
*/
|
||||
function SymbolIteratorActor(objectActor) {
|
||||
let symbols = [];
|
||||
if (DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
|
||||
try {
|
||||
symbols = objectActor.obj.getOwnPropertySymbols();
|
||||
} catch (err) {
|
||||
// The above can throw when the debuggee does not subsume the object's
|
||||
// compartment, or for some WrappedNatives like Cu.Sandbox.
|
||||
}
|
||||
}
|
||||
|
||||
this.iterator = {
|
||||
size: symbols.length,
|
||||
symbolDescription(index) {
|
||||
const symbol = symbols[index];
|
||||
return {
|
||||
name: symbol.toString(),
|
||||
descriptor: objectActor._propertyDescriptor(symbol)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SymbolIteratorActor.prototype = {
|
||||
actorPrefix: "symbolIterator",
|
||||
|
||||
grip() {
|
||||
return {
|
||||
type: this.actorPrefix,
|
||||
actor: this.actorID,
|
||||
count: this.iterator.size
|
||||
};
|
||||
},
|
||||
|
||||
slice({ start, count }) {
|
||||
let ownSymbols = [];
|
||||
for (let i = start, m = start + count; i < m; i++) {
|
||||
ownSymbols.push(this.iterator.symbolDescription(i));
|
||||
}
|
||||
return {
|
||||
ownSymbols
|
||||
};
|
||||
},
|
||||
|
||||
all() {
|
||||
return this.slice({ start: 0, count: this.iterator.size });
|
||||
}
|
||||
};
|
||||
|
||||
SymbolIteratorActor.prototype.requestTypes = {
|
||||
"slice": SymbolIteratorActor.prototype.slice,
|
||||
"all": SymbolIteratorActor.prototype.all,
|
||||
};
|
||||
|
||||
exports.SymbolIteratorActor = SymbolIteratorActor;
|
107
devtools/server/actors/object/symbol.js
Normal file
107
devtools/server/actors/object/symbol.js
Normal file
@ -0,0 +1,107 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "createValueGrip", "devtools/server/actors/object/utils", true);
|
||||
|
||||
/**
|
||||
* Creates an actor for the specified symbol.
|
||||
*
|
||||
* @param symbol Symbol
|
||||
* The symbol.
|
||||
*/
|
||||
function SymbolActor(symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
SymbolActor.prototype = {
|
||||
actorPrefix: "symbol",
|
||||
|
||||
rawValue: function() {
|
||||
return this.symbol;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
// Because symbolActors is not a weak map, we won't automatically leave
|
||||
// it so we need to manually leave on destroy so that we don't leak
|
||||
// memory.
|
||||
this._releaseActor();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a grip for this actor for returning in a protocol message.
|
||||
*/
|
||||
grip: function() {
|
||||
let form = {
|
||||
type: "symbol",
|
||||
actor: this.actorID,
|
||||
};
|
||||
let name = getSymbolName(this.symbol);
|
||||
if (name !== undefined) {
|
||||
// Create a grip for the name because it might be a longString.
|
||||
form.name = createValueGrip(name, this.registeredPool);
|
||||
}
|
||||
return form;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a request to release this SymbolActor instance.
|
||||
*/
|
||||
onRelease: function() {
|
||||
// TODO: also check if registeredPool === threadActor.threadLifetimePool
|
||||
// when the web console moves away from manually releasing pause-scoped
|
||||
// actors.
|
||||
this._releaseActor();
|
||||
this.registeredPool.removeActor(this);
|
||||
return {};
|
||||
},
|
||||
|
||||
_releaseActor: function() {
|
||||
if (this.registeredPool && this.registeredPool.symbolActors) {
|
||||
delete this.registeredPool.symbolActors[this.symbol];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SymbolActor.prototype.requestTypes = {
|
||||
"release": SymbolActor.prototype.onRelease
|
||||
};
|
||||
|
||||
const symbolProtoToString = Symbol.prototype.toString;
|
||||
|
||||
function getSymbolName(symbol) {
|
||||
const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
|
||||
return name || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a grip for the given symbol.
|
||||
*
|
||||
* @param sym Symbol
|
||||
* The symbol we are creating a grip for.
|
||||
* @param pool ActorPool
|
||||
* The actor pool where the new actor will be added.
|
||||
*/
|
||||
function symbolGrip(sym, pool) {
|
||||
if (!pool.symbolActors) {
|
||||
pool.symbolActors = Object.create(null);
|
||||
}
|
||||
|
||||
if (sym in pool.symbolActors) {
|
||||
return pool.symbolActors[sym].grip();
|
||||
}
|
||||
|
||||
let actor = new SymbolActor(sym);
|
||||
pool.addActor(actor);
|
||||
pool.symbolActors[sym] = actor;
|
||||
return actor.grip();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SymbolActor,
|
||||
symbolGrip,
|
||||
};
|
203
devtools/server/actors/object/utils.js
Normal file
203
devtools/server/actors/object/utils.js
Normal file
@ -0,0 +1,203 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { assert } = DevToolsUtils;
|
||||
|
||||
loader.lazyRequireGetter(this, "longStringGrip", "devtools/server/actors/object/long-string", true);
|
||||
loader.lazyRequireGetter(this, "symbolGrip", "devtools/server/actors/object/symbol", true);
|
||||
|
||||
/**
|
||||
* Get thisDebugger.Object referent's `promiseState`.
|
||||
*
|
||||
* @returns Object
|
||||
* An object of one of the following forms:
|
||||
* - { state: "pending" }
|
||||
* - { state: "fulfilled", value }
|
||||
* - { state: "rejected", reason }
|
||||
*/
|
||||
function getPromiseState(obj) {
|
||||
if (obj.class != "Promise") {
|
||||
throw new Error(
|
||||
"Can't call `getPromiseState` on `Debugger.Object`s that don't " +
|
||||
"refer to Promise objects.");
|
||||
}
|
||||
|
||||
let state = { state: obj.promiseState };
|
||||
if (state.state === "fulfilled") {
|
||||
state.value = obj.promiseValue;
|
||||
} else if (state.state === "rejected") {
|
||||
state.reason = obj.promiseReason;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a debuggee value for the given object, if needed. Primitive values
|
||||
* are left the same.
|
||||
*
|
||||
* Use case: you have a raw JS object (after unsafe dereference) and you want to
|
||||
* send it to the client. In that case you need to use an ObjectActor which
|
||||
* requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
|
||||
* method works only for JS objects and functions.
|
||||
*
|
||||
* @param Debugger.Object obj
|
||||
* @param any value
|
||||
* @return object
|
||||
*/
|
||||
function makeDebuggeeValueIfNeeded(obj, value) {
|
||||
if (value && (typeof value == "object" || typeof value == "function")) {
|
||||
return obj.makeDebuggeeValue(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a grip for the given debuggee value. If the value is an
|
||||
* object, will create an actor with the given lifetime.
|
||||
*/
|
||||
function createValueGrip(value, pool, makeObjectGrip) {
|
||||
switch (typeof value) {
|
||||
case "boolean":
|
||||
return value;
|
||||
|
||||
case "string":
|
||||
if (stringIsLong(value)) {
|
||||
return longStringGrip(value, pool);
|
||||
}
|
||||
return value;
|
||||
|
||||
case "number":
|
||||
if (value === Infinity) {
|
||||
return { type: "Infinity" };
|
||||
} else if (value === -Infinity) {
|
||||
return { type: "-Infinity" };
|
||||
} else if (Number.isNaN(value)) {
|
||||
return { type: "NaN" };
|
||||
} else if (!value && 1 / value === -Infinity) {
|
||||
return { type: "-0" };
|
||||
}
|
||||
return value;
|
||||
|
||||
case "undefined":
|
||||
return { type: "undefined" };
|
||||
|
||||
case "object":
|
||||
if (value === null) {
|
||||
return { type: "null" };
|
||||
} else if (value.optimizedOut ||
|
||||
value.uninitialized ||
|
||||
value.missingArguments) {
|
||||
// The slot is optimized out, an uninitialized binding, or
|
||||
// arguments on a dead scope
|
||||
return {
|
||||
type: "null",
|
||||
optimizedOut: value.optimizedOut,
|
||||
uninitialized: value.uninitialized,
|
||||
missingArguments: value.missingArguments
|
||||
};
|
||||
}
|
||||
return makeObjectGrip(value, pool);
|
||||
|
||||
case "symbol":
|
||||
return symbolGrip(value, pool);
|
||||
|
||||
default:
|
||||
assert(false, "Failed to provide a grip for: " + value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* of passing the value directly over the protocol.
|
||||
*
|
||||
* @param str String
|
||||
* The string we are checking the length of.
|
||||
*/
|
||||
function stringIsLong(str) {
|
||||
return str.length >= DebuggerServer.LONG_STRING_LENGTH;
|
||||
}
|
||||
|
||||
const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
|
||||
"Uint32Array", "Int8Array", "Int16Array", "Int32Array",
|
||||
"Float32Array", "Float64Array"];
|
||||
|
||||
/**
|
||||
* Returns true if a debuggee object is a typed array.
|
||||
*
|
||||
* @param obj Debugger.Object
|
||||
* The debuggee object to test.
|
||||
* @return Boolean
|
||||
*/
|
||||
function isTypedArray(object) {
|
||||
return TYPED_ARRAY_CLASSES.includes(object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a debuggee object is an array, including a typed array.
|
||||
*
|
||||
* @param obj Debugger.Object
|
||||
* The debuggee object to test.
|
||||
* @return Boolean
|
||||
*/
|
||||
function isArray(object) {
|
||||
return isTypedArray(object) || object.class === "Array";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of an array (or typed array).
|
||||
*
|
||||
* @param obj Debugger.Object
|
||||
* The debuggee object of the array.
|
||||
* @return Number
|
||||
* @throws if the object is not an array.
|
||||
*/
|
||||
function getArrayLength(object) {
|
||||
if (!isArray(object)) {
|
||||
throw new Error("Expected an array, got a " + object.class);
|
||||
}
|
||||
|
||||
// Real arrays have a reliable `length` own property.
|
||||
if (object.class === "Array") {
|
||||
return DevToolsUtils.getProperty(object, "length");
|
||||
}
|
||||
|
||||
// For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
|
||||
// getter could be shadowed by an own property, and `getOwnPropertyNames` is
|
||||
// unnecessarily slow. Obtain the `length` getter safely and call it manually.
|
||||
let typedProto = Object.getPrototypeOf(Uint8Array.prototype);
|
||||
let getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
|
||||
return getter.call(object.unsafeDereference());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the parameter is suitable to be an array index.
|
||||
*
|
||||
* @param str String
|
||||
* @return Boolean
|
||||
*/
|
||||
function isArrayIndex(str) {
|
||||
// Transform the parameter to a 32-bit unsigned integer.
|
||||
let num = str >>> 0;
|
||||
// Check that the parameter is a canonical Uint32 index.
|
||||
return num + "" === str &&
|
||||
// Array indices cannot attain the maximum Uint32 value.
|
||||
num != -1 >>> 0;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPromiseState,
|
||||
makeDebuggeeValueIfNeeded,
|
||||
createValueGrip,
|
||||
stringIsLong,
|
||||
isTypedArray,
|
||||
isArray,
|
||||
getArrayLength,
|
||||
isArrayIndex,
|
||||
};
|
@ -7,7 +7,8 @@
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const { promisesSpec } = require("devtools/shared/specs/promises");
|
||||
const { expectState, ActorPool } = require("devtools/server/actors/common");
|
||||
const { ObjectActor, createValueGrip } = require("devtools/server/actors/object");
|
||||
const { ObjectActor } = require("devtools/server/actors/object");
|
||||
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
|
@ -10,7 +10,7 @@ const { Cc, Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
|
||||
const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
|
||||
const { createValueGrip, arrayBufferGrip } = require("devtools/server/actors/object");
|
||||
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
||||
const { ActorClassWithSpec } = require("devtools/shared/protocol");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { assert, fetch } = DevToolsUtils;
|
||||
@ -20,6 +20,7 @@ const { sourceSpec } = require("devtools/shared/specs/source");
|
||||
loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
|
||||
loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
|
||||
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
|
||||
loader.lazyRequireGetter(this, "arrayBufferGrip", "devtools/server/actors/array-buffer", true);
|
||||
|
||||
function isEvalSource(source) {
|
||||
let introType = source.introductionType;
|
||||
|
@ -9,7 +9,8 @@
|
||||
const Services = require("Services");
|
||||
const { Cr } = require("chrome");
|
||||
const { ActorPool, GeneratedLocation } = require("devtools/server/actors/common");
|
||||
const { createValueGrip, longStringGrip } = require("devtools/server/actors/object");
|
||||
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
||||
const { longStringGrip } = require("devtools/server/actors/object/long-string");
|
||||
const { ActorClassWithSpec } = require("devtools/shared/protocol");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const flags = require("devtools/shared/flags");
|
||||
|
@ -9,7 +9,7 @@ const { Cu } = require("chrome");
|
||||
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const { CallWatcherActor } = require("devtools/server/actors/call-watcher");
|
||||
const { createValueGrip } = require("devtools/server/actors/object");
|
||||
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
||||
const AutomationTimeline = require("./utils/automation-timeline");
|
||||
const {
|
||||
audionodeSpec,
|
||||
|
@ -12,7 +12,9 @@ const Services = require("Services");
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { DebuggerServer, ActorPool } = require("devtools/server/main");
|
||||
const { ThreadActor } = require("devtools/server/actors/thread");
|
||||
const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object");
|
||||
const { ObjectActor } = require("devtools/server/actors/object");
|
||||
const { LongStringActor } = require("devtools/server/actors/object/long-string");
|
||||
const { createValueGrip, stringIsLong } = require("devtools/server/actors/object/utils");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const ErrorDocs = require("devtools/server/actors/errordocs");
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { LongStringActor } = require("devtools/server/actors/object");
|
||||
const { LongStringActor } = require("devtools/server/actors/object/long-string");
|
||||
|
||||
function run_test() {
|
||||
test_LSA_destroy();
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { SymbolActor } = require("devtools/server/actors/object");
|
||||
const { SymbolActor } = require("devtools/server/actors/object/symbol");
|
||||
|
||||
function run_test() {
|
||||
test_SA_destroy();
|
||||
|
@ -12,6 +12,9 @@
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/CycleCollectedJSRuntime.h"
|
||||
#ifndef RELEASE_OR_BETA
|
||||
#include "mozilla/PerformanceUtils.h"
|
||||
#endif
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/dom/ContentParent.h"
|
||||
#include "mozilla/dom/IdleDeadline.h"
|
||||
@ -659,11 +662,25 @@ ChromeUtils::ClearRecentJSDevError(GlobalObject&)
|
||||
ChromeUtils::RequestPerformanceMetrics(GlobalObject&)
|
||||
{
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
|
||||
// calling all content processes via IPDL (async)
|
||||
nsTArray<ContentParent*> children;
|
||||
ContentParent::GetAll(children);
|
||||
for (uint32_t i = 0; i < children.Length(); i++) {
|
||||
mozilla::Unused << children[i]->SendRequestPerformanceMetrics();
|
||||
}
|
||||
|
||||
|
||||
// collecting the current process counters and notifying them
|
||||
nsTArray<PerformanceInfo> info;
|
||||
CollectPerformanceInfo(info);
|
||||
SystemGroup::Dispatch(TaskCategory::Performance,
|
||||
NS_NewRunnableFunction(
|
||||
"RequestPerformanceMetrics",
|
||||
[info]() { mozilla::Unused << NS_WARN_IF(NS_FAILED(NotifyPerformanceInfo(info))); }
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -7,7 +7,9 @@
|
||||
#ifndef nsPerformanceMetrics_h___
|
||||
#define nsPerformanceMetrics_h___
|
||||
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsIPerformanceMetrics.h"
|
||||
#include "nsString.h"
|
||||
|
||||
|
||||
class PerformanceMetricsDispatchCategory final : public nsIPerformanceMetricsDispatchCategory
|
||||
|
@ -69,6 +69,9 @@
|
||||
#include "mozilla/net/NeckoChild.h"
|
||||
#include "mozilla/net/CookieServiceChild.h"
|
||||
#include "mozilla/net/CaptivePortalService.h"
|
||||
#ifndef RELEASE_OR_BETA
|
||||
#include "mozilla/PerformanceUtils.h"
|
||||
#endif
|
||||
#include "mozilla/plugins/PluginInstanceParent.h"
|
||||
#include "mozilla/plugins/PluginModuleParent.h"
|
||||
#include "mozilla/widget/ScreenManager.h"
|
||||
@ -81,7 +84,6 @@
|
||||
#include "imgLoader.h"
|
||||
#include "GMPServiceChild.h"
|
||||
#include "NullPrincipal.h"
|
||||
#include "nsIPerformanceMetrics.h"
|
||||
#include "nsISimpleEnumerator.h"
|
||||
#include "nsIWorkerDebuggerManager.h"
|
||||
|
||||
@ -1388,27 +1390,9 @@ mozilla::ipc::IPCResult
|
||||
ContentChild::RecvRequestPerformanceMetrics()
|
||||
{
|
||||
#ifndef RELEASE_OR_BETA
|
||||
// iterate on all WorkerDebugger
|
||||
RefPtr<WorkerDebuggerManager> wdm = WorkerDebuggerManager::GetOrCreate();
|
||||
if (NS_WARN_IF(!wdm)) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < wdm->GetDebuggersLength(); index++) {
|
||||
WorkerDebugger* debugger = wdm->GetDebuggerAt(index);
|
||||
MOZ_ASSERT(debugger);
|
||||
SendAddPerformanceMetrics(debugger->ReportPerformanceInfo());
|
||||
}
|
||||
|
||||
// iterate on all DocGroup
|
||||
nsTArray<RefPtr<TabChild>> tabs = TabChild::GetAll();
|
||||
for (const auto& tabChild : tabs) {
|
||||
TabGroup* tabGroup = tabChild->TabGroup();
|
||||
for (auto iter = tabGroup->Iter(); !iter.Done(); iter.Next()) {
|
||||
RefPtr<DocGroup> docGroup = iter.Get()->mDocGroup;
|
||||
SendAddPerformanceMetrics(docGroup->ReportPerformanceInfo());
|
||||
}
|
||||
}
|
||||
nsTArray<PerformanceInfo> info;
|
||||
CollectPerformanceInfo(info);
|
||||
SendAddPerformanceMetrics(info);
|
||||
return IPC_OK();
|
||||
#endif
|
||||
#ifdef RELEASE_OR_BETA
|
||||
|
@ -184,6 +184,9 @@
|
||||
#include "private/pprio.h"
|
||||
#include "ContentProcessManager.h"
|
||||
#include "mozilla/dom/ipc/StructuredCloneData.h"
|
||||
#ifndef RELEASE_OR_BETA
|
||||
#include "mozilla/PerformanceUtils.h"
|
||||
#endif
|
||||
#include "mozilla/psm/PSMContentListener.h"
|
||||
#include "nsPluginHost.h"
|
||||
#include "nsPluginTags.h"
|
||||
@ -193,10 +196,7 @@
|
||||
#include "nsHostObjectProtocolHandler.h"
|
||||
#include "nsICaptivePortalService.h"
|
||||
#include "nsIObjectLoadingContent.h"
|
||||
#include "nsPerformanceMetrics.h"
|
||||
|
||||
#include "nsIBidiKeyboard.h"
|
||||
|
||||
#include "nsLayoutStylesheetCache.h"
|
||||
|
||||
#include "mozilla/Sprintf.h"
|
||||
@ -3322,41 +3322,12 @@ ContentParent::RecvFinishMemoryReport(const uint32_t& aGeneration)
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
ContentParent::RecvAddPerformanceMetrics(const PerformanceInfo& aMetrics)
|
||||
ContentParent::RecvAddPerformanceMetrics(nsTArray<PerformanceInfo>&& aMetrics)
|
||||
{
|
||||
#ifndef RELEASE_OR_BETA
|
||||
// converting the data we get from a child as a notification
|
||||
if (aMetrics.items().IsEmpty()) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIMutableArray> xpItems = do_CreateInstance(NS_ARRAY_CONTRACTID);
|
||||
if (NS_WARN_IF(!xpItems)) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i<aMetrics.items().Length(); i++) {
|
||||
const CategoryDispatch& entry = aMetrics.items()[i];
|
||||
nsCOMPtr<nsIPerformanceMetricsDispatchCategory> item =
|
||||
new PerformanceMetricsDispatchCategory(entry.category(),
|
||||
entry.count());
|
||||
xpItems->AppendElement(item);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPerformanceMetricsData> data =
|
||||
new PerformanceMetricsData(aMetrics.pid(), aMetrics.wid(), aMetrics.pwid(),
|
||||
aMetrics.host(), aMetrics.duration(),
|
||||
aMetrics.worker(), xpItems);
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (!obs) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
obs->NotifyObservers(data, "performance-metrics", nullptr);
|
||||
return IPC_OK();
|
||||
Unused << NS_WARN_IF(NS_FAILED(mozilla::NotifyPerformanceInfo(aMetrics)));
|
||||
#endif
|
||||
#ifdef RELEASE_OR_BETA
|
||||
return IPC_OK();
|
||||
#endif
|
||||
}
|
||||
|
||||
PCycleCollectWithLogsParent*
|
||||
|
@ -848,7 +848,7 @@ private:
|
||||
|
||||
mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport) override;
|
||||
mozilla::ipc::IPCResult RecvFinishMemoryReport(const uint32_t& aGeneration) override;
|
||||
mozilla::ipc::IPCResult RecvAddPerformanceMetrics(const PerformanceInfo& aMetrics) override;
|
||||
mozilla::ipc::IPCResult RecvAddPerformanceMetrics(nsTArray<PerformanceInfo>&& aMetrics) override;
|
||||
|
||||
virtual bool
|
||||
DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) override;
|
||||
|
@ -150,7 +150,7 @@ struct PerformanceInfo
|
||||
// Host of the document, if any
|
||||
nsCString host;
|
||||
// process id
|
||||
uint16_t pid;
|
||||
uint32_t pid;
|
||||
// window id
|
||||
uint64_t wid;
|
||||
// "parent" window id
|
||||
|
@ -1136,7 +1136,7 @@ parent:
|
||||
|
||||
async BHRThreadHang(HangDetails aHangDetails);
|
||||
|
||||
async AddPerformanceMetrics(PerformanceInfo aMetrics);
|
||||
async AddPerformanceMetrics(PerformanceInfo[] aMetrics);
|
||||
both:
|
||||
async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
|
||||
Principal aPrincipal, ClonedMessageData aData);
|
||||
|
@ -18,9 +18,20 @@
|
||||
|
||||
namespace mozilla {
|
||||
struct AudioChunk;
|
||||
class AudioSegment;
|
||||
}
|
||||
DECLARE_USE_COPY_CONSTRUCTORS(mozilla::AudioChunk)
|
||||
|
||||
/**
|
||||
* This allows compilation of nsTArray<AudioSegment> and
|
||||
* AutoTArray<AudioSegment> since without it, static analysis fails on the
|
||||
* mChunks member being a non-memmovable AutoTArray.
|
||||
*
|
||||
* Note that AudioSegment(const AudioSegment&) is deleted, so this should
|
||||
* never come into effect.
|
||||
*/
|
||||
DECLARE_USE_COPY_CONSTRUCTORS(mozilla::AudioSegment)
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
template<typename T>
|
||||
@ -245,7 +256,7 @@ struct AudioChunk {
|
||||
return static_cast<T*>(const_cast<void*>(mChannelData[aChannel]));
|
||||
}
|
||||
|
||||
PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
|
||||
const PrincipalHandle& GetPrincipalHandle() const { return mPrincipalHandle; }
|
||||
|
||||
StreamTime mDuration = 0; // in frames within the buffer
|
||||
RefPtr<ThreadSharedObject> mBuffer; // the buffer object whose lifetime is managed; null means data is all zeroes
|
||||
|
@ -130,13 +130,20 @@ Benchmark::ReturnResult(uint32_t aDecodeFps)
|
||||
mPromise.ResolveIfExists(aDecodeFps, __func__);
|
||||
}
|
||||
|
||||
void
|
||||
Benchmark::ReturnError(const MediaResult& aError)
|
||||
{
|
||||
MOZ_ASSERT(OnThread());
|
||||
|
||||
mPromise.RejectIfExists(aError, __func__);
|
||||
}
|
||||
|
||||
void
|
||||
Benchmark::Dispose()
|
||||
{
|
||||
MOZ_ASSERT(OnThread());
|
||||
|
||||
mKeepAliveUntilComplete = nullptr;
|
||||
mPromise.RejectIfExists(false, __func__);
|
||||
}
|
||||
|
||||
void
|
||||
@ -184,12 +191,12 @@ BenchmarkPlayback::DemuxSamples()
|
||||
mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
|
||||
}
|
||||
if (!mTrackDemuxer) {
|
||||
MainThreadShutdown();
|
||||
Error(MediaResult(NS_ERROR_FAILURE, "Can't create track demuxer"));
|
||||
return;
|
||||
}
|
||||
DemuxNextSample();
|
||||
},
|
||||
[this, ref](const MediaResult& aError) { MainThreadShutdown(); });
|
||||
[this, ref](const MediaResult& aError) { Error(aError); });
|
||||
}
|
||||
|
||||
void
|
||||
@ -217,7 +224,8 @@ BenchmarkPlayback::DemuxNextSample()
|
||||
InitDecoder(Move(*mTrackDemuxer->GetInfo()));
|
||||
break;
|
||||
default:
|
||||
MainThreadShutdown();
|
||||
Error(aError);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -230,18 +238,38 @@ BenchmarkPlayback::InitDecoder(TrackInfo&& aInfo)
|
||||
RefPtr<PDMFactory> platform = new PDMFactory();
|
||||
mDecoder = platform->CreateDecoder({ aInfo, mDecoderTaskQueue });
|
||||
if (!mDecoder) {
|
||||
MainThreadShutdown();
|
||||
Error(MediaResult(NS_ERROR_FAILURE, "Failed to create decoder"));
|
||||
return;
|
||||
}
|
||||
RefPtr<Benchmark> ref(mMainThreadState);
|
||||
mDecoder->Init()->Then(
|
||||
Thread(), __func__,
|
||||
[this, ref](TrackInfo::TrackType aTrackType) {
|
||||
InputExhausted();
|
||||
},
|
||||
[this, ref](const MediaResult& aError) {
|
||||
MainThreadShutdown();
|
||||
});
|
||||
[this, ref](TrackInfo::TrackType aTrackType) { InputExhausted(); },
|
||||
[this, ref](const MediaResult& aError) { Error(aError); });
|
||||
}
|
||||
|
||||
void
|
||||
BenchmarkPlayback::FinalizeShutdown()
|
||||
{
|
||||
MOZ_ASSERT(OnThread());
|
||||
|
||||
MOZ_ASSERT(!mDecoder, "mDecoder must have been shutdown already");
|
||||
mDecoderTaskQueue->BeginShutdown();
|
||||
mDecoderTaskQueue->AwaitShutdownAndIdle();
|
||||
mDecoderTaskQueue = nullptr;
|
||||
|
||||
if (mTrackDemuxer) {
|
||||
mTrackDemuxer->Reset();
|
||||
mTrackDemuxer->BreakCycles();
|
||||
mTrackDemuxer = nullptr;
|
||||
}
|
||||
mDemuxer = nullptr;
|
||||
|
||||
RefPtr<Benchmark> ref(mMainThreadState);
|
||||
Thread()->AsTaskQueue()->BeginShutdown()->Then(
|
||||
ref->Thread(), __func__,
|
||||
[ref]() { ref->Dispose(); },
|
||||
[]() { MOZ_CRASH("not reached"); });
|
||||
}
|
||||
|
||||
void
|
||||
@ -249,10 +277,8 @@ BenchmarkPlayback::MainThreadShutdown()
|
||||
{
|
||||
MOZ_ASSERT(OnThread());
|
||||
|
||||
if (mFinished) {
|
||||
// Nothing more to do.
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(!mFinished, "We've already shutdown");
|
||||
|
||||
mFinished = true;
|
||||
|
||||
if (mDecoder) {
|
||||
@ -263,25 +289,14 @@ BenchmarkPlayback::MainThreadShutdown()
|
||||
mDecoder->Shutdown()->Then(
|
||||
Thread(), __func__,
|
||||
[ref, this]() {
|
||||
mDecoderTaskQueue->BeginShutdown();
|
||||
mDecoderTaskQueue->AwaitShutdownAndIdle();
|
||||
mDecoderTaskQueue = nullptr;
|
||||
|
||||
if (mTrackDemuxer) {
|
||||
mTrackDemuxer->Reset();
|
||||
mTrackDemuxer->BreakCycles();
|
||||
mTrackDemuxer = nullptr;
|
||||
}
|
||||
|
||||
Thread()->AsTaskQueue()->BeginShutdown()->Then(
|
||||
ref->Thread(), __func__,
|
||||
[ref]() { ref->Dispose(); },
|
||||
[]() { MOZ_CRASH("not reached"); });
|
||||
FinalizeShutdown();
|
||||
},
|
||||
[]() { MOZ_CRASH("not reached"); });
|
||||
mDecoder = nullptr;
|
||||
},
|
||||
[]() { MOZ_CRASH("not reached"); });
|
||||
} else {
|
||||
FinalizeShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,6 +304,8 @@ void
|
||||
BenchmarkPlayback::Output(const MediaDataDecoder::DecodedData& aResults)
|
||||
{
|
||||
MOZ_ASSERT(OnThread());
|
||||
MOZ_ASSERT(!mFinished);
|
||||
|
||||
RefPtr<Benchmark> ref(mMainThreadState);
|
||||
mFrameCount += aResults.Length();
|
||||
if (!mDecodeStartTime && mFrameCount >= ref->mParameters.mStartupFrame) {
|
||||
@ -297,9 +314,8 @@ BenchmarkPlayback::Output(const MediaDataDecoder::DecodedData& aResults)
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
int32_t frames = mFrameCount - ref->mParameters.mStartupFrame;
|
||||
TimeDuration elapsedTime = now - mDecodeStartTime.refOr(now);
|
||||
if (!mFinished &&
|
||||
(((frames == ref->mParameters.mFramesToMeasure) && frames > 0) ||
|
||||
elapsedTime >= ref->mParameters.mTimeout || mDrained)) {
|
||||
if (((frames == ref->mParameters.mFramesToMeasure) && frames > 0) ||
|
||||
elapsedTime >= ref->mParameters.mTimeout || mDrained) {
|
||||
uint32_t decodeFps = frames / elapsedTime.ToSeconds();
|
||||
MainThreadShutdown();
|
||||
ref->Dispatch(
|
||||
@ -309,34 +325,64 @@ BenchmarkPlayback::Output(const MediaDataDecoder::DecodedData& aResults)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BenchmarkPlayback::Error(const MediaResult& aError)
|
||||
{
|
||||
MOZ_ASSERT(OnThread());
|
||||
|
||||
RefPtr<Benchmark> ref(mMainThreadState);
|
||||
MainThreadShutdown();
|
||||
ref->Dispatch(NS_NewRunnableFunction(
|
||||
"BenchmarkPlayback::Error",
|
||||
[ref, aError]() { ref->ReturnError(aError); }));
|
||||
}
|
||||
|
||||
void
|
||||
BenchmarkPlayback::InputExhausted()
|
||||
{
|
||||
MOZ_ASSERT(OnThread());
|
||||
if (mFinished || mSampleIndex >= mSamples.Length()) {
|
||||
MOZ_ASSERT(!mFinished);
|
||||
|
||||
if (mSampleIndex >= mSamples.Length()) {
|
||||
Error(MediaResult(NS_ERROR_FAILURE, "Nothing left to decode"));
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<MediaRawData> sample = mSamples[mSampleIndex];
|
||||
RefPtr<Benchmark> ref(mMainThreadState);
|
||||
mDecoder->Decode(mSamples[mSampleIndex])
|
||||
->Then(Thread(), __func__,
|
||||
[ref, this](const MediaDataDecoder::DecodedData& aResults) {
|
||||
Output(aResults);
|
||||
InputExhausted();
|
||||
},
|
||||
[ref, this](const MediaResult& aError) { MainThreadShutdown(); });
|
||||
RefPtr<MediaDataDecoder::DecodePromise> p = mDecoder->Decode(sample);
|
||||
|
||||
mSampleIndex++;
|
||||
if (mSampleIndex == mSamples.Length()) {
|
||||
if (ref->mParameters.mStopAtFrame) {
|
||||
if (mSampleIndex == mSamples.Length() && !ref->mParameters.mStopAtFrame) {
|
||||
// Complete current frame decode then drain if still necessary.
|
||||
p->Then(Thread(), __func__,
|
||||
[ref, this](const MediaDataDecoder::DecodedData& aResults) {
|
||||
Output(aResults);
|
||||
if (!mFinished) {
|
||||
mDecoder->Drain()->Then(
|
||||
Thread(), __func__,
|
||||
[ref, this](const MediaDataDecoder::DecodedData& aResults) {
|
||||
mDrained = true;
|
||||
Output(aResults);
|
||||
MOZ_ASSERT(mFinished, "We must be done now");
|
||||
},
|
||||
[ref, this](const MediaResult& aError) { Error(aError); });
|
||||
}
|
||||
},
|
||||
[ref, this](const MediaResult& aError) { Error(aError); });
|
||||
} else {
|
||||
if (mSampleIndex == mSamples.Length() && ref->mParameters.mStopAtFrame) {
|
||||
mSampleIndex = 0;
|
||||
} else {
|
||||
mDecoder->Drain()->Then(
|
||||
Thread(), __func__,
|
||||
[ref, this](const MediaDataDecoder::DecodedData& aResults) {
|
||||
mDrained = true;
|
||||
Output(aResults);
|
||||
},
|
||||
[ref, this](const MediaResult& aError) { MainThreadShutdown(); });
|
||||
}
|
||||
// Continue decoding
|
||||
p->Then(Thread(), __func__,
|
||||
[ref, this](const MediaDataDecoder::DecodedData& aResults) {
|
||||
Output(aResults);
|
||||
if (!mFinished) {
|
||||
InputExhausted();
|
||||
}
|
||||
},
|
||||
[ref, this](const MediaResult& aError) { Error(aError); });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,12 @@ class BenchmarkPlayback : public QueueObject
|
||||
void InitDecoder(TrackInfo&& aInfo);
|
||||
|
||||
void Output(const MediaDataDecoder::DecodedData& aResults);
|
||||
void Error(const MediaResult& aError);
|
||||
void InputExhausted();
|
||||
|
||||
// Shutdown trackdemuxer and demuxer if any and shutdown the task queues.
|
||||
void FinalizeShutdown();
|
||||
|
||||
Atomic<Benchmark*> mMainThreadState;
|
||||
|
||||
RefPtr<TaskQueue> mDecoderTaskQueue;
|
||||
@ -81,7 +85,7 @@ public:
|
||||
const TimeDuration mTimeout;
|
||||
};
|
||||
|
||||
typedef MozPromise<uint32_t, bool, /* IsExclusive = */ true> BenchmarkPromise;
|
||||
typedef MozPromise<uint32_t, MediaResult, /* IsExclusive = */ true> BenchmarkPromise;
|
||||
|
||||
explicit Benchmark(MediaDataDemuxer* aDemuxer,
|
||||
const Parameters& aParameters = Parameters());
|
||||
@ -93,6 +97,7 @@ private:
|
||||
friend class BenchmarkPlayback;
|
||||
virtual ~Benchmark();
|
||||
void ReturnResult(uint32_t aDecodeFps);
|
||||
void ReturnError(const MediaResult& aError);
|
||||
void Dispose();
|
||||
const Parameters mParameters;
|
||||
RefPtr<Benchmark> mKeepAliveUntilComplete;
|
||||
|
@ -56,6 +56,16 @@ const StreamTime STREAM_TIME_MAX = MEDIA_TIME_MAX;
|
||||
typedef MediaTime GraphTime;
|
||||
const GraphTime GRAPH_TIME_MAX = MEDIA_TIME_MAX;
|
||||
|
||||
/**
|
||||
* The number of chunks allocated by default for a MediaSegment.
|
||||
* Appending more chunks than this will cause further allocations.
|
||||
*
|
||||
* 16 is an arbitrary number intended to cover the most common cases in the
|
||||
* MediaStreamGraph (1 with silence and 1-2 with data for a realtime track)
|
||||
* with some margin.
|
||||
*/
|
||||
const size_t DEFAULT_SEGMENT_CAPACITY = 16;
|
||||
|
||||
/**
|
||||
* We pass the principal through the MediaStreamGraph by wrapping it in a thread
|
||||
* safe nsMainThreadPtrHandle, since it cannot be used directly off the main
|
||||
@ -74,13 +84,13 @@ inline PrincipalHandle MakePrincipalHandle(nsIPrincipal* aPrincipal)
|
||||
|
||||
#define PRINCIPAL_HANDLE_NONE nullptr
|
||||
|
||||
inline nsIPrincipal* GetPrincipalFromHandle(PrincipalHandle& aPrincipalHandle)
|
||||
inline nsIPrincipal* GetPrincipalFromHandle(const PrincipalHandle& aPrincipalHandle)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return aPrincipalHandle.get();
|
||||
}
|
||||
|
||||
inline bool PrincipalHandleMatches(PrincipalHandle& aPrincipalHandle,
|
||||
inline bool PrincipalHandleMatches(const PrincipalHandle& aPrincipalHandle,
|
||||
nsIPrincipal* aOther)
|
||||
{
|
||||
if (!aOther) {
|
||||
@ -138,14 +148,14 @@ public:
|
||||
/**
|
||||
* Gets the last principal id that was appended to this segment.
|
||||
*/
|
||||
PrincipalHandle GetLastPrincipalHandle() const { return mLastPrincipalHandle; }
|
||||
const PrincipalHandle& GetLastPrincipalHandle() const { return mLastPrincipalHandle; }
|
||||
/**
|
||||
* Called by the MediaStreamGraph as it appends a chunk with a different
|
||||
* principal id than the current one.
|
||||
*/
|
||||
void SetLastPrincipalHandle(const PrincipalHandle& aLastPrincipalHandle)
|
||||
void SetLastPrincipalHandle(PrincipalHandle aLastPrincipalHandle)
|
||||
{
|
||||
mLastPrincipalHandle = aLastPrincipalHandle;
|
||||
mLastPrincipalHandle = Forward<PrincipalHandle>(aLastPrincipalHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,7 +364,8 @@ public:
|
||||
void Clear() override
|
||||
{
|
||||
mDuration = 0;
|
||||
mChunks.Clear();
|
||||
mChunks.ClearAndRetainStorage();
|
||||
mChunks.SetCapacity(DEFAULT_SEGMENT_CAPACITY);
|
||||
}
|
||||
|
||||
class ChunkIterator {
|
||||
@ -436,15 +447,24 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit MediaSegmentBase(Type aType) : MediaSegment(aType) {}
|
||||
explicit MediaSegmentBase(Type aType)
|
||||
: MediaSegment(aType)
|
||||
, mChunks()
|
||||
{}
|
||||
|
||||
MediaSegmentBase(MediaSegmentBase&& aSegment)
|
||||
: MediaSegment(Move(aSegment))
|
||||
, mChunks(Move(aSegment.mChunks))
|
||||
, mChunks()
|
||||
#ifdef MOZILLA_INTERNAL_API
|
||||
, mTimeStamp(Move(aSegment.mTimeStamp))
|
||||
#endif
|
||||
{}
|
||||
{
|
||||
mChunks.SwapElements(aSegment.mChunks);
|
||||
MOZ_ASSERT(mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
|
||||
"Capacity must be retained in self after swap");
|
||||
MOZ_ASSERT(aSegment.mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
|
||||
"Capacity must be retained in other after swap");
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the contents of aSource to this segment, clearing aSource.
|
||||
@ -454,12 +474,20 @@ protected:
|
||||
MOZ_ASSERT(aSource->mDuration >= 0);
|
||||
mDuration += aSource->mDuration;
|
||||
aSource->mDuration = 0;
|
||||
size_t offset = 0;
|
||||
if (!mChunks.IsEmpty() && !aSource->mChunks.IsEmpty() &&
|
||||
mChunks[mChunks.Length() - 1].CanCombineWithFollowing(aSource->mChunks[0])) {
|
||||
mChunks[mChunks.Length() - 1].mDuration += aSource->mChunks[0].mDuration;
|
||||
aSource->mChunks.RemoveElementAt(0);
|
||||
offset = 1;
|
||||
}
|
||||
mChunks.AppendElements(Move(aSource->mChunks));
|
||||
|
||||
for (; offset < aSource->mChunks.Length(); ++offset) {
|
||||
mChunks.AppendElement(Move(aSource->mChunks[offset]));
|
||||
}
|
||||
|
||||
aSource->mChunks.ClearAndRetainStorage();
|
||||
MOZ_ASSERT(aSource->mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
|
||||
"Capacity must be retained after appending from aSource");
|
||||
}
|
||||
|
||||
void AppendSliceInternal(const MediaSegmentBase<C, Chunk>& aSource,
|
||||
@ -512,8 +540,15 @@ protected:
|
||||
t -= c->GetDuration();
|
||||
chunksToRemove = i + 1 - aStartIndex;
|
||||
}
|
||||
mChunks.RemoveElementsAt(aStartIndex, chunksToRemove);
|
||||
if (aStartIndex == 0 && chunksToRemove == mChunks.Length()) {
|
||||
mChunks.ClearAndRetainStorage();
|
||||
} else {
|
||||
mChunks.RemoveElementsAt(aStartIndex, chunksToRemove);
|
||||
}
|
||||
mDuration -= aDuration - t;
|
||||
|
||||
MOZ_ASSERT(mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
|
||||
"Capacity must be retained after removing chunks");
|
||||
}
|
||||
|
||||
void RemoveTrailing(StreamTime aKeep, uint32_t aStartIndex)
|
||||
@ -535,10 +570,12 @@ protected:
|
||||
if (i+1 < mChunks.Length()) {
|
||||
mChunks.RemoveElementsAt(i+1, mChunks.Length() - (i+1));
|
||||
}
|
||||
MOZ_ASSERT(mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
|
||||
"Capacity must be retained after removing chunks");
|
||||
// Caller must adjust mDuration
|
||||
}
|
||||
|
||||
nsTArray<Chunk> mChunks;
|
||||
AutoTArray<Chunk, DEFAULT_SEGMENT_CAPACITY> mChunks;
|
||||
#ifdef MOZILLA_INTERNAL_API
|
||||
mozilla::TimeStamp mTimeStamp;
|
||||
#endif
|
||||
|
@ -243,7 +243,7 @@ MediaStreamGraphImpl::ProcessChunkMetadataForInterval(MediaStream* aStream,
|
||||
if (chunk->IsNull() || offset < aStart) {
|
||||
continue;
|
||||
}
|
||||
PrincipalHandle principalHandle = chunk->GetPrincipalHandle();
|
||||
const PrincipalHandle& principalHandle = chunk->GetPrincipalHandle();
|
||||
if (principalHandle != aSegment.GetLastPrincipalHandle()) {
|
||||
aSegment.SetLastPrincipalHandle(principalHandle);
|
||||
LOG(LogLevel::Debug,
|
||||
@ -332,31 +332,32 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
MediaStreamGraphImpl::AudioTrackPresent(bool& aNeedsAEC)
|
||||
MediaStreamGraphImpl::AudioTrackPresent()
|
||||
{
|
||||
MOZ_ASSERT(OnGraphThreadOrNotRunning());
|
||||
|
||||
bool audioTrackPresent = false;
|
||||
for (uint32_t i = 0; i < mStreams.Length() && audioTrackPresent == false; ++i) {
|
||||
MediaStream* stream = mStreams[i];
|
||||
SourceMediaStream* source = stream->AsSourceStream();
|
||||
#ifdef MOZ_WEBRTC
|
||||
if (source && source->NeedsMixing()) {
|
||||
aNeedsAEC = true;
|
||||
}
|
||||
#endif
|
||||
// If this is a AudioNodeStream, force a AudioCallbackDriver.
|
||||
for (MediaStream* stream : mStreams) {
|
||||
if (stream->AsAudioNodeStream()) {
|
||||
audioTrackPresent = true;
|
||||
} else {
|
||||
for (StreamTracks::TrackIter tracks(stream->GetStreamTracks(), MediaSegment::AUDIO);
|
||||
!tracks.IsEnded(); tracks.Next()) {
|
||||
audioTrackPresent = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (source) {
|
||||
|
||||
if (!StreamTracks::TrackIter(
|
||||
stream->GetStreamTracks(),
|
||||
MediaSegment::AUDIO
|
||||
).IsEnded()) {
|
||||
audioTrackPresent = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (SourceMediaStream* source = stream->AsSourceStream()) {
|
||||
audioTrackPresent = source->HasPendingAudioTrack();
|
||||
}
|
||||
|
||||
if (audioTrackPresent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// XXX For some reason, there are race conditions when starting an audio input where
|
||||
@ -365,9 +366,6 @@ MediaStreamGraphImpl::AudioTrackPresent(bool& aNeedsAEC)
|
||||
if (!audioTrackPresent && mInputDeviceUsers.Count() != 0) {
|
||||
NS_WARNING("No audio tracks, but full-duplex audio is enabled!!!!!");
|
||||
audioTrackPresent = true;
|
||||
#ifdef MOZ_WEBRTC
|
||||
aNeedsAEC = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
return audioTrackPresent;
|
||||
@ -377,8 +375,7 @@ void
|
||||
MediaStreamGraphImpl::UpdateStreamOrder()
|
||||
{
|
||||
MOZ_ASSERT(OnGraphThreadOrNotRunning());
|
||||
bool shouldAEC = false;
|
||||
bool audioTrackPresent = AudioTrackPresent(shouldAEC);
|
||||
bool audioTrackPresent = AudioTrackPresent();
|
||||
|
||||
// Note that this looks for any audio streams, input or output, and switches to a
|
||||
// SystemClockDriver if there are none. However, if another is already pending, let that
|
||||
@ -895,8 +892,7 @@ MediaStreamGraphImpl::CloseAudioInputImpl(AudioDataListener *aListener)
|
||||
mAudioInputs.RemoveElement(aListener);
|
||||
|
||||
// Switch Drivers since we're adding or removing an input (to nothing/system or output only)
|
||||
bool shouldAEC = false;
|
||||
bool audioTrackPresent = AudioTrackPresent(shouldAEC);
|
||||
bool audioTrackPresent = AudioTrackPresent();
|
||||
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (LifecycleStateRef() == LIFECYCLE_RUNNING) {
|
||||
@ -4148,8 +4144,7 @@ MediaStreamGraphImpl::ApplyAudioContextOperationImpl(
|
||||
// This is the same logic as in UpdateStreamOrder, but it's simpler to have it
|
||||
// here as well so we don't have to store the Promise(s) on the Graph.
|
||||
if (aOperation != AudioContextOperation::Resume) {
|
||||
bool shouldAEC = false;
|
||||
bool audioTrackPresent = AudioTrackPresent(shouldAEC);
|
||||
bool audioTrackPresent = AudioTrackPresent();
|
||||
|
||||
if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) {
|
||||
CurrentDriver()->AsAudioCallbackDriver()->
|
||||
|
@ -322,9 +322,8 @@ public:
|
||||
|
||||
/**
|
||||
* Determine if we have any audio tracks, or are about to add any audiotracks.
|
||||
* Also checks if we'll need the AEC running (i.e. microphone input tracks)
|
||||
*/
|
||||
bool AudioTrackPresent(bool& aNeedsAEC);
|
||||
bool AudioTrackPresent();
|
||||
|
||||
/**
|
||||
* Sort mStreams so that every stream not in a cycle is after any streams
|
||||
|
@ -40,8 +40,11 @@ public:
|
||||
Image* GetImage() const { return mImage; }
|
||||
void SetForceBlack(bool aForceBlack) { mForceBlack = aForceBlack; }
|
||||
bool GetForceBlack() const { return mForceBlack; }
|
||||
void SetPrincipalHandle(const PrincipalHandle& aPrincipalHandle) { mPrincipalHandle = aPrincipalHandle; }
|
||||
PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
|
||||
void SetPrincipalHandle(PrincipalHandle aPrincipalHandle)
|
||||
{
|
||||
mPrincipalHandle = Forward<PrincipalHandle>(aPrincipalHandle);
|
||||
}
|
||||
const PrincipalHandle& GetPrincipalHandle() const { return mPrincipalHandle; }
|
||||
const gfx::IntSize& GetIntrinsicSize() const { return mIntrinsicSize; }
|
||||
void SetNull();
|
||||
void TakeFrom(VideoFrame* aFrame);
|
||||
@ -89,7 +92,7 @@ struct VideoChunk {
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrincipalHandle GetPrincipalHandle() const { return mFrame.GetPrincipalHandle(); }
|
||||
const PrincipalHandle& GetPrincipalHandle() const { return mFrame.GetPrincipalHandle(); }
|
||||
|
||||
StreamTime mDuration;
|
||||
VideoFrame mFrame;
|
||||
|
@ -897,7 +897,7 @@ StripURIForReporting(nsIURI* aURI,
|
||||
|
||||
nsresult
|
||||
nsCSPContext::GatherSecurityPolicyViolationEventData(
|
||||
nsISupports* aBlockedContentSource,
|
||||
nsIURI* aBlockedURI,
|
||||
nsIURI* aOriginalURI,
|
||||
nsAString& aViolatedDirective,
|
||||
uint32_t aViolatedPolicyIndex,
|
||||
@ -921,23 +921,9 @@ nsCSPContext::GatherSecurityPolicyViolationEventData(
|
||||
aViolationEventInit.mReferrer = mReferrer;
|
||||
|
||||
// blocked-uri
|
||||
if (aBlockedContentSource) {
|
||||
if (aBlockedURI) {
|
||||
nsAutoCString reportBlockedURI;
|
||||
nsCOMPtr<nsIURI> uri = do_QueryInterface(aBlockedContentSource);
|
||||
// could be a string or URI
|
||||
if (uri) {
|
||||
StripURIForReporting(uri, mSelfURI, reportBlockedURI);
|
||||
} else {
|
||||
nsCOMPtr<nsISupportsCString> cstr = do_QueryInterface(aBlockedContentSource);
|
||||
if (cstr) {
|
||||
cstr->GetData(reportBlockedURI);
|
||||
}
|
||||
}
|
||||
if (reportBlockedURI.IsEmpty()) {
|
||||
// this can happen for frame-ancestors violation where the violating
|
||||
// ancestor is cross-origin.
|
||||
NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report.");
|
||||
}
|
||||
StripURIForReporting(aBlockedURI, mSelfURI, reportBlockedURI);
|
||||
aViolationEventInit.mBlockedURI = NS_ConvertUTF8toUTF16(reportBlockedURI);
|
||||
}
|
||||
|
||||
@ -1266,8 +1252,10 @@ class CSPReportSenderRunnable final : public Runnable
|
||||
|
||||
// 0) prepare violation data
|
||||
mozilla::dom::SecurityPolicyViolationEventInit init;
|
||||
// mBlockedContentSource could be a URI or a string.
|
||||
nsCOMPtr<nsIURI> blockedURI = do_QueryInterface(mBlockedContentSource);
|
||||
rv = mCSPContext->GatherSecurityPolicyViolationEventData(
|
||||
mBlockedContentSource, mOriginalURI,
|
||||
blockedURI, mOriginalURI,
|
||||
mViolatedDirective, mViolatedPolicyIndex,
|
||||
mSourceFile, mScriptSample, mLineNum,
|
||||
init);
|
||||
@ -1285,8 +1273,6 @@ class CSPReportSenderRunnable final : public Runnable
|
||||
mCSPContext->SendReports(init, mViolatedPolicyIndex);
|
||||
|
||||
// 3) log to console (one per policy violation)
|
||||
// mBlockedContentSource could be a URI or a string.
|
||||
nsCOMPtr<nsIURI> blockedURI = do_QueryInterface(mBlockedContentSource);
|
||||
// if mBlockedContentSource is not a URI, it could be a string
|
||||
nsCOMPtr<nsISupportsCString> blockedString = do_QueryInterface(mBlockedContentSource);
|
||||
|
||||
|
@ -64,9 +64,8 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
||||
/**
|
||||
* Construct SecurityPolicyViolationEventInit structure.
|
||||
*
|
||||
* @param aBlockedContentSource
|
||||
* Either a CSP Source (like 'self', as string) or nsIURI: the source
|
||||
* of the violation.
|
||||
* @param aBlockedURI
|
||||
* A nsIURI: the source of the violation.
|
||||
* @param aOriginalUri
|
||||
* The original URI if the blocked content is a redirect, else null
|
||||
* @param aViolatedDirective
|
||||
@ -81,7 +80,7 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
||||
* The output
|
||||
*/
|
||||
nsresult GatherSecurityPolicyViolationEventData(
|
||||
nsISupports* aBlockedContentSource,
|
||||
nsIURI* aBlockedURI,
|
||||
nsIURI* aOriginalURI,
|
||||
nsAString& aViolatedDirective,
|
||||
uint32_t aViolatedPolicyIndex,
|
||||
|
@ -48,7 +48,7 @@ window.checkResults = function(reportObj) {
|
||||
ok(cspReport["referrer"].startsWith("http://mochi.test:8888/tests/dom/security/test/csp/test_report.html"),
|
||||
"Incorrect referrer");
|
||||
|
||||
is(cspReport["blocked-uri"], "self", "Incorrect blocked-uri");
|
||||
is(cspReport["blocked-uri"], "", "Incorrect blocked-uri");
|
||||
|
||||
is(cspReport["violated-directive"], "default-src", "Incorrect violated-directive");
|
||||
|
||||
|
@ -106,7 +106,7 @@ function run_test() {
|
||||
createInstance(Ci.nsISupportsString);
|
||||
content.data = "";
|
||||
// test that inline script violations cause a report.
|
||||
makeTest(0, {"blocked-uri": "self"}, false,
|
||||
makeTest(0, {"blocked-uri": ""}, false,
|
||||
function(csp) {
|
||||
let inlineOK = true;
|
||||
inlineOK = csp.getAllowsInline(Ci.nsIContentPolicy.TYPE_SCRIPT,
|
||||
@ -120,7 +120,7 @@ function run_test() {
|
||||
});
|
||||
|
||||
// test that eval violations cause a report.
|
||||
makeTest(1, {"blocked-uri": "self",
|
||||
makeTest(1, {"blocked-uri": "",
|
||||
// JSON script-sample is UTF8 encoded
|
||||
"script-sample" : "\xc2\xa3\xc2\xa5\xc2\xb5\xe5\x8c\x97\xf0\xa0\x9d\xb9"}, false,
|
||||
function(csp) {
|
||||
@ -153,7 +153,7 @@ function run_test() {
|
||||
});
|
||||
|
||||
// test that inline script violations cause a report in report-only policy
|
||||
makeTest(3, {"blocked-uri": "self"}, true,
|
||||
makeTest(3, {"blocked-uri": ""}, true,
|
||||
function(csp) {
|
||||
let inlineOK = true;
|
||||
let content = Cc["@mozilla.org/supports-string;1"].
|
||||
@ -170,7 +170,7 @@ function run_test() {
|
||||
});
|
||||
|
||||
// test that eval violations cause a report in report-only policy
|
||||
makeTest(4, {"blocked-uri": "self"}, true,
|
||||
makeTest(4, {"blocked-uri": ""}, true,
|
||||
function(csp) {
|
||||
let evalOK = true, oReportViolation = {'value': false};
|
||||
evalOK = csp.getAllowsEval(oReportViolation);
|
||||
|
@ -9,6 +9,7 @@ support-files =
|
||||
worker_bug1004814.js
|
||||
geo_leak_test.html
|
||||
dummy.html
|
||||
ping_worker.html
|
||||
test_largeAllocation.html
|
||||
test_largeAllocation.html^headers^
|
||||
test_largeAllocation2.html
|
||||
|
@ -4,52 +4,113 @@
|
||||
* 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/. */
|
||||
|
||||
const TEST_URL = "http://example.com/browser/dom/tests/browser/dummy.html";
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
const ROOT_URL = "http://example.com/browser/dom/tests/browser";
|
||||
const DUMMY_URL = ROOT_URL + "/dummy.html";
|
||||
const WORKER_URL = ROOT_URL + "/ping_worker.html";
|
||||
|
||||
|
||||
let nextId = 0;
|
||||
|
||||
function jsonrpc(tab, method, params) {
|
||||
let currentId = nextId++;
|
||||
let messageManager = tab.linkedBrowser.messageManager;
|
||||
messageManager.sendAsyncMessage("jsonrpc", {
|
||||
id: currentId,
|
||||
method: method,
|
||||
params: params
|
||||
});
|
||||
return new Promise(function (resolve, reject) {
|
||||
messageManager.addMessageListener("jsonrpc", function listener(event) {
|
||||
let { id, result, error } = event.data;
|
||||
if (id !== currentId) {
|
||||
return;
|
||||
}
|
||||
messageManager.removeMessageListener("jsonrpc", listener);
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function postMessageToWorker(tab, message) {
|
||||
return jsonrpc(tab, "postMessageToWorker", [WORKER_URL, message]);
|
||||
}
|
||||
|
||||
add_task(async function test() {
|
||||
if (!AppConstants.RELEASE_OR_BETA) {
|
||||
SpecialPowers.setBoolPref('dom.performance.enable_scheduler_timing', true);
|
||||
waitForExplicitFinish();
|
||||
SpecialPowers.setBoolPref('dom.performance.enable_scheduler_timing', true);
|
||||
waitForExplicitFinish();
|
||||
|
||||
await BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com" },
|
||||
async function(browser) {
|
||||
// Load 3 pages and wait. The 3rd one has a worker
|
||||
let page1 = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser, opening: 'about:about', forceNewProcess: false
|
||||
});
|
||||
|
||||
// grab events..
|
||||
var events = [];
|
||||
function getInfoFromService(subject, topic, value) {
|
||||
subject = subject.QueryInterface(Ci.nsIPerformanceMetricsData);
|
||||
if (subject.host == "example.com") {
|
||||
events.push(subject);
|
||||
let page2 = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser, opening: 'about:memory', forceNewProcess: false
|
||||
});
|
||||
|
||||
let page3 = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser, opening: "about:performance", forceNewProcess: true
|
||||
});
|
||||
|
||||
let parent_process_event = false;
|
||||
let worker_event = false;
|
||||
|
||||
// load a 4th tab with a worker
|
||||
await BrowserTestUtils.withNewTab({ gBrowser, url: WORKER_URL },
|
||||
async function(browser) {
|
||||
// grab events..
|
||||
var events = [];
|
||||
function getInfoFromService(subject, topic, value) {
|
||||
subject = subject.QueryInterface(Ci.nsIMutableArray);
|
||||
let enumerator = subject.enumerate();
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let item = enumerator.getNext();
|
||||
item = item.QueryInterface(Ci.nsIPerformanceMetricsData);
|
||||
if (item.pid == Services.appinfo.processID) {
|
||||
parent_process_event = true;
|
||||
}
|
||||
if (item.worker) {
|
||||
worker_event = true;
|
||||
}
|
||||
events.push(item);
|
||||
}
|
||||
Services.obs.addObserver(getInfoFromService, "performance-metrics");
|
||||
}
|
||||
|
||||
// trigger an IPDL call
|
||||
Services.obs.addObserver(getInfoFromService, "performance-metrics");
|
||||
|
||||
// wait until we get some events back by triggering requestPerformanceMetrics
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
ChromeUtils.requestPerformanceMetrics();
|
||||
return events.length > 10;
|
||||
}, "wait for events to come in", 500, 10);
|
||||
|
||||
// wait until we get the events back
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
return events.length > 0;
|
||||
}, "wait for events to come in", 100, 20);
|
||||
|
||||
// let's check the last example.com tab event we got
|
||||
let last = events[0];
|
||||
Assert.equal(last.host, "example.com", "host should be example.com");
|
||||
Assert.ok(last.duration > 0, "Duration should be positive");
|
||||
BrowserTestUtils.removeTab(page1);
|
||||
BrowserTestUtils.removeTab(page2);
|
||||
BrowserTestUtils.removeTab(page3);
|
||||
|
||||
// let's check the events
|
||||
let duration = 0;
|
||||
let total = 0;
|
||||
for (let i=0; i < events.length; i++) {
|
||||
duration += events[i].duration;
|
||||
// let's look at the XPCOM data we got back
|
||||
let items = last.items.QueryInterface(Ci.nsIMutableArray);
|
||||
let items = events[i].items.QueryInterface(Ci.nsIMutableArray);
|
||||
let enumerator = items.enumerate();
|
||||
let total = 0;
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let item = enumerator.getNext();
|
||||
item = item.QueryInterface(Ci.nsIPerformanceMetricsDispatchCategory);
|
||||
total += item.count;
|
||||
}
|
||||
Assert.ok(total > 0);
|
||||
});
|
||||
SpecialPowers.clearUserPref('dom.performance.enable_scheduler_timing');
|
||||
}
|
||||
}
|
||||
|
||||
Assert.ok(duration > 0, "Duration should be positive");
|
||||
Assert.ok(total > 0, "Should get a positive count");
|
||||
Assert.ok(parent_process_event, "parent process sent back some events");
|
||||
});
|
||||
|
||||
SpecialPowers.clearUserPref('dom.performance.enable_scheduler_timing');
|
||||
});
|
||||
|
17
dom/tests/browser/ping_worker.html
Normal file
17
dom/tests/browser/ping_worker.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
|
||||
var myWorker;
|
||||
function init() {
|
||||
myWorker = new Worker('ping_worker.js');
|
||||
myWorker.postMessage("ping");
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<h1>A page with a worker</h1>
|
||||
</body>
|
||||
</html>
|
11
dom/tests/browser/ping_worker.js
Normal file
11
dom/tests/browser/ping_worker.js
Normal file
@ -0,0 +1,11 @@
|
||||
/* 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";
|
||||
|
||||
function messageListener(event) {
|
||||
postMessage("pong");
|
||||
}
|
||||
|
||||
addEventListener("message", { handleEvent: messageListener });
|
@ -502,12 +502,16 @@ WorkerDebugger::ReportPerformanceInfo()
|
||||
uint16_t count = perf->GetTotalDispatchCount();
|
||||
uint64_t duration = perf->GetExecutionDuration();
|
||||
RefPtr<nsIURI> uri = mWorkerPrivate->GetResolvedScriptURI();
|
||||
CategoryDispatch item = CategoryDispatch(DispatchCategory::Worker.GetValue(), count);
|
||||
|
||||
// Workers only produce metrics for a single category - DispatchCategory::Worker.
|
||||
// We still return an array of CategoryDispatch so the PerformanceInfo
|
||||
// struct is common to all performance counters throughout Firefox.
|
||||
FallibleTArray<CategoryDispatch> items;
|
||||
CategoryDispatch item = CategoryDispatch(DispatchCategory::Worker.GetValue(), count);
|
||||
if (!items.AppendElement(item, fallible)) {
|
||||
NS_ERROR("Could not complete the operation");
|
||||
return PerformanceInfo(uri->GetSpecOrDefault(), pid, wid, pwid, duration,
|
||||
true, items);
|
||||
true, items);
|
||||
}
|
||||
perf->ResetPerformanceCounters();
|
||||
return PerformanceInfo(uri->GetSpecOrDefault(), pid, wid, pwid, duration,
|
||||
|
@ -424,10 +424,14 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
// PerformanceStorage needs to be initialized on the worker thread before
|
||||
// being used on main-thread. Let's be sure that it is created before any
|
||||
// PerformanceStorage & PerformanceCounter both need to be initialized
|
||||
// on the worker thread before being used on main-thread.
|
||||
// Let's be sure that it is created before any
|
||||
// content loading.
|
||||
aWorkerPrivate->EnsurePerformanceStorage();
|
||||
#ifndef RELEASE_OR_BETA
|
||||
aWorkerPrivate->EnsurePerformanceCounter();
|
||||
#endif
|
||||
|
||||
ErrorResult rv;
|
||||
workerinternals::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript, rv);
|
||||
@ -5210,15 +5214,19 @@ WorkerPrivate::DumpCrashInformation(nsACString& aString)
|
||||
}
|
||||
|
||||
#ifndef RELEASE_OR_BETA
|
||||
PerformanceCounter*
|
||||
WorkerPrivate::GetPerformanceCounter()
|
||||
void
|
||||
WorkerPrivate::EnsurePerformanceCounter()
|
||||
{
|
||||
AssertIsOnWorkerThread();
|
||||
|
||||
if (!mPerformanceCounter) {
|
||||
mPerformanceCounter = new PerformanceCounter(NS_ConvertUTF16toUTF8(mWorkerName));
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceCounter*
|
||||
WorkerPrivate::GetPerformanceCounter()
|
||||
{
|
||||
MOZ_ASSERT(mPerformanceCounter);
|
||||
return mPerformanceCounter;
|
||||
}
|
||||
#endif
|
||||
|
@ -561,6 +561,11 @@ public:
|
||||
void
|
||||
EnsurePerformanceStorage();
|
||||
|
||||
#ifndef RELEASE_OR_BETA
|
||||
void
|
||||
EnsurePerformanceCounter();
|
||||
#endif
|
||||
|
||||
const ClientInfo&
|
||||
GetClientInfo() const;
|
||||
|
||||
|
@ -8,7 +8,7 @@ authors = [
|
||||
description = "Remote Cubeb IPC"
|
||||
|
||||
[dependencies]
|
||||
cubeb = "0.5"
|
||||
cubeb = "0.5.2"
|
||||
bincode = "1.0"
|
||||
bytes = "0.4"
|
||||
futures = "0.1.18"
|
||||
|
@ -9,7 +9,7 @@ description = "Remote cubeb server"
|
||||
|
||||
[dependencies]
|
||||
audioipc = { path = "../audioipc" }
|
||||
cubeb = "0.5"
|
||||
cubeb = "0.5.2"
|
||||
bytes = "0.4"
|
||||
lazycell = "^0.4"
|
||||
libc = "0.2"
|
||||
|
29
mfbt/Algorithm.h
Normal file
29
mfbt/Algorithm.h
Normal file
@ -0,0 +1,29 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* A polyfill for `<algorithm>`. */
|
||||
|
||||
#ifndef mozilla_Algorithm_h
|
||||
#define mozilla_Algorithm_h
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Returns true if all elements in the range [aFirst, aLast)
|
||||
// satisfy the predicate aPred.
|
||||
template <class Iter, class Pred>
|
||||
constexpr bool AllOf(Iter aFirst, Iter aLast, Pred aPred)
|
||||
{
|
||||
for (; aFirst != aLast; ++aFirst) {
|
||||
if (!aPred(*aFirst)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_Algorithm_h
|
@ -12,6 +12,7 @@ TEST_DIRS += ['tests']
|
||||
Library('mfbt')
|
||||
|
||||
EXPORTS.mozilla = [
|
||||
'Algorithm.h',
|
||||
'Alignment.h',
|
||||
'AllocPolicy.h',
|
||||
'AlreadyAddRefed.h',
|
||||
|
44
mfbt/tests/TestAlgorithm.cpp
Normal file
44
mfbt/tests/TestAlgorithm.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Algorithm.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include <iterator>
|
||||
|
||||
static constexpr bool even(int32_t n) { return !(n & 1); }
|
||||
static constexpr bool odd(int32_t n) { return (n & 1); }
|
||||
|
||||
void TestAllOf()
|
||||
{
|
||||
using namespace mozilla;
|
||||
using namespace std;
|
||||
|
||||
constexpr static int32_t arr1[3] = {1, 2, 3};
|
||||
MOZ_RELEASE_ASSERT(!AllOf(begin(arr1), end(arr1), even));
|
||||
MOZ_RELEASE_ASSERT(!AllOf(begin(arr1), end(arr1), odd));
|
||||
static_assert(!AllOf(arr1, arr1 + ArrayLength(arr1), even), "1-1");
|
||||
static_assert(!AllOf(arr1, arr1 + ArrayLength(arr1), odd), "1-2");
|
||||
|
||||
constexpr static int32_t arr2[3] = {1, 3, 5};
|
||||
MOZ_RELEASE_ASSERT(!AllOf(begin(arr2), end(arr2), even));
|
||||
MOZ_RELEASE_ASSERT(AllOf(begin(arr2), end(arr2), odd));
|
||||
static_assert(!AllOf(arr2, arr2 + ArrayLength(arr2), even), "2-1");
|
||||
static_assert(AllOf(arr2, arr2 + ArrayLength(arr2), odd), "2-2");
|
||||
|
||||
constexpr static int32_t arr3[3] = {2, 4, 6};
|
||||
MOZ_RELEASE_ASSERT(AllOf(begin(arr3), end(arr3), even));
|
||||
MOZ_RELEASE_ASSERT(!AllOf(begin(arr3), end(arr3), odd));
|
||||
static_assert(AllOf(arr3, arr3 + ArrayLength(arr3), even), "3-1");
|
||||
static_assert(!AllOf(arr3, arr3 + ArrayLength(arr3), odd), "3-2");
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
TestAllOf();
|
||||
return 0;
|
||||
}
|
@ -10,6 +10,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT']:
|
||||
]
|
||||
|
||||
CppUnitTests([
|
||||
'TestAlgorithm',
|
||||
'TestArray',
|
||||
'TestArrayUtils',
|
||||
'TestAtomics',
|
||||
|
@ -286,26 +286,31 @@ TRRService::Observe(nsISupports *aSubject,
|
||||
} else if (!strcmp(aTopic, NS_CAPTIVE_PORTAL_CONNECTIVITY)) {
|
||||
nsAutoCString data = NS_ConvertUTF16toUTF8(aData);
|
||||
LOG(("TRRservice captive portal was %s\n", data.get()));
|
||||
if (!mTRRBLStorage) {
|
||||
mTRRBLStorage = DataStorage::Get(DataStorageClass::TRRBlacklist);
|
||||
if (mTRRBLStorage) {
|
||||
bool storageWillPersist = true;
|
||||
if (NS_FAILED(mTRRBLStorage->Init(storageWillPersist))) {
|
||||
mTRRBLStorage = nullptr;
|
||||
}
|
||||
if (mClearTRRBLStorage) {
|
||||
if (mTRRBLStorage) {
|
||||
mTRRBLStorage->Clear();
|
||||
if (data.Equals("clear")) {
|
||||
if (!mTRRBLStorage) {
|
||||
mTRRBLStorage = DataStorage::Get(DataStorageClass::TRRBlacklist);
|
||||
if (mTRRBLStorage) {
|
||||
bool storageWillPersist = true;
|
||||
if (NS_FAILED(mTRRBLStorage->Init(storageWillPersist))) {
|
||||
mTRRBLStorage = nullptr;
|
||||
}
|
||||
if (mClearTRRBLStorage) {
|
||||
if (mTRRBLStorage) {
|
||||
mTRRBLStorage->Clear();
|
||||
}
|
||||
mClearTRRBLStorage = false;
|
||||
}
|
||||
mClearTRRBLStorage = false;
|
||||
}
|
||||
}
|
||||
if (mConfirmationState != CONFIRM_OK) {
|
||||
mConfirmationState = CONFIRM_TRYING;
|
||||
MaybeConfirm();
|
||||
} else {
|
||||
LOG(("TRRservice CP clear when already up!\n"));
|
||||
}
|
||||
mCaptiveIsPassed = true;
|
||||
}
|
||||
|
||||
mConfirmationState = CONFIRM_TRYING;
|
||||
MaybeConfirm();
|
||||
mCaptiveIsPassed = true;
|
||||
|
||||
} else if (!strcmp(aTopic, kClearPrivateData) ||
|
||||
!strcmp(aTopic, kPurge)) {
|
||||
// flush the TRR blacklist, both in-memory and on-disk
|
||||
|
@ -7326,7 +7326,7 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
|
||||
}
|
||||
// shift http to https disposition enums
|
||||
chanDisposition = static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
|
||||
} else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
|
||||
} else if (mLoadInfo && mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
|
||||
// HTTP content the browser would upgrade to HTTPS if upgrading was enabled
|
||||
upgradeKey = NS_LITERAL_CSTRING("disabledUpgrade");
|
||||
} else {
|
||||
|
4
other-licenses/nsis/Contrib/CityHash/CityHash.h
Normal file → Executable file
4
other-licenses/nsis/Contrib/CityHash/CityHash.h
Normal file → Executable file
@ -29,7 +29,3 @@
|
||||
#else
|
||||
#define CITYHASH_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
#ifndef ssize_t
|
||||
typedef int ssize_t;
|
||||
#endif
|
||||
|
4
other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp
Normal file → Executable file
4
other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp
Normal file → Executable file
@ -202,13 +202,13 @@ uint64 CityHash64WithSeeds(const char *s, size_t len,
|
||||
}
|
||||
|
||||
// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings
|
||||
// of any length representable in ssize_t. Based on City and Murmur.
|
||||
// of any length representable in an int. Based on City and Murmur.
|
||||
static uint128 CityMurmur(const char *s, size_t len, uint128 seed) {
|
||||
uint64 a = Uint128Low64(seed);
|
||||
uint64 b = Uint128High64(seed);
|
||||
uint64 c = 0;
|
||||
uint64 d = 0;
|
||||
ssize_t l = len - 16;
|
||||
int l = len - 16;
|
||||
if (l <= 0) { // len <= 16
|
||||
c = b * k1 + HashLen0to16(s, len);
|
||||
d = Rotate(a + (len >= 8 ? UNALIGNED_LOAD64(s) : c), 32);
|
||||
|
563
python/l10n/fluent_migrations/bug_1445694_preferences_sync.py
Normal file
563
python/l10n/fluent_migrations/bug_1445694_preferences_sync.py
Normal file
@ -0,0 +1,563 @@
|
||||
# coding=utf8
|
||||
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
from __future__ import absolute_import
|
||||
import fluent.syntax.ast as FTL
|
||||
from fluent.migrate.helpers import MESSAGE_REFERENCE, EXTERNAL_ARGUMENT
|
||||
from fluent.migrate import COPY, CONCAT, REPLACE
|
||||
|
||||
# Custom extension of the CONCAT migration tailored to concat
|
||||
# two strings separated by a placeable.
|
||||
class CONCAT_BEFORE_AFTER(CONCAT):
|
||||
def __call__(self, ctx):
|
||||
assert len(self.elements) == 3
|
||||
pattern_before, placeable, pattern_after = self.elements
|
||||
elem_before = pattern_before.elements[0]
|
||||
elem_after = pattern_after.elements[0]
|
||||
|
||||
if isinstance(elem_before, FTL.TextElement) and elem_before.value[-1] != " ":
|
||||
elem_before.value += " "
|
||||
if isinstance(elem_after, FTL.TextElement) and elem_after.value[0] != " ":
|
||||
elem_after.value = " " + elem_after.value
|
||||
return super(CONCAT_BEFORE_AFTER, self).__call__(ctx)
|
||||
|
||||
def migrate(ctx):
|
||||
"""Bug 1445694 - Migrate Preferences::Sync to Fluent, part {index}."""
|
||||
|
||||
ctx.add_transforms(
|
||||
'browser/browser/branding/sync-brand.ftl',
|
||||
'browser/browser/branding/sync-brand.ftl',
|
||||
[
|
||||
FTL.Term(
|
||||
id=FTL.Identifier('-fxaccount-brand-name'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/syncBrand.dtd',
|
||||
'syncBrand.fxAccount.label'
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
ctx.add_transforms(
|
||||
'browser/browser/preferences/preferences.ftl',
|
||||
'browser/browser/preferences/preferences.ftl',
|
||||
[
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedout-caption'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedOut.caption',
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedout-description'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedOut.description',
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedout-account-title'),
|
||||
value=REPLACE(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedOut.accountBox.title',
|
||||
{
|
||||
'&syncBrand.fxAccount.label;': MESSAGE_REFERENCE('-fxaccount-brand-name')
|
||||
},
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedout-account-create'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedOut.accountBox.create2',
|
||||
),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedOut.accountBox.create2.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedout-account-signin'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedOut.accountBox.signin2',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedOut.accountBox.signin2.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-profile-picture'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('tooltiptext'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'profilePicture.tooltip',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-disconnect'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'disconnect3.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'disconnect3.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-manage-account'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'verifiedManage.label',
|
||||
),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'verifiedManage.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedin-unverified'),
|
||||
value=CONCAT_BEFORE_AFTER(
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedInUnverified.beforename.label',
|
||||
),
|
||||
EXTERNAL_ARGUMENT('email'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedInUnverified.aftername.label',
|
||||
),
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedin-login-failure'),
|
||||
value=CONCAT_BEFORE_AFTER(
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedInLoginFailure.beforename.label',
|
||||
),
|
||||
EXTERNAL_ARGUMENT('email'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedInLoginFailure.aftername.label',
|
||||
),
|
||||
)
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-resend-verification'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'resendVerification.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'resendVerification.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-remove-account'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'removeAccount.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'removeAccount.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-sign-in'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signIn.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signIn.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedin-settings-header'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedIn.settings.label',
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-signedin-settings-desc'),
|
||||
value=REPLACE(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'signedIn.settings.description',
|
||||
{
|
||||
'&brandShortName;': MESSAGE_REFERENCE('-brand-short-name')
|
||||
},
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-bookmarks'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.bookmarks.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.bookmarks.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-history'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.history.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.history.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-tabs'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.tabs.label2',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('tooltiptext'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.tabs.title',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.tabs.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-logins'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.logins.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('tooltiptext'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.logins.title',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.logins.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-addresses'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.addresses.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('tooltiptext'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.addresses.title',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.addresses.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-creditcards'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.creditcards.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('tooltiptext'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.creditcards.title',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.creditcards.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-addons'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.addons.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('tooltiptext'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.addons.title',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.addons.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-engine-prefs'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
FTL.Pattern([
|
||||
FTL.Placeable(FTL.SelectExpression(
|
||||
expression=FTL.CallExpression(
|
||||
callee=FTL.Identifier('PLATFORM')
|
||||
),
|
||||
variants=[
|
||||
FTL.Variant(
|
||||
key=FTL.VariantName('windows'),
|
||||
default=False,
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.prefsWin.label'
|
||||
)
|
||||
),
|
||||
FTL.Variant(
|
||||
key=FTL.VariantName('other'),
|
||||
default=True,
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.prefs.label'
|
||||
)
|
||||
)
|
||||
]
|
||||
)),
|
||||
]),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('tooltiptext'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.prefs.title'
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'engine.prefs.accesskey'
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-device-name-header'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'fxaSyncDeviceName.label',
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-device-name-change'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'changeSyncDeviceName2.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'changeSyncDeviceName2.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-device-name-cancel'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'cancelChangeSyncDeviceName.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'cancelChangeSyncDeviceName.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-device-name-save'),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('label'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'saveChangeSyncDeviceName.label',
|
||||
),
|
||||
),
|
||||
FTL.Attribute(
|
||||
FTL.Identifier('accesskey'),
|
||||
COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'saveChangeSyncDeviceName.accesskey',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-mobilepromo-single'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'mobilepromo.singledevice',
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-mobilepromo-multi'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'mobilepromo.multidevice',
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-tos-link'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'prefs.tosLink.label',
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier('sync-fxa-privacy-notice'),
|
||||
value=COPY(
|
||||
'browser/chrome/browser/preferences/sync.dtd',
|
||||
'fxaPrivacyNotice.link.label',
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
@ -1016,23 +1016,6 @@ LoadLoadableRootsTask::Dispatch()
|
||||
NS_IMETHODIMP
|
||||
LoadLoadableRootsTask::Run()
|
||||
{
|
||||
// First we Run() on the "LoadRoots" thread, do our work, and then we Run()
|
||||
// again on the main thread so we can shut down the thread (since we don't
|
||||
// need it any more). We can't shut down the thread while we're *on* the
|
||||
// thread, which is why we do the dispatch to the main thread. CryptoTask.cpp
|
||||
// (which informs this code) says that we can't null out mThread. This appears
|
||||
// to be because its refcount could be decreased while this dispatch is being
|
||||
// processed, so it might get prematurely destroyed. I'm not sure this makes
|
||||
// sense but it'll get cleaned up in our destructor anyway, so it's fine to
|
||||
// not null it out here (as long as we don't run twice on the main thread,
|
||||
// which shouldn't be possible).
|
||||
if (NS_IsMainThread()) {
|
||||
if (mThread) {
|
||||
mThread->Shutdown();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv = LoadLoadableRoots();
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("LoadLoadableRoots failed"));
|
||||
@ -1060,9 +1043,7 @@ LoadLoadableRootsTask::Run()
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Go back to the main thread to clean up this worker thread.
|
||||
return NS_DispatchToMainThread(this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -144,8 +144,9 @@ SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath,
|
||||
// actually exist, if it's something that's optional or part of a
|
||||
// search path (e.g., shared libraries). In those cases, this
|
||||
// error message is expected.
|
||||
SANDBOX_LOG_ERROR("Failed errno %d op %d flags 0%o path %s",
|
||||
resp.mError, aReq->mOp, aReq->mFlags, path);
|
||||
SANDBOX_LOG_ERROR("Failed errno %d op %s flags 0%o path %s",
|
||||
resp.mError, OperationDescription[aReq->mOp],
|
||||
aReq->mFlags, path);
|
||||
}
|
||||
if (openedFd >= 0) {
|
||||
close(openedFd);
|
||||
|
@ -654,7 +654,8 @@ SandboxBroker::SymlinkPermissions(const char* aPath, const size_t aPathLen)
|
||||
// We finished the translation, so we have a usable return in "perms".
|
||||
return perms;
|
||||
} else {
|
||||
// Empty path means we got a writable dir in the chain.
|
||||
// Empty path means we got a writable dir in the chain or tried
|
||||
// to back out of a link target.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -798,27 +799,27 @@ SandboxBroker::ThreadMain(void)
|
||||
// Was it a tempdir that we can remap?
|
||||
pathLen = RemapTempDirs(pathBuf, sizeof(pathBuf), pathLen);
|
||||
perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
|
||||
if (!perms) {
|
||||
// Did we arrive from a symlink in a path that is not writable?
|
||||
// Then try to figure out the original path and see if that is
|
||||
// readable. Work on the original path, this reverses
|
||||
// ConvertRelative above.
|
||||
int symlinkPerms = SymlinkPermissions(recvBuf, first_len);
|
||||
if (symlinkPerms > 0) {
|
||||
perms = symlinkPerms;
|
||||
}
|
||||
if (!perms) {
|
||||
// Now try the opposite case: translate symlinks to their
|
||||
// actual destination file. Firefox always resolves symlinks,
|
||||
// and in most cases we have whitelisted fixed paths that
|
||||
// libraries will rely on and try to open. So this codepath
|
||||
// is mostly useful for Mesa which had its kernel interface
|
||||
// moved around.
|
||||
pathLen = RealPath(pathBuf, sizeof(pathBuf), pathLen);
|
||||
perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
|
||||
}
|
||||
}
|
||||
if (!perms) {
|
||||
// Did we arrive from a symlink in a path that is not writable?
|
||||
// Then try to figure out the original path and see if that is
|
||||
// readable. Work on the original path, this reverses
|
||||
// ConvertRelative above.
|
||||
int symlinkPerms = SymlinkPermissions(recvBuf, first_len);
|
||||
if (symlinkPerms > 0) {
|
||||
perms = symlinkPerms;
|
||||
}
|
||||
}
|
||||
if (!perms) {
|
||||
// Now try the opposite case: translate symlinks to their
|
||||
// actual destination file. Firefox always resolves symlinks,
|
||||
// and in most cases we have whitelisted fixed paths that
|
||||
// libraries will rely on and try to open. So this codepath
|
||||
// is mostly useful for Mesa which had its kernel interface
|
||||
// moved around.
|
||||
pathLen = RealPath(pathBuf, sizeof(pathBuf), pathLen);
|
||||
perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
|
||||
}
|
||||
|
||||
// Same for the second path.
|
||||
pathLen2 = strnlen(pathBuf2, kMaxPathLen);
|
||||
@ -1055,8 +1056,9 @@ SandboxBroker::AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPat
|
||||
errno = 0;
|
||||
}
|
||||
|
||||
SANDBOX_LOG_ERROR("SandboxBroker: would have denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
|
||||
" permissive=1 error=\"%s\"", aOp, aFlags, aPerms,
|
||||
SANDBOX_LOG_ERROR("SandboxBroker: would have denied op=%s rflags=%o perms=%d path=%s for pid=%d" \
|
||||
" permissive=1 error=\"%s\"", OperationDescription[aOp],
|
||||
aFlags, aPerms,
|
||||
aPath, mChildPid, strerror(errno));
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "mozilla/SandboxSettings.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "mozilla/SandboxLaunch.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsString.h"
|
||||
@ -51,6 +52,7 @@ static const int rdonly = SandboxBroker::MAY_READ;
|
||||
static const int wronly = SandboxBroker::MAY_WRITE;
|
||||
static const int rdwr = rdonly | wronly;
|
||||
static const int rdwrcr = rdwr | SandboxBroker::MAY_CREATE;
|
||||
static const int access = SandboxBroker::MAY_ACCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -528,6 +530,15 @@ SandboxBrokerPolicyFactory::GetContentPolicy(int aPid, bool aFileProcess)
|
||||
policy->AddPath(rdonly, "/var/lib/dbus/machine-id");
|
||||
}
|
||||
|
||||
// Bug 1434711 - AMDGPU-PRO crashes if it can't read it's marketing ids
|
||||
// and various other things
|
||||
if (HasAtiDrivers()) {
|
||||
policy->AddDir(rdonly, "/opt/amdgpu/share");
|
||||
policy->AddPath(rdonly, "/sys/module/amdgpu");
|
||||
// AMDGPU-PRO's MESA version likes to readlink a lot of things here
|
||||
policy->AddDir(access, "/sys");
|
||||
}
|
||||
|
||||
// Return the common policy.
|
||||
policy->FixRecursivePermissions();
|
||||
return policy;
|
||||
|
@ -119,6 +119,31 @@ IsDisplayLocal()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasAtiDrivers()
|
||||
{
|
||||
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
|
||||
nsAutoString vendorID;
|
||||
static const Array<nsresult (nsIGfxInfo::*)(nsAString&), 2> kMethods = {
|
||||
&nsIGfxInfo::GetAdapterVendorID,
|
||||
&nsIGfxInfo::GetAdapterVendorID2,
|
||||
};
|
||||
for (const auto method : kMethods) {
|
||||
if (NS_SUCCEEDED((gfxInfo->*method)(vendorID))) {
|
||||
// This test is based on telemetry data. The proprietary ATI
|
||||
// drivers seem to use this vendor string, including for some
|
||||
// newer devices that have AMD branding in the device name, such
|
||||
// as those using AMDGPU-PRO drivers.
|
||||
// The open-source drivers integrated into Mesa appear to use
|
||||
// the vendor ID "X.Org" instead.
|
||||
if (vendorID.EqualsLiteral("ATI Technologies Inc.")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Content processes may need direct access to SysV IPC in certain
|
||||
// uncommon use cases.
|
||||
static bool
|
||||
@ -138,24 +163,10 @@ ContentNeedsSysVIPC()
|
||||
}
|
||||
|
||||
// The fglrx (ATI Catalyst) GPU drivers use SysV IPC.
|
||||
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
|
||||
nsAutoString vendorID;
|
||||
static const Array<nsresult (nsIGfxInfo::*)(nsAString&), 2> kMethods = {
|
||||
&nsIGfxInfo::GetAdapterVendorID,
|
||||
&nsIGfxInfo::GetAdapterVendorID2,
|
||||
};
|
||||
for (const auto method : kMethods) {
|
||||
if (NS_SUCCEEDED((gfxInfo->*method)(vendorID))) {
|
||||
// This test is based on telemetry data. The proprietary ATI
|
||||
// drivers seem to use this vendor string, including for some
|
||||
// newer devices that have AMD branding in the device name.
|
||||
// The open-source drivers integrated into Mesa appear to use
|
||||
// the vendor ID "X.Org" instead.
|
||||
if (vendorID.EqualsLiteral("ATI Technologies Inc.")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (HasAtiDrivers()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ namespace mozilla {
|
||||
// called on the main thread in order to access prefs.
|
||||
void SandboxLaunchPrepare(GeckoProcessType aType,
|
||||
base::LaunchOptions* aOptions);
|
||||
bool HasAtiDrivers();
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -614,3 +614,18 @@ macro_rules! impl_performance_entry_struct(
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
macro_rules! handle_potential_webgl_error {
|
||||
($context:expr, $call:expr, $return_on_error:expr) => {
|
||||
match $call {
|
||||
Ok(ret) => ret,
|
||||
Err(error) => {
|
||||
$context.webgl_error(error);
|
||||
$return_on_error
|
||||
}
|
||||
}
|
||||
};
|
||||
($context:expr, $call:expr) => {
|
||||
handle_potential_webgl_error!($context, $call, ());
|
||||
};
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user