mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 05:45:37 +00:00
Merge mozilla-central to inbound. a=merge
This commit is contained in:
commit
8cd0e59ac5
@ -1230,26 +1230,34 @@ BrowserPageActions.shareURL = {
|
||||
let shareProviders = sharingService.getSharingProviders(currentURI);
|
||||
let fragment = document.createDocumentFragment();
|
||||
|
||||
let onCommand = event => {
|
||||
let shareName = event.target.getAttribute("share-name");
|
||||
if (shareName) {
|
||||
sharingService.shareUrl(shareName,
|
||||
currentURI,
|
||||
gBrowser.selectedBrowser.contentTitle);
|
||||
} else if (event.target.classList.contains("share-more-button")) {
|
||||
sharingService.openSharingPreferences();
|
||||
}
|
||||
PanelMultiView.hidePopup(BrowserPageActions.panelNode);
|
||||
};
|
||||
|
||||
shareProviders.forEach(function(share) {
|
||||
let item = document.createElement("toolbarbutton");
|
||||
item.setAttribute("label", share.menuItemTitle);
|
||||
item.setAttribute("share-name", share.name);
|
||||
item.setAttribute("image", share.image);
|
||||
item.classList.add("subviewbutton", "subviewbutton-iconic");
|
||||
|
||||
item.addEventListener("command", event => {
|
||||
let shareName = event.target.getAttribute("share-name");
|
||||
if (shareName) {
|
||||
sharingService.shareUrl(shareName,
|
||||
currentURI,
|
||||
gBrowser.selectedBrowser.contentTitle);
|
||||
}
|
||||
PanelMultiView.hidePopup(BrowserPageActions.panelNode);
|
||||
});
|
||||
|
||||
item.addEventListener("command", onCommand);
|
||||
fragment.appendChild(item);
|
||||
});
|
||||
|
||||
let item = document.createElement("toolbarbutton");
|
||||
item.setAttribute("label", BrowserPageActions.panelNode.getAttribute("shareMore-label"));
|
||||
item.classList.add("subviewbutton", "subviewbutton-iconic", "share-more-button");
|
||||
item.addEventListener("command", onCommand);
|
||||
fragment.appendChild(item);
|
||||
|
||||
while (bodyNode.firstChild) {
|
||||
bodyNode.firstChild.remove();
|
||||
}
|
||||
|
@ -474,7 +474,8 @@
|
||||
emailLink-title="&emailPageCmd.label;"
|
||||
sendToDevice-title="&pageAction.sendTabToDevice.label;"
|
||||
sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;"
|
||||
shareURL-title="&pageAction.shareUrl.label;">
|
||||
shareURL-title="&pageAction.shareUrl.label;"
|
||||
shareMore-label="&pageAction.shareMore.label;">
|
||||
<panelmultiview id="pageActionPanelMultiView"
|
||||
mainViewId="pageActionPanelMainView"
|
||||
viewCacheId="appMenu-viewCache">
|
||||
|
@ -2642,6 +2642,13 @@ window._gBrowser = {
|
||||
}
|
||||
|
||||
var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
|
||||
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
// We have to sample the tab width now, since _beginRemoveTab might
|
||||
// end up modifying the DOM in such a way that aTab gets a new
|
||||
// frame created for it (for example, by updating the visually selected
|
||||
// state).
|
||||
let tabWidth = windowUtils.getBoundsWithoutFlushing(aTab).width;
|
||||
|
||||
if (!this._beginRemoveTab(aTab, null, null, true, skipPermitUnload)) {
|
||||
TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_ANIM_MS", aTab);
|
||||
@ -2650,7 +2657,7 @@ window._gBrowser = {
|
||||
}
|
||||
|
||||
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
|
||||
this.tabContainer._lockTabSizing(aTab);
|
||||
this.tabContainer._lockTabSizing(aTab, tabWidth);
|
||||
else
|
||||
this.tabContainer._unlockTabSizing();
|
||||
|
||||
|
@ -400,6 +400,7 @@
|
||||
<!-- Try to keep the active tab's close button under the mouse cursor -->
|
||||
<method name="_lockTabSizing">
|
||||
<parameter name="aTab"/>
|
||||
<parameter name="aTabWidth"/>
|
||||
<body><![CDATA[
|
||||
let tabs = this._getVisibleTabs();
|
||||
if (!tabs.length) {
|
||||
@ -407,7 +408,6 @@
|
||||
}
|
||||
|
||||
var isEndTab = (aTab._tPos > tabs[tabs.length - 1]._tPos);
|
||||
var tabWidth = aTab.getBoundingClientRect().width;
|
||||
|
||||
if (!this._tabDefaultMaxWidth) {
|
||||
this._tabDefaultMaxWidth =
|
||||
@ -427,7 +427,7 @@
|
||||
if (aTab.owner) {
|
||||
return;
|
||||
}
|
||||
this._expandSpacerBy(tabWidth);
|
||||
this._expandSpacerBy(aTabWidth);
|
||||
} else { // non-overflow mode
|
||||
// Locking is neither in effect nor needed, so let tabs expand normally.
|
||||
if (isEndTab && !this._hasTabTempMaxWidth) {
|
||||
@ -439,18 +439,18 @@
|
||||
// tabbar width is the same.
|
||||
if (isEndTab) {
|
||||
let numNormalTabs = tabs.length - numPinned;
|
||||
tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
|
||||
if (tabWidth > this._tabDefaultMaxWidth) {
|
||||
tabWidth = this._tabDefaultMaxWidth;
|
||||
aTabWidth = aTabWidth * (numNormalTabs + 1) / numNormalTabs;
|
||||
if (aTabWidth > this._tabDefaultMaxWidth) {
|
||||
aTabWidth = this._tabDefaultMaxWidth;
|
||||
}
|
||||
}
|
||||
tabWidth += "px";
|
||||
aTabWidth += "px";
|
||||
for (let i = numPinned; i < tabs.length; i++) {
|
||||
let tab = tabs[i];
|
||||
tab.style.setProperty("max-width", tabWidth, "important");
|
||||
tab.style.setProperty("max-width", aTabWidth, "important");
|
||||
if (!isEndTab) { // keep tabs the same width
|
||||
tab.style.transition = "none";
|
||||
tab.clientTop; // flush styles to skip animation; see bug 649247
|
||||
window.getComputedStyle(tab); // flush styles to skip animation; see bug 649247
|
||||
tab.style.transition = "";
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ add_task(async function() {
|
||||
await withPerfObserver(async function() {
|
||||
let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
|
||||
let tab = gBrowser.tabs[gBrowser.tabs.length - 1];
|
||||
gBrowser.removeTab(tab, { animate: true });
|
||||
gBrowser.removeTab(tab, { animate: true, byMouse: true });
|
||||
await BrowserTestUtils.waitForEvent(tab, "transitionend",
|
||||
false, e => e.propertyName === "max-width");
|
||||
await switchDone;
|
||||
|
@ -10,9 +10,8 @@ Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
|
||||
const URL = "http://example.org/";
|
||||
|
||||
// Keep track of title of service we chose to share with
|
||||
let serviceName;
|
||||
let sharedUrl;
|
||||
let sharedTitle;
|
||||
let serviceName, sharedUrl, sharedTitle;
|
||||
let sharingPreferencesCalled = false;
|
||||
|
||||
let mockShareData = [{
|
||||
name: "NSA",
|
||||
@ -30,6 +29,9 @@ let stub = sinon.stub(BrowserPageActions.shareURL, "_sharingService").get(() =>
|
||||
serviceName = name;
|
||||
sharedUrl = url;
|
||||
sharedTitle = title;
|
||||
},
|
||||
openSharingPreferences() {
|
||||
sharingPreferencesCalled = true;
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -54,7 +56,8 @@ add_task(async function shareURL() {
|
||||
let view = await viewPromise;
|
||||
let body = document.getElementById(view.id + "-body");
|
||||
|
||||
Assert.equal(body.childNodes.length, 1, "Has correct share receivers");
|
||||
// We should see 1 receiver and one extra node for the "More..." button
|
||||
Assert.equal(body.childNodes.length, 2, "Has correct share receivers");
|
||||
let shareButton = body.childNodes[0];
|
||||
Assert.equal(shareButton.label, mockShareData[0].menuItemTitle);
|
||||
let hiddenPromise = promisePageActionPanelHidden();
|
||||
@ -107,7 +110,8 @@ add_task(async function shareURLAddressBar() {
|
||||
|
||||
// Ensure we have share providers
|
||||
let panel = document.getElementById("pageAction-urlbar-shareURL-subview-body");
|
||||
Assert.equal(panel.childNodes.length, 1, "Has correct share receivers");
|
||||
// We should see 1 receiver and one extra node for the "More..." button
|
||||
Assert.equal(panel.childNodes.length, 2, "Has correct share receivers");
|
||||
|
||||
// Remove the Share URL button from the Address bar so we dont interfere
|
||||
// with future tests
|
||||
@ -125,3 +129,30 @@ add_task(async function shareURLAddressBar() {
|
||||
await contextMenuPromise;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function openSharingPreferences() {
|
||||
await BrowserTestUtils.withNewTab(URL, async () => {
|
||||
// Open the panel.
|
||||
await promisePageActionPanelOpen();
|
||||
|
||||
// Click Share URL.
|
||||
let shareURLButton = document.getElementById("pageAction-panel-shareURL");
|
||||
let viewPromise = promisePageActionViewShown();
|
||||
EventUtils.synthesizeMouseAtCenter(shareURLButton, {});
|
||||
|
||||
let view = await viewPromise;
|
||||
let body = document.getElementById(view.id + "-body");
|
||||
|
||||
// We should see 1 receiver and one extra node for the "More..." button
|
||||
Assert.equal(body.childNodes.length, 2, "Has correct share receivers");
|
||||
let moreButton = body.childNodes[1];
|
||||
let hiddenPromise = promisePageActionPanelHidden();
|
||||
// Click on the "more" button, panel should hide and we should call
|
||||
// the sharingService function to open preferences
|
||||
EventUtils.synthesizeMouseAtCenter(moreButton, {});
|
||||
await hiddenPromise;
|
||||
|
||||
Assert.equal(sharingPreferencesCalled, true,
|
||||
"We called openSharingPreferences");
|
||||
});
|
||||
});
|
||||
|
@ -77,7 +77,8 @@ Preferences.addAll([
|
||||
{ id: "privacy.donottrackheader.enabled", type: "bool" },
|
||||
|
||||
// Media
|
||||
{ id: "media.autoplay.enabled", type: "bool" },
|
||||
{ id: "media.autoplay.default", type: "int" },
|
||||
{ id: "media.autoplay.enabled.ask-permission", type: "bool" },
|
||||
{ id: "media.autoplay.enabled.user-gestures-needed", type: "bool" },
|
||||
|
||||
// Popups
|
||||
@ -258,7 +259,6 @@ var gPrivacyPane = {
|
||||
|
||||
this._updateSanitizeSettingsButton();
|
||||
this.initializeHistoryMode();
|
||||
this.updateAutoplayMediaControls();
|
||||
this.updateAutoplayMediaControlsVisibility();
|
||||
this.updateHistoryModePane();
|
||||
this.updatePrivacyMicroControls();
|
||||
@ -276,8 +276,8 @@ var gPrivacyPane = {
|
||||
gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
|
||||
Preferences.get("privacy.trackingprotection.pbmode.enabled").on("change",
|
||||
gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
|
||||
Preferences.get("media.autoplay.enabled").on("change",
|
||||
gPrivacyPane.updateAutoplayMediaControls.bind(gPrivacyPane));
|
||||
Preferences.get("media.autoplay.enabled.ask-permission").on("change",
|
||||
gPrivacyPane.updateAutoplayMediaControlsVisibility.bind(gPrivacyPane));
|
||||
Preferences.get("media.autoplay.enabled.user-gestures-needed").on("change",
|
||||
gPrivacyPane.updateAutoplayMediaControlsVisibility.bind(gPrivacyPane));
|
||||
setEventListener("historyMode", "command", function() {
|
||||
@ -342,10 +342,12 @@ var gPrivacyPane = {
|
||||
gPrivacyPane.showMicrophoneExceptions);
|
||||
setEventListener("popupPolicyButton", "command",
|
||||
gPrivacyPane.showPopupExceptions);
|
||||
setEventListener("autoplayMediaPolicy", "command",
|
||||
setEventListener("autoplayMediaCheckbox", "command",
|
||||
gPrivacyPane.toggleAutoplayMedia);
|
||||
setEventListener("autoplayMediaPolicyButton", "command",
|
||||
gPrivacyPane.showAutoplayMediaExceptions);
|
||||
setEventListener("autoplayMediaPolicyComboboxButton", "command",
|
||||
gPrivacyPane.showAutoplayMediaExceptions);
|
||||
setEventListener("notificationsDoNotDisturb", "command",
|
||||
gPrivacyPane.toggleDoNotDisturbNotifications);
|
||||
|
||||
@ -988,25 +990,34 @@ var gPrivacyPane = {
|
||||
// MEDIA
|
||||
|
||||
/**
|
||||
* media.autoplay.enabled works the opposite to most of the other preferences.
|
||||
* The checkbox enabled sets the pref to false
|
||||
* The checkbox enabled sets the pref to BLOCKED
|
||||
*/
|
||||
toggleAutoplayMedia(event) {
|
||||
Services.prefs.setBoolPref("media.autoplay.enabled", !event.target.checked);
|
||||
},
|
||||
|
||||
updateAutoplayMediaControls() {
|
||||
let autoPlayEnabled = Preferences.get("media.autoplay.enabled").value;
|
||||
document.getElementById("autoplayMediaPolicy").checked = !autoPlayEnabled;
|
||||
document.getElementById("autoplayMediaPolicyButton").disabled = autoPlayEnabled;
|
||||
let blocked = event.target.checked ? Ci.nsIAutoplay.BLOCKED : Ci.nsIAutoplay.ALLOWED;
|
||||
Services.prefs.setIntPref("media.autoplay.default", blocked);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the controls for the new media autoplay behaviour behind a pref for now
|
||||
* If user-gestures-needed is false we do not show any UI for configuring autoplay,
|
||||
* if user-gestures-needed is false and ask-permission is false we show a checkbox
|
||||
* which only allows the user to block autoplay
|
||||
* if user-gestures-needed and ask-permission are true we show a combobox that
|
||||
* allows the user to block / allow or prompt for autoplay
|
||||
* We will be performing a shield study to determine the behaviour to be
|
||||
* shipped, at which point we can remove these pref switches.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1475099
|
||||
*/
|
||||
updateAutoplayMediaControlsVisibility() {
|
||||
document.getElementById("autoplayMediaBox").hidden =
|
||||
!Services.prefs.getBoolPref("media.autoplay.enabled.user-gestures-needed", false);
|
||||
let askPermission =
|
||||
Services.prefs.getBoolPref("media.autoplay.ask-permission", false);
|
||||
let userGestures =
|
||||
Services.prefs.getBoolPref("media.autoplay.enabled.user-gestures-needed", false);
|
||||
// Hide the combobox if we don't let the user ask for permission.
|
||||
document.getElementById("autoplayMediaComboboxWrapper").hidden =
|
||||
!userGestures || !askPermission;
|
||||
// If the user may ask for permission, hide the checkbox instead.
|
||||
document.getElementById("autoplayMediaCheckboxWrapper").hidden =
|
||||
!userGestures || askPermission;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1015,7 +1026,7 @@ var gPrivacyPane = {
|
||||
*/
|
||||
showAutoplayMediaExceptions() {
|
||||
var params = {
|
||||
blockVisible: false, sessionVisible: false, allowVisible: true,
|
||||
blockVisible: true, sessionVisible: false, allowVisible: true,
|
||||
prefilledHost: "", permissionType: "autoplay-media"
|
||||
};
|
||||
|
||||
|
@ -496,8 +496,8 @@
|
||||
|
||||
<separator flex="1"/>
|
||||
|
||||
<hbox align="start" id="autoplayMediaBox" hidden="true">
|
||||
<checkbox id="autoplayMediaPolicy"
|
||||
<hbox align="start" id="autoplayMediaCheckboxWrapper" hidden="true">
|
||||
<checkbox id="autoplayMediaCheckbox"
|
||||
data-l10n-id="permissions-block-autoplay-media"
|
||||
flex="1" />
|
||||
<!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
|
||||
@ -568,6 +568,37 @@
|
||||
data-l10n-id="permissions-a11y-privacy-link"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<hbox align="center" id="autoplayMediaComboboxWrapper" hidden="true">
|
||||
<hbox align="center" flex="1">
|
||||
<label id="autoplayMediaPolicy"
|
||||
control="autoplayMediaPolicyMenu"
|
||||
data-l10n-id="permissions-block-autoplay-media-menu"/>
|
||||
<menulist id="autoplayMediaPolicyMenu"
|
||||
sizetopopup="always"
|
||||
preference="media.autoplay.default">
|
||||
<menupopup>
|
||||
<!-- Defined in dom/media/nsIAutoplay.idl -->
|
||||
<menuitem data-l10n-id="autoplay-option-allow" value="0"/>
|
||||
<menuitem data-l10n-id="autoplay-option-ask" value="2"/>
|
||||
<menuitem data-l10n-id="autoplay-option-block" value="1"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<hbox pack="end">
|
||||
<button id="autoplayMediaPolicyComboboxButton"
|
||||
class="accessory-button"
|
||||
data-l10n-id="permissions-block-autoplay-media-exceptions"
|
||||
search-l10n-ids="permissions-address,
|
||||
permissions-button-cancel.label,
|
||||
permissions-button-ok.label,
|
||||
permissions-exceptions-autoplay-media-window.title,
|
||||
permissions-exceptions-autoplay-media-desc
|
||||
" />
|
||||
</hbox>
|
||||
</hbox>
|
||||
|
||||
</groupbox>
|
||||
|
||||
<!-- Firefox Data Collection and Use -->
|
||||
|
@ -80,6 +80,7 @@ run-if = nightly_build
|
||||
[browser_spotlight.js]
|
||||
[browser_site_login_exceptions.js]
|
||||
[browser_site_autoplay_media_exceptions.js]
|
||||
[browser_site_autoplay_media_prompt.js]
|
||||
[browser_permissions_dialog.js]
|
||||
[browser_subdialogs.js]
|
||||
support-files =
|
||||
|
@ -7,13 +7,15 @@ const PRINCIPAL = Services.scriptSecurityManager
|
||||
.createCodebasePrincipal(Services.io.newURI(URL), {});
|
||||
|
||||
const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
|
||||
const AUTOPLAY_ENABLED_KEY = "media.autoplay.enabled";
|
||||
const AUTOPLAY_ENABLED_KEY = "media.autoplay.default";
|
||||
const GESTURES_NEEDED_KEY = "media.autoplay.enabled.user-gestures-needed";
|
||||
const ASK_PERMISSIONS_KEY = "media.autoplay.enabled.ask-permissions";
|
||||
|
||||
var exceptionsDialog;
|
||||
|
||||
Services.prefs.setBoolPref(AUTOPLAY_ENABLED_KEY, true);
|
||||
Services.prefs.setIntPref(AUTOPLAY_ENABLED_KEY, Ci.nsIAutoplay.ALLOWED);
|
||||
Services.prefs.setBoolPref(GESTURES_NEEDED_KEY, false);
|
||||
Services.prefs.setBoolPref(ASK_PERMISSIONS_KEY, true);
|
||||
|
||||
async function openExceptionsDialog() {
|
||||
let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
|
||||
@ -29,6 +31,7 @@ add_task(async function ensureCheckboxHidden() {
|
||||
registerCleanupFunction(async function() {
|
||||
Services.prefs.clearUserPref(AUTOPLAY_ENABLED_KEY);
|
||||
Services.prefs.clearUserPref(GESTURES_NEEDED_KEY);
|
||||
Services.prefs.clearUserPref(ASK_PERMISSIONS_KEY);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
@ -44,11 +47,12 @@ add_task(async function enableBlockingAutoplay() {
|
||||
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
let doc = content.document;
|
||||
let autoplayCheckBox = doc.getElementById("autoplayMediaPolicy");
|
||||
let autoplayCheckBox = doc.getElementById("autoplayMediaCheckbox");
|
||||
autoplayCheckBox.click();
|
||||
});
|
||||
|
||||
Assert.equal(Services.prefs.getBoolPref(AUTOPLAY_ENABLED_KEY), false,
|
||||
Assert.equal(Services.prefs.getIntPref(AUTOPLAY_ENABLED_KEY),
|
||||
Ci.nsIAutoplay.BLOCKED,
|
||||
"Ensure we have set autoplay to false");
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,114 @@
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource:///modules/SitePermissions.jsm");
|
||||
|
||||
const URL = "http://www.example.com";
|
||||
const PRINCIPAL = Services.scriptSecurityManager
|
||||
.createCodebasePrincipal(Services.io.newURI(URL), {});
|
||||
|
||||
const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
|
||||
const AUTOPLAY_ENABLED_KEY = "media.autoplay.default";
|
||||
const GESTURES_NEEDED_KEY = "media.autoplay.enabled.user-gestures-needed";
|
||||
const ASK_PERMISSIONS_KEY = "media.autoplay.enabled.ask-permissions";
|
||||
|
||||
var exceptionsDialog;
|
||||
|
||||
Services.prefs.setIntPref(AUTOPLAY_ENABLED_KEY, Ci.nsIAutoplay.ALLOWED);
|
||||
Services.prefs.setBoolPref(GESTURES_NEEDED_KEY, false);
|
||||
Services.prefs.setBoolPref(ASK_PERMISSIONS_KEY, true);
|
||||
|
||||
async function openExceptionsDialog() {
|
||||
let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
let exceptionsButton = content.document.getElementById("autoplayMediaPolicyButton");
|
||||
exceptionsButton.click();
|
||||
});
|
||||
exceptionsDialog = await dialogOpened;
|
||||
}
|
||||
|
||||
add_task(async function ensureMenuHidden() {
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
Services.prefs.clearUserPref(AUTOPLAY_ENABLED_KEY);
|
||||
Services.prefs.clearUserPref(GESTURES_NEEDED_KEY);
|
||||
Services.prefs.clearUserPref(ASK_PERMISSIONS_KEY);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
|
||||
let win = gBrowser.selectedBrowser.contentWindow;
|
||||
is_element_hidden(win.document.getElementById("autoplayMediaPolicy"),
|
||||
"Ensure checkbox is hidden when preffed off");
|
||||
});
|
||||
|
||||
add_task(async function enableBlockingAutoplay() {
|
||||
|
||||
Services.prefs.setBoolPref(GESTURES_NEEDED_KEY, true);
|
||||
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
let doc = content.document;
|
||||
let autoplayMenu = doc.getElementById("autoplayMediaPolicyMenu");
|
||||
autoplayMenu.click();
|
||||
let askMenuItem = autoplayMenu.childNodes[0].childNodes[1];
|
||||
askMenuItem.click();
|
||||
});
|
||||
|
||||
Assert.equal(Services.prefs.getIntPref(AUTOPLAY_ENABLED_KEY),
|
||||
Ci.nsIAutoplay.PROMPT,
|
||||
"Ensure we have set autoplay to false");
|
||||
});
|
||||
|
||||
add_task(async function addException() {
|
||||
await openExceptionsDialog();
|
||||
let doc = exceptionsDialog.document;
|
||||
|
||||
let richlistbox = doc.getElementById("permissionsBox");
|
||||
Assert.equal(richlistbox.itemCount, 0, "Row count should initially be 0");
|
||||
|
||||
let inputBox = doc.getElementById("url");
|
||||
inputBox.focus();
|
||||
|
||||
EventUtils.sendString(URL, exceptionsDialog);
|
||||
|
||||
let btnAllow = doc.getElementById("btnAllow");
|
||||
btnAllow.click();
|
||||
|
||||
await TestUtils.waitForCondition(() => richlistbox.itemCount == 1);
|
||||
Assert.equal(richlistbox.getItemAtIndex(0).getAttribute("origin"), URL);
|
||||
|
||||
let permChanged = TestUtils.topicObserved("perm-changed");
|
||||
let btnApplyChanges = doc.getElementById("btnApplyChanges");
|
||||
btnApplyChanges.click();
|
||||
await permChanged;
|
||||
|
||||
is(Services.perms.testPermissionFromPrincipal(PRINCIPAL, "autoplay-media"),
|
||||
Ci.nsIPermissionManager.ALLOW_ACTION, "Correctly added the exception");
|
||||
});
|
||||
|
||||
add_task(async function deleteException() {
|
||||
await openExceptionsDialog();
|
||||
let doc = exceptionsDialog.document;
|
||||
|
||||
let richlistbox = doc.getElementById("permissionsBox");
|
||||
Assert.equal(richlistbox.itemCount, 1, "Row count should initially be 1");
|
||||
richlistbox.focus();
|
||||
richlistbox.selectedIndex = 0;
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
EventUtils.synthesizeKey("KEY_Backspace");
|
||||
} else {
|
||||
EventUtils.synthesizeKey("KEY_Delete");
|
||||
}
|
||||
|
||||
await TestUtils.waitForCondition(() => richlistbox.itemCount == 0);
|
||||
is_element_visible(content.gSubDialog._dialogs[0]._box,
|
||||
"Subdialog is visible after deleting an element");
|
||||
|
||||
let permChanged = TestUtils.topicObserved("perm-changed");
|
||||
let btnApplyChanges = doc.getElementById("btnApplyChanges");
|
||||
btnApplyChanges.click();
|
||||
await permChanged;
|
||||
|
||||
is(Services.perms.testPermissionFromPrincipal(PRINCIPAL, "autoplay-media"),
|
||||
Ci.nsIPermissionManager.UNKNOWN_ACTION, "Correctly removed the exception");
|
||||
});
|
@ -367,7 +367,6 @@ var SessionFileInternal = {
|
||||
this._initializationStarted = false;
|
||||
// Reset the counter and report to telemetry.
|
||||
this._workerHealth.failures = 0;
|
||||
Telemetry.scalarAdd("browser.session.restore.worker_restart_count", 1);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* The primary purpose of this test is to ensure that the sessionstore component
|
||||
* records information about erroneous workers into a scalar.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
const Telemetry = Services.telemetry;
|
||||
const ScalarId = "browser.session.restore.worker_restart_count";
|
||||
|
||||
// Prepare the session file.
|
||||
var profd = do_get_profile();
|
||||
ChromeUtils.import("resource:///modules/sessionstore/SessionFile.jsm", this);
|
||||
|
||||
/**
|
||||
* In order to use browser.session.restore.worker_restart_count scalar, it has
|
||||
* to be registered in "toolkit/components/telemetry/Scalars.yaml".
|
||||
* This test ensures that the scalar is registered and empty.
|
||||
*/
|
||||
add_task(async function test_ensure_scalar_is_empty() {
|
||||
const scalars = Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT, false).parent || {};
|
||||
Assert.ok(!(ScalarId in scalars), "Sanity check; no scalars should be there yet.");
|
||||
});
|
||||
|
||||
/**
|
||||
* Makes sure that the scalar is positively updated when amount of failures
|
||||
* becomes higher than the threshold.
|
||||
*/
|
||||
add_task(async function test_worker_restart() {
|
||||
let backstagePass = ChromeUtils.import("resource:///modules/sessionstore/SessionFile.jsm", {});
|
||||
backstagePass.SessionFileInternal._workerHealth.failures = backstagePass.kMaxWriteFailures + 1;
|
||||
backstagePass.SessionFileInternal._checkWorkerHealth();
|
||||
|
||||
Assert.equal(backstagePass.SessionFileInternal._workerHealth.failures, 0,
|
||||
"Worker failure count should've been reset.");
|
||||
|
||||
// Checking if the scalar is positively updated.
|
||||
const scalars = Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT, false).parent;
|
||||
Assert.equal(scalars[ScalarId], 1, "Should be increased with one hit.");
|
||||
});
|
@ -10,7 +10,6 @@ support-files =
|
||||
[test_backup_once.js]
|
||||
[test_histogram_corrupt_files.js]
|
||||
[test_migration_lz4compression.js]
|
||||
[test_scalar_worker_restarts.js]
|
||||
[test_shutdown_cleanup.js]
|
||||
[test_startup_nosession_async.js]
|
||||
[test_startup_session_async.js]
|
||||
|
@ -47,6 +47,7 @@
|
||||
[@AB_CD@]
|
||||
@RESPATH@/dictionaries/*
|
||||
@RESPATH@/browser/localization/*
|
||||
@RESPATH@/localization/*
|
||||
#if defined(XP_WIN) || defined(XP_LINUX)
|
||||
@RESPATH@/fonts/*
|
||||
#endif
|
||||
|
@ -849,10 +849,19 @@ permissions-block-autoplay-media =
|
||||
.label = Block websites from automatically playing media with sound
|
||||
.accesskey = B
|
||||
|
||||
permissions-block-autoplay-media-menu = For websites that autoplay sound
|
||||
|
||||
permissions-block-autoplay-media-exceptions =
|
||||
.label = Exceptions…
|
||||
.accesskey = E
|
||||
|
||||
autoplay-option-ask =
|
||||
.label = Always Ask
|
||||
autoplay-option-allow =
|
||||
.label = Allow Autoplay
|
||||
autoplay-option-block =
|
||||
.label = Block Autoplay
|
||||
|
||||
permissions-block-popups =
|
||||
.label = Block pop-up windows
|
||||
.accesskey = B
|
||||
|
@ -1031,6 +1031,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||
<!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
|
||||
|
||||
<!ENTITY pageAction.shareUrl.label "Share">
|
||||
<!ENTITY pageAction.shareMore.label "More…">
|
||||
|
||||
<!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
|
||||
|
||||
|
@ -608,8 +608,12 @@ var gPermissionObject = {
|
||||
"autoplay-media": {
|
||||
exactHostMatch: true,
|
||||
getDefault() {
|
||||
if (Services.prefs.getBoolPref("media.autoplay.enabled")) {
|
||||
let state = Services.prefs.getIntPref("media.autoplay.default",
|
||||
Ci.nsIAutoplay.PROMPT);
|
||||
if (state == Ci.nsIAutoplay.ALLOW) {
|
||||
return SitePermissions.ALLOW;
|
||||
} if (state == Ci.nsIAutoplay.BLOCK) {
|
||||
return SitePermissions.DENY;
|
||||
}
|
||||
return SitePermissions.UNKNOWN;
|
||||
},
|
||||
|
@ -32,9 +32,9 @@ add_task(async function test_midi_permission_prompt() {
|
||||
|
||||
// Tests that AutoplayPermissionPrompt works as expected
|
||||
add_task(async function test_autoplay_permission_prompt() {
|
||||
Services.prefs.setBoolPref("media.autoplay.enabled", false);
|
||||
Services.prefs.setIntPref("media.autoplay.default", Ci.nsIAutoplay.PROMPT);
|
||||
await testPrompt(PermissionUI.AutoplayPermissionPrompt);
|
||||
Services.prefs.clearUserPref("media.autoplay.enabled");
|
||||
Services.prefs.clearUserPref("media.autoplay.default");
|
||||
});
|
||||
|
||||
async function testPrompt(Prompt) {
|
||||
|
@ -298,7 +298,7 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#pageActionButton {
|
||||
#pageActionButton, .share-more-button {
|
||||
list-style-image: url("chrome://browser/skin/page-action.svg");
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ skip-if = !e10s # too slow on !e10s, logging fully serialized actors (Bug 144659
|
||||
subsuite = clipboard
|
||||
[browser_fontinspector_edit-previews.js]
|
||||
[browser_fontinspector_editor-font-size-conversion.js]
|
||||
skip-if = true # Bug 1476535
|
||||
[browser_fontinspector_editor-values.js]
|
||||
[browser_fontinspector_editor-keywords.js]
|
||||
[browser_fontinspector_expand-css-code.js]
|
||||
|
@ -102,7 +102,6 @@ support-files =
|
||||
test-insecure-passwords-web-console-warning.html
|
||||
test-inspect-cross-domain-objects-frame.html
|
||||
test-inspect-cross-domain-objects-top.html
|
||||
test-jsterm-dollar.html
|
||||
test_jsterm_screenshot_command.html
|
||||
test-local-session-storage.html
|
||||
test-location-debugger-link-console-log.js
|
||||
@ -200,12 +199,12 @@ skip-if = verify
|
||||
[browser_jsterm_autocomplete_return_key.js]
|
||||
[browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
|
||||
[browser_jsterm_completion.js]
|
||||
[browser_jsterm_content_defined_helpers.js]
|
||||
[browser_jsterm_copy_command.js]
|
||||
[browser_jsterm_ctrl_a_select_all.js]
|
||||
[browser_jsterm_ctrl_key_nav.js]
|
||||
skip-if = os != 'mac' # The tested ctrl+key shortcuts are OSX only
|
||||
[browser_jsterm_document_no_xray.js]
|
||||
[browser_jsterm_dollar.js]
|
||||
[browser_jsterm_error_docs.js]
|
||||
[browser_jsterm_error_outside_valid_range.js]
|
||||
[browser_jsterm_helper_clear.js]
|
||||
|
@ -0,0 +1,63 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that using helper functions in jsterm call the global content functions
|
||||
// if they are defined.
|
||||
|
||||
const PREFIX = "content-";
|
||||
const HELPERS = [
|
||||
"$_",
|
||||
"$",
|
||||
"$$",
|
||||
"$0",
|
||||
"$x",
|
||||
"cd",
|
||||
"clear",
|
||||
"clearHistory",
|
||||
"copy",
|
||||
"help",
|
||||
"inspect",
|
||||
"keys",
|
||||
"pprint",
|
||||
"screenshot",
|
||||
"values",
|
||||
];
|
||||
|
||||
// The page script sets a global function for each known helper (except print).
|
||||
const TEST_URI = `data:text/html,<meta charset=utf8>
|
||||
<script>
|
||||
const helpers = ${JSON.stringify(HELPERS)};
|
||||
for (const helper of helpers) {
|
||||
window[helper] = () => "${PREFIX}" + helper;
|
||||
}
|
||||
</script>`;
|
||||
|
||||
add_task(async function() {
|
||||
// Run test with legacy JsTerm
|
||||
await performTests();
|
||||
// And then run it with the CodeMirror-powered one.
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
|
||||
await performTests();
|
||||
});
|
||||
|
||||
async function performTests() {
|
||||
const {jsterm} = await openNewTabAndConsole(TEST_URI);
|
||||
const {autocompletePopup} = jsterm;
|
||||
|
||||
for (const helper of HELPERS) {
|
||||
await jstermSetValueAndComplete(jsterm, helper);
|
||||
const autocompleteItems = getPopupLabels(autocompletePopup).filter(l => l === helper);
|
||||
is(autocompleteItems.length, 1,
|
||||
`There's no duplicated "${helper}" item in the autocomplete popup`);
|
||||
const msg = await jsterm.execute(`${helper}()`);
|
||||
ok(msg.textContent.includes(PREFIX + helper), `output is correct for ${helper}()`);
|
||||
}
|
||||
}
|
||||
|
||||
function getPopupLabels(popup) {
|
||||
return popup.getItems().map(item => item.label);
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that using `$` and `$$` in jsterm call the global content functions
|
||||
// if they are defined. See Bug 621644.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
||||
"test/mochitest/test-jsterm-dollar.html";
|
||||
|
||||
add_task(async function() {
|
||||
// Run test with legacy JsTerm
|
||||
await performTests();
|
||||
// And then run it with the CodeMirror-powered one.
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
|
||||
await performTests();
|
||||
});
|
||||
|
||||
async function performTests() {
|
||||
const hud = await openNewTabAndConsole(TEST_URI);
|
||||
await test$(hud);
|
||||
await test$$(hud);
|
||||
}
|
||||
|
||||
async function test$(hud) {
|
||||
hud.ui.clearOutput();
|
||||
const msg = await hud.jsterm.execute("$(document.body)");
|
||||
ok(msg.textContent.includes("<p>"), "jsterm output is correct for $()");
|
||||
}
|
||||
|
||||
async function test$$(hud) {
|
||||
hud.ui.clearOutput();
|
||||
hud.jsterm.setInputValue();
|
||||
const msg = await hud.jsterm.execute("$$(document)");
|
||||
ok(msg.textContent.includes("621644"), "jsterm output is correct for $$()");
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr" xml:lang="en-US" lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Web Console test for bug 621644</title>
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
function $(elem) {
|
||||
return elem.innerHTML;
|
||||
}
|
||||
function $$(doc) {
|
||||
return doc.title;
|
||||
}
|
||||
</script>
|
||||
<!--
|
||||
- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
<h1>Web Console test for bug 621644</h1>
|
||||
<p>hello world!</p>
|
||||
</body>
|
||||
</html>
|
@ -27,6 +27,7 @@ loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsol
|
||||
loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
|
||||
loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true);
|
||||
loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true);
|
||||
loader.lazyRequireGetter(this, "WebConsoleCommands", "devtools/server/actors/webconsole/utils", true);
|
||||
loader.lazyRequireGetter(this, "addWebConsoleCommands", "devtools/server/actors/webconsole/utils", true);
|
||||
loader.lazyRequireGetter(this, "formatCommand", "devtools/server/actors/webconsole/commands", true);
|
||||
loader.lazyRequireGetter(this, "isCommand", "devtools/server/actors/webconsole/commands", true);
|
||||
@ -1134,9 +1135,14 @@ WebConsoleActor.prototype =
|
||||
));
|
||||
}
|
||||
|
||||
// Make sure we return an array with unique items, since `matches` can hold twice
|
||||
// the same function name if it was defined in the content page and match an helper
|
||||
// function (e.g. $, keys, …).
|
||||
matches = [...new Set(matches)].sort();
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
matches: matches.sort(),
|
||||
matches,
|
||||
matchProp: result.matchProp,
|
||||
};
|
||||
},
|
||||
@ -1418,39 +1424,35 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the Debugger.Frame or Debugger.Object for the global include
|
||||
// $ or $$. We will not overwrite these functions with the Web Console
|
||||
// commands.
|
||||
let found$ = false, found$$ = false, disableScreenshot = false;
|
||||
// Check if the Debugger.Frame or Debugger.Object for the global include any of the
|
||||
// helper function we set. We will not overwrite these functions with the Web Console
|
||||
// commands. The exception being "print" which should exist everywhere as
|
||||
// `window.print`, and that we don't want to trigger from the console.
|
||||
const availableHelpers = [...WebConsoleCommands._originalCommands.keys()]
|
||||
.filter(h => h !== "print");
|
||||
|
||||
let helpersToDisable = [];
|
||||
const helperCache = {};
|
||||
|
||||
// do not override command functions if we are using the command key `:`
|
||||
// before the command string
|
||||
if (!isCmd) {
|
||||
// if we do not have the command key as a prefix, screenshot is disabled by default
|
||||
disableScreenshot = true;
|
||||
if (frame) {
|
||||
const env = frame.environment;
|
||||
if (env) {
|
||||
found$ = !!env.find("$");
|
||||
found$$ = !!env.find("$$");
|
||||
helpersToDisable = availableHelpers.filter(name => !!env.find(name));
|
||||
}
|
||||
} else {
|
||||
found$ = !!dbgWindow.getOwnPropertyDescriptor("$");
|
||||
found$$ = !!dbgWindow.getOwnPropertyDescriptor("$$");
|
||||
helpersToDisable = availableHelpers.filter(name =>
|
||||
!!dbgWindow.getOwnPropertyDescriptor(name));
|
||||
}
|
||||
// if we do not have the command key as a prefix, screenshot is disabled by default
|
||||
helpersToDisable.push("screenshot");
|
||||
}
|
||||
|
||||
let $ = null, $$ = null, screenshot = null;
|
||||
if (found$) {
|
||||
$ = bindings.$;
|
||||
delete bindings.$;
|
||||
}
|
||||
if (found$$) {
|
||||
$$ = bindings.$$;
|
||||
delete bindings.$$;
|
||||
}
|
||||
if (disableScreenshot) {
|
||||
screenshot = bindings.screenshot;
|
||||
delete bindings.screenshot;
|
||||
for (const helper of helpersToDisable) {
|
||||
helperCache[helper] = bindings[helper];
|
||||
delete bindings[helper];
|
||||
}
|
||||
|
||||
// Ready to evaluate the string.
|
||||
@ -1547,14 +1549,8 @@ WebConsoleActor.prototype =
|
||||
delete helpers.helperResult;
|
||||
delete helpers.selectedNode;
|
||||
|
||||
if ($) {
|
||||
bindings.$ = $;
|
||||
}
|
||||
if ($$) {
|
||||
bindings.$$ = $$;
|
||||
}
|
||||
if (screenshot) {
|
||||
bindings.screenshot = screenshot;
|
||||
for (const [helperName, helper] of Object.entries(helperCache)) {
|
||||
bindings[helperName] = helper;
|
||||
}
|
||||
|
||||
if (bindings._self) {
|
||||
|
@ -87,6 +87,7 @@
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsIAsyncVerifyRedirectCallback.h"
|
||||
#include "nsIAutoplay.h"
|
||||
#include "nsICachingChannel.h"
|
||||
#include "nsICategoryManager.h"
|
||||
#include "nsIClassOfService.h"
|
||||
@ -1998,7 +1999,7 @@ HTMLMediaElement::Load()
|
||||
HasSourceChildren(this),
|
||||
EventStateManager::IsHandlingUserInput(),
|
||||
HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay),
|
||||
AutoplayPolicy::IsAllowedToPlay(*this) == Authorization::Allowed,
|
||||
AutoplayPolicy::IsAllowedToPlay(*this) == nsIAutoplay::ALLOWED,
|
||||
OwnerDoc(),
|
||||
DocumentOrigin(OwnerDoc()).get(),
|
||||
OwnerDoc() ? OwnerDoc()->HasBeenUserGestureActivated() : 0,
|
||||
@ -2520,7 +2521,7 @@ HTMLMediaElement::UpdatePreloadAction()
|
||||
PreloadAction nextAction = PRELOAD_UNDEFINED;
|
||||
// If autoplay is set, or we're playing, we should always preload data,
|
||||
// as we'll need it to play.
|
||||
if ((AutoplayPolicy::IsAllowedToPlay(*this) == Authorization::Allowed &&
|
||||
if ((AutoplayPolicy::IsAllowedToPlay(*this) == nsIAutoplay::ALLOWED &&
|
||||
HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) ||
|
||||
!mPaused) {
|
||||
nextAction = HTMLMediaElement::PRELOAD_ENOUGH;
|
||||
@ -3053,7 +3054,7 @@ HTMLMediaElement::PauseIfShouldNotBePlaying()
|
||||
if (GetPaused()) {
|
||||
return;
|
||||
}
|
||||
if (AutoplayPolicy::IsAllowedToPlay(*this) != Authorization::Allowed) {
|
||||
if (AutoplayPolicy::IsAllowedToPlay(*this) != nsIAutoplay::ALLOWED) {
|
||||
ErrorResult rv;
|
||||
Pause(rv);
|
||||
}
|
||||
@ -4064,13 +4065,13 @@ HTMLMediaElement::Play(ErrorResult& aRv)
|
||||
|
||||
const bool handlingUserInput = EventStateManager::IsHandlingUserInput();
|
||||
switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
|
||||
case Authorization::Allowed: {
|
||||
case nsIAutoplay::ALLOWED: {
|
||||
mPendingPlayPromises.AppendElement(promise);
|
||||
PlayInternal(handlingUserInput);
|
||||
UpdateCustomPolicyAfterPlayed();
|
||||
break;
|
||||
}
|
||||
case Authorization::Blocked: {
|
||||
case nsIAutoplay::BLOCKED: {
|
||||
LOG(LogLevel::Debug, ("%p play not blocked.", this));
|
||||
promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
|
||||
if (StaticPrefs::MediaBlockEventEnabled()) {
|
||||
@ -4078,7 +4079,7 @@ HTMLMediaElement::Play(ErrorResult& aRv)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Authorization::Prompt: {
|
||||
case nsIAutoplay::PROMPT: {
|
||||
// Prompt the user for permission to play.
|
||||
mPendingPlayPromises.AppendElement(promise);
|
||||
EnsureAutoplayRequested(handlingUserInput);
|
||||
@ -6127,7 +6128,7 @@ HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
||||
if (!mPaused) {
|
||||
if (mDecoder && !mPausedForInactiveDocumentOrChannel) {
|
||||
MOZ_ASSERT(AutoplayPolicy::IsAllowedToPlay(*this) ==
|
||||
Authorization::Allowed);
|
||||
nsIAutoplay::ALLOWED);
|
||||
mDecoder->Play();
|
||||
}
|
||||
NotifyAboutPlaying();
|
||||
@ -6239,12 +6240,12 @@ HTMLMediaElement::CheckAutoplayDataReady()
|
||||
}
|
||||
|
||||
switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
|
||||
case Authorization::Blocked:
|
||||
case nsIAutoplay::BLOCKED:
|
||||
return;
|
||||
case Authorization::Prompt:
|
||||
case nsIAutoplay::PROMPT:
|
||||
EnsureAutoplayRequested(false);
|
||||
return;
|
||||
case Authorization::Allowed:
|
||||
case nsIAutoplay::ALLOWED:
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "mozilla/AutoplayPermissionManager.h"
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
#include "mozilla/dom/HTMLMediaElementBinding.h"
|
||||
#include "nsIAutoplay.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "MediaManager.h"
|
||||
@ -88,42 +89,47 @@ AutoplayPolicy::RequestFor(const nsIDocument& aDocument)
|
||||
return window->GetAutoplayPermissionManager();
|
||||
}
|
||||
|
||||
/* static */ Authorization
|
||||
static uint32_t
|
||||
DefaultAutoplayBehaviour()
|
||||
{
|
||||
int prefValue = Preferences::GetInt("media.autoplay.default", nsIAutoplay::ALLOWED);
|
||||
if (prefValue < nsIAutoplay::ALLOWED || prefValue > nsIAutoplay::PROMPT) {
|
||||
// Invalid pref values are just converted to ALLOWED.
|
||||
return nsIAutoplay::ALLOWED;
|
||||
}
|
||||
return prefValue;
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
|
||||
{
|
||||
if (Preferences::GetBool("media.autoplay.enabled")) {
|
||||
return Authorization::Allowed;
|
||||
}
|
||||
|
||||
const uint32_t autoplayDefault = DefaultAutoplayBehaviour();
|
||||
// TODO : this old way would be removed when user-gestures-needed becomes
|
||||
// as a default option to block autoplay.
|
||||
if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
|
||||
// If element is blessed, it would always be allowed to play().
|
||||
return (aElement.IsBlessed() || EventStateManager::IsHandlingUserInput())
|
||||
? Authorization::Allowed
|
||||
: Authorization::Blocked;
|
||||
return (autoplayDefault == nsIAutoplay::ALLOWED ||
|
||||
aElement.IsBlessed() ||
|
||||
EventStateManager::IsHandlingUserInput())
|
||||
? nsIAutoplay::ALLOWED : nsIAutoplay::BLOCKED;
|
||||
}
|
||||
|
||||
// Muted content
|
||||
if (aElement.Volume() == 0.0 || aElement.Muted()) {
|
||||
return Authorization::Allowed;
|
||||
return nsIAutoplay::ALLOWED;
|
||||
}
|
||||
|
||||
if (IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow())) {
|
||||
return Authorization::Allowed;
|
||||
return nsIAutoplay::ALLOWED;
|
||||
}
|
||||
|
||||
if (Preferences::GetBool("media.autoplay.ask-permission", false)) {
|
||||
return Authorization::Prompt;
|
||||
}
|
||||
|
||||
return Authorization::Blocked;
|
||||
return autoplayDefault;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
|
||||
{
|
||||
if (Preferences::GetBool("media.autoplay.enabled")) {
|
||||
if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -20,20 +20,13 @@ namespace dom {
|
||||
class HTMLMediaElement;
|
||||
class AudioContext;
|
||||
|
||||
enum class Authorization
|
||||
{
|
||||
Allowed,
|
||||
Blocked,
|
||||
Prompt
|
||||
};
|
||||
|
||||
/**
|
||||
* AutoplayPolicy is used to manage autoplay logic for all kinds of media,
|
||||
* including MediaElement, Web Audio and Web Speech.
|
||||
*
|
||||
* Autoplay could be disable by turn off the pref "media.autoplay.enabled".
|
||||
* Once user disable autoplay, media could only be played if one of following
|
||||
* conditions is true.
|
||||
* Autoplay could be disable by setting the pref "media.autoplay.default"
|
||||
* to anything but nsIAutoplay::Allowed. Once user disables autoplay, media
|
||||
* could only be played if one of following conditions is true.
|
||||
* 1) Owner document is activated by user gestures
|
||||
* We restrict user gestures to "mouse click", "keyboard press" and "touch".
|
||||
* 2) Muted media content or video without audio content.
|
||||
@ -43,7 +36,7 @@ class AutoplayPolicy
|
||||
{
|
||||
public:
|
||||
// Returns whether a given media element is allowed to play.
|
||||
static Authorization IsAllowedToPlay(const HTMLMediaElement& aElement);
|
||||
static uint32_t IsAllowedToPlay(const HTMLMediaElement& aElement);
|
||||
|
||||
// Returns whether a given AudioContext is allowed to play.
|
||||
static bool IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "mozilla/dom/CanvasCaptureMediaStreamBinding.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "Tracing.h"
|
||||
|
||||
using namespace mozilla::layers;
|
||||
using namespace mozilla::gfx;
|
||||
@ -51,6 +52,8 @@ public:
|
||||
void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
|
||||
{
|
||||
// Called on the MediaStreamGraph thread.
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p track %i",
|
||||
mSourceStream.get(), mTrackId);
|
||||
MOZ_ASSERT(mSourceStream);
|
||||
StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
|
||||
if (delta > 0) {
|
||||
|
@ -2764,7 +2764,7 @@ SourceMediaStream::SetPullEnabled(bool aEnabled)
|
||||
bool
|
||||
SourceMediaStream::PullNewData(StreamTime aDesiredUpToTime)
|
||||
{
|
||||
TRACE_AUDIO_CALLBACK();
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p", this);
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (!mPullEnabled || mFinished) {
|
||||
return false;
|
||||
|
@ -7,9 +7,7 @@
|
||||
#include "Tracing.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <cstdio>
|
||||
|
||||
#include "AsyncLogger.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
using namespace mozilla;
|
||||
@ -40,16 +38,16 @@ AutoTracer::PrintEvent(const char* aName,
|
||||
void
|
||||
AutoTracer::PrintBudget(const char* aName,
|
||||
const char* aCategory,
|
||||
const char* aComment,
|
||||
uint64_t aDuration,
|
||||
uint64_t aPID,
|
||||
uint64_t aThread,
|
||||
uint64_t aFrames)
|
||||
uint64_t aFrames,
|
||||
uint64_t aSampleRate)
|
||||
{
|
||||
mLogger.Log("{\"name\": \"%s\", \"cat\": \"%s\", \"ph\": \"X\","
|
||||
"\"ts\": %" PRIu64 ", \"dur\": %" PRIu64 ", \"pid\": %" PRIu64 ","
|
||||
"\"tid\": %" PRIu64 ", \"args\": { \"comment\": %" PRIu64 "}},",
|
||||
aName, aCategory, NowInUs(), aDuration, aPID, aThread, aFrames);
|
||||
"\"tid\": %" PRIu64 ", \"args\": { \"comment\": \"%" PRIu64 "/%" PRIu64 "\"}},",
|
||||
aName, aCategory, NowInUs(), aDuration, aPID, aThread, aFrames, aSampleRate);
|
||||
}
|
||||
|
||||
AutoTracer::AutoTracer(AsyncLogger& aLogger,
|
||||
@ -70,7 +68,7 @@ AutoTracer::AutoTracer(AsyncLogger& aLogger,
|
||||
|
||||
if (aLogger.Enabled()) {
|
||||
float durationUS = (static_cast<float>(aFrames) / aSampleRate) * 1e6;
|
||||
PrintBudget(aLocation, "perf", mComment, durationUS, mPID, mTID, aFrames);
|
||||
PrintBudget(aLocation, "perf", durationUS, mPID, mTID, aFrames, aSampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,14 @@
|
||||
#ifndef TRACING_H
|
||||
#define TRACING_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "AsyncLogger.h"
|
||||
|
||||
#include <mozilla/Attributes.h>
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <process.h>
|
||||
@ -44,24 +47,31 @@
|
||||
#define TRACE_AUDIO_CALLBACK_BUDGET(aFrames, aSampleRate) \
|
||||
AutoTracer budget(gMSGTraceLogger, "Real-time budget", getpid(), 1, \
|
||||
AutoTracer::EventType::BUDGET, aFrames, aSampleRate);
|
||||
#define TRACE_AUDIO_CALLBACK_COMMENT(aFmt, ...) \
|
||||
AutoTracer trace(gMSGTraceLogger, FUNCTION_SIGNATURE, getpid(), 0, \
|
||||
AutoTracer::EventType::DURATION, \
|
||||
aFmt, ##__VA_ARGS__);
|
||||
#define TRACE() \
|
||||
AutoTracer trace(gMSGTraceLogger, FUNCTION_SIGNATURE, getpid(), \
|
||||
std::hash<std::thread::id>{}(std::this_thread::get_id()));
|
||||
#define TRACE_COMMENT(aComment) \
|
||||
#define TRACE_COMMENT(aFmt, ...) \
|
||||
AutoTracer trace(gMSGTraceLogger, FUNCTION_SIGNATURE, getpid(), \
|
||||
std::hash<std::thread::id>{}(std::this_thread::get_id()), \
|
||||
AutoTracer::EventType::DURATION, \
|
||||
aComment);
|
||||
aFmt, ##__VA_ARGS__);
|
||||
#else
|
||||
#define TRACE_AUDIO_CALLBACK()
|
||||
#define TRACE_AUDIO_CALLBACK_BUDGET(aFrames, aSampleRate)
|
||||
#define TRACE_AUDIO_CALLBACK_COMMENT(aFmt, ...)
|
||||
#define TRACE()
|
||||
#define TRACE_COMMENT(aComment)
|
||||
#define TRACE_COMMENT(aFmt, ...)
|
||||
#endif
|
||||
|
||||
class MOZ_RAII AutoTracer
|
||||
{
|
||||
public:
|
||||
static const int32_t BUFFER_SIZE = mozilla::AsyncLogger::MAX_MESSAGE_LENGTH / 2;
|
||||
|
||||
enum class EventType
|
||||
{
|
||||
DURATION,
|
||||
@ -74,13 +84,39 @@ public:
|
||||
uint64_t aTID,
|
||||
EventType aEventType = EventType::DURATION,
|
||||
const char* aComment = nullptr);
|
||||
|
||||
template<typename... Args>
|
||||
AutoTracer(mozilla::AsyncLogger& aLogger,
|
||||
const char* aLocation,
|
||||
uint64_t aPID,
|
||||
uint64_t aTID,
|
||||
EventType aEventType,
|
||||
uint64_t aSampleRate,
|
||||
uint64_t aFrames);
|
||||
const char* aFormat,
|
||||
Args... aArgs)
|
||||
: mLogger(aLogger)
|
||||
, mLocation(aLocation)
|
||||
, mComment(mBuffer)
|
||||
, mEventType(aEventType)
|
||||
, mPID(aPID)
|
||||
, mTID(aTID)
|
||||
{
|
||||
MOZ_ASSERT(aEventType == EventType::DURATION);
|
||||
if (aLogger.Enabled()) {
|
||||
int32_t size = snprintf(mBuffer, BUFFER_SIZE, aFormat, aArgs...);
|
||||
size = std::min(size, BUFFER_SIZE - 1);
|
||||
mBuffer[size] = 0;
|
||||
PrintEvent(aLocation, "perf", mComment, TracingPhase::BEGIN, NowInUs(), aPID, aTID);
|
||||
}
|
||||
}
|
||||
|
||||
AutoTracer(mozilla::AsyncLogger& aLogger,
|
||||
const char* aLocation,
|
||||
uint64_t aPID,
|
||||
uint64_t aTID,
|
||||
EventType aEventType,
|
||||
uint64_t aFrames,
|
||||
uint64_t aSampleRate);
|
||||
|
||||
~AutoTracer();
|
||||
private:
|
||||
uint64_t NowInUs();
|
||||
@ -108,11 +144,11 @@ private:
|
||||
|
||||
void PrintBudget(const char* aName,
|
||||
const char* aCategory,
|
||||
const char* aComment,
|
||||
uint64_t aDuration,
|
||||
uint64_t aPID,
|
||||
uint64_t aThread,
|
||||
uint64_t aFrames);
|
||||
uint64_t aFrames,
|
||||
uint64_t aSampleRate);
|
||||
|
||||
// The logger to use. It musdt have a lifetime longer than the block an
|
||||
// instance of this class traces.
|
||||
@ -123,6 +159,8 @@ private:
|
||||
// A comment for this trace point, abitrary string literal with a static
|
||||
// lifetime.
|
||||
const char* mComment;
|
||||
// A buffer used to hold string-formatted traces.
|
||||
char mBuffer[BUFFER_SIZE];
|
||||
// The event type, for now either a budget or a duration.
|
||||
const EventType mEventType;
|
||||
// The process ID of the calling process. Traces are grouped by PID in the
|
||||
|
@ -68,7 +68,7 @@ TrackUnionStream::TrackUnionStream()
|
||||
}
|
||||
void TrackUnionStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
|
||||
{
|
||||
TRACE();
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("TrackUnionStream %p", this);
|
||||
if (IsFinishedOnGraphThread()) {
|
||||
return;
|
||||
}
|
||||
@ -290,6 +290,11 @@ TrackUnionStream::TrackUnionStream()
|
||||
bool* aOutputTrackFinished)
|
||||
{
|
||||
TrackMapEntry* map = &mTrackMap[aMapIndex];
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("Input stream %p track %i -> TrackUnionStream %p track %i",
|
||||
map->mInputPort->GetSource(),
|
||||
map->mInputTrackID,
|
||||
this,
|
||||
map->mOutputTrackID);
|
||||
StreamTracks::Track* outputTrack = mTracks.FindTrack(map->mOutputTrackID);
|
||||
MOZ_ASSERT(outputTrack && !outputTrack->IsEnded(), "Can't copy to ended track");
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "VideoFrameContainer.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "MediaDecoderOwner.h"
|
||||
#include "Tracing.h"
|
||||
|
||||
using namespace mozilla::layers;
|
||||
|
||||
@ -118,6 +119,8 @@ private:
|
||||
|
||||
void VideoFrameContainer::SetCurrentFrames(const VideoSegment& aSegment)
|
||||
{
|
||||
TRACE();
|
||||
|
||||
if (aSegment.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "OggWriter.h"
|
||||
#include "OpusTrackEncoder.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "Tracing.h"
|
||||
|
||||
#ifdef MOZ_WEBM_ENCODER
|
||||
#include "VP8TrackEncoder.h"
|
||||
@ -89,6 +90,7 @@ public:
|
||||
StreamTime aTrackOffset,
|
||||
const MediaSegment& aQueuedMedia) override
|
||||
{
|
||||
TRACE_COMMENT("Encoder %p", mEncoder.get());
|
||||
MOZ_ASSERT(mEncoder);
|
||||
MOZ_ASSERT(mEncoderThread);
|
||||
|
||||
@ -137,6 +139,7 @@ public:
|
||||
StreamTime aTrackOffset,
|
||||
const MediaSegment& aMedia) override
|
||||
{
|
||||
TRACE_COMMENT("Encoder %p", mEncoder.get());
|
||||
MOZ_ASSERT(mEncoder);
|
||||
MOZ_ASSERT(mEncoderThread);
|
||||
|
||||
@ -249,6 +252,7 @@ public:
|
||||
StreamTime aTrackOffset,
|
||||
const MediaSegment& aQueuedMedia) override
|
||||
{
|
||||
TRACE_COMMENT("Encoder %p", mEncoder.get());
|
||||
MOZ_ASSERT(mEncoder);
|
||||
MOZ_ASSERT(mEncoderThread);
|
||||
|
||||
@ -291,6 +295,7 @@ public:
|
||||
|
||||
void SetCurrentFrames(const VideoSegment& aMedia) override
|
||||
{
|
||||
TRACE_COMMENT("Encoder %p", mEncoder.get());
|
||||
MOZ_ASSERT(mEncoder);
|
||||
MOZ_ASSERT(mEncoderThread);
|
||||
|
||||
|
@ -29,6 +29,8 @@ if CONFIG['MOZ_WEBM_ENCODER']:
|
||||
]
|
||||
LOCAL_INCLUDES += ['/media/libyuv/libyuv/include']
|
||||
|
||||
DEFINES['TRACING'] = True
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
# These includes are from Android JB, for use of MediaCodec.
|
||||
|
@ -81,6 +81,7 @@ if CONFIG['MOZ_WEBRTC']:
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIAudioDeviceInfo.idl',
|
||||
'nsIAutoplay.idl',
|
||||
'nsIDOMNavigatorUserMedia.idl',
|
||||
'nsIMediaManager.idl',
|
||||
]
|
||||
|
17
dom/media/nsIAutoplay.idl
Normal file
17
dom/media/nsIAutoplay.idl
Normal file
@ -0,0 +1,17 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(048a24f6-c4d6-47bc-bea2-f6038d1db80a)]
|
||||
interface nsIAutoplay : nsISupports
|
||||
{
|
||||
/*
|
||||
* Possible values for the "media.autoplay.default" preference.
|
||||
*/
|
||||
const uint32_t ALLOWED = 0;
|
||||
const uint32_t BLOCKED = 1;
|
||||
const uint32_t PROMPT = 2;
|
||||
};
|
@ -14,7 +14,7 @@
|
||||
|
||||
let manager = new MediaTestManager;
|
||||
|
||||
gTestPrefs.push(["media.autoplay.enabled", false],
|
||||
gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]);
|
||||
|
||||
window.info = function(msg, token) {
|
||||
@ -71,7 +71,7 @@ function createTestArray()
|
||||
/**
|
||||
* Main test function for different autoplay cases without user interaction.
|
||||
*
|
||||
* When the pref "media.autoplay.enabled" is false and the pref
|
||||
* When the pref "media.autoplay.default" is 1 and the pref
|
||||
* "media.autoplay.enabled.user-gestures-needed" is true, we only allow
|
||||
* audible media to autoplay after the website has been activated by specific
|
||||
* user gestures. However, inaudible media won't be restricted.
|
||||
@ -152,4 +152,4 @@ async function testAutoplayKeyword(test, token) {
|
||||
removeNodeAndSource(element);
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
@ -14,7 +14,7 @@
|
||||
// Tests that videos can only play audibly in windows/frames
|
||||
// which have been activated by same-origin user gesture.
|
||||
|
||||
gTestPrefs.push(["media.autoplay.enabled", false],
|
||||
gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]);
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': gTestPrefs}, () => {
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
// Tests that we gesture activate on mousedown and keydown.
|
||||
|
||||
gTestPrefs.push(["media.autoplay.enabled", false],
|
||||
gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]);
|
||||
|
||||
SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
|
||||
@ -52,4 +52,4 @@
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -18,7 +18,7 @@
|
||||
// don't gesture activate documents, and don't unblock
|
||||
// audible autoplay.
|
||||
|
||||
gTestPrefs.push(["media.autoplay.enabled", false],
|
||||
gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]);
|
||||
|
||||
SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
|
||||
@ -44,4 +44,4 @@
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
// Tests that origins with "autoplay-media" permission can autoplay.
|
||||
|
||||
gTestPrefs.push(["media.autoplay.enabled", false],
|
||||
gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true],
|
||||
// Note: permission prompt disabled, as we want immediate
|
||||
// notification in this test that an origin is blocked.
|
||||
|
@ -19,7 +19,7 @@
|
||||
// Tests that videos which have no audio track will play if play()
|
||||
// is called before the video has reached readyState >= HAVE_METADATA.
|
||||
|
||||
gTestPrefs.push(["media.autoplay.enabled", false],
|
||||
gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]);
|
||||
|
||||
SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
|
||||
@ -70,4 +70,4 @@
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -19,7 +19,7 @@
|
||||
// Tests that videos can only play audibly in windows/frames
|
||||
// which have been activated by same-origin user gesture.
|
||||
|
||||
gTestPrefs.push(["media.autoplay.enabled", false],
|
||||
gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]);
|
||||
|
||||
SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
|
||||
@ -61,4 +61,4 @@
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -439,7 +439,7 @@ function setupEnvironment() {
|
||||
["media.navigator.video.default_width", 320],
|
||||
["media.navigator.video.default_height", 240],
|
||||
["media.navigator.video.max_fr", 10],
|
||||
["media.autoplay.enabled", true]
|
||||
["media.autoplay.default", Ci.nsIAutoplay.ALLOWED]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ SimpleTest.registerCleanupFunction(function() {
|
||||
SpecialPowers.removeAsyncObserver(observer, "webaudio-node-demise");
|
||||
});
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.autoplay.enabled", false],
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]]},
|
||||
startTest);
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "nsIFilePicker.h"
|
||||
#include "nsIPrefBranch.h"
|
||||
#include "nsIPrefService.h"
|
||||
#include "Tracing.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
#include "nsISupportsUtils.h"
|
||||
@ -330,6 +331,8 @@ MediaEngineDefaultVideoSource::Pull(const RefPtr<const AllocationHandle>& aHandl
|
||||
StreamTime aDesiredTime,
|
||||
const PrincipalHandle& aPrincipalHandle)
|
||||
{
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p track %i",
|
||||
aStream.get(), aTrackID);
|
||||
// AppendFrame takes ownership of `segment`
|
||||
VideoSegment segment;
|
||||
|
||||
@ -542,6 +545,8 @@ MediaEngineDefaultAudioSource::Pull(const RefPtr<const AllocationHandle>& aHandl
|
||||
StreamTime aDesiredTime,
|
||||
const PrincipalHandle& aPrincipalHandle)
|
||||
{
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p track %i",
|
||||
aStream.get(), aTrackID);
|
||||
AudioSegment segment;
|
||||
// avoid accumulating rounding errors
|
||||
TrackTicks desired = aStream->TimeToTicksRoundUp(aStream->GraphRate(), aDesiredTime);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "mozilla/ErrorNames.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsIPrefService.h"
|
||||
#include "Tracing.h"
|
||||
#include "VideoFrameUtils.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "webrtc/common_video/include/video_frame_buffer.h"
|
||||
@ -486,6 +487,8 @@ MediaEngineRemoteVideoSource::Pull(const RefPtr<const AllocationHandle>& aHandle
|
||||
StreamTime aDesiredTime,
|
||||
const PrincipalHandle& aPrincipalHandle)
|
||||
{
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p track %i",
|
||||
aStream.get(), aTrackID);
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (mState == kReleased) {
|
||||
// We end the track before deallocating, so this is safe.
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIPrefService.h"
|
||||
#include "MediaTrackConstraints.h"
|
||||
#include "Tracing.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -257,6 +258,8 @@ MediaEngineTabVideoSource::Pull(const RefPtr<const AllocationHandle>& aHandle,
|
||||
StreamTime aDesiredTime,
|
||||
const PrincipalHandle& aPrincipalHandle)
|
||||
{
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p track %i",
|
||||
aStream.get(), aTrackID);
|
||||
VideoSegment segment;
|
||||
RefPtr<layers::Image> image;
|
||||
gfx::IntSize imageSize;
|
||||
|
@ -788,6 +788,8 @@ MediaEngineWebRTCMicrophoneSource::Pull(const RefPtr<const AllocationHandle>& aH
|
||||
StreamTime aDesiredTime,
|
||||
const PrincipalHandle& aPrincipalHandle)
|
||||
{
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p track %i",
|
||||
aStream.get(), aTrackID);
|
||||
StreamTime delta;
|
||||
|
||||
{
|
||||
|
@ -57,6 +57,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
#include "mozilla/gfx/DeviceManagerDx.h"
|
||||
#endif
|
||||
@ -2522,6 +2523,22 @@ gfxPlatform::WebRenderEnvvarEnabled()
|
||||
return (env && *env == '1');
|
||||
}
|
||||
|
||||
/* This is a pretty conservative check for having a battery.
|
||||
* For now we'd rather err on the side of thinking we do. */
|
||||
static bool HasBattery()
|
||||
{
|
||||
#ifdef XP_WIN
|
||||
SYSTEM_POWER_STATUS status;
|
||||
const BYTE NO_SYSTEM_BATTERY = 128;
|
||||
if (GetSystemPowerStatus(&status)) {
|
||||
if (status.BatteryFlag == NO_SYSTEM_BATTERY) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatform::InitWebRenderConfig()
|
||||
{
|
||||
@ -2572,7 +2589,7 @@ gfxPlatform::InitWebRenderConfig()
|
||||
int32_t status;
|
||||
if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBRENDER,
|
||||
discardFailureId, &status))) {
|
||||
if (status == nsIGfxInfo::FEATURE_STATUS_OK) {
|
||||
if (status == nsIGfxInfo::FEATURE_STATUS_OK && !HasBattery()) {
|
||||
featureWebRender.UserEnable("Qualified enabled by pref ");
|
||||
} else {
|
||||
featureWebRender.ForceDisable(FeatureStatus::Blocked,
|
||||
|
@ -345,8 +345,6 @@ __END
|
||||
|
||||
our $totalData = 0;
|
||||
|
||||
print HEADER "#pragma pack(1)\n\n";
|
||||
|
||||
sub sprintCharProps2_short
|
||||
{
|
||||
my $usv = shift;
|
||||
@ -364,8 +362,6 @@ struct nsCharProps2 {
|
||||
|;
|
||||
&genTables("CharProp2", $type, "nsCharProps2", 9, 7, \&sprintCharProps2_short, 16, 1, 1);
|
||||
|
||||
print HEADER "#pragma pack()\n\n";
|
||||
|
||||
sub sprintHanVariants
|
||||
{
|
||||
my $baseUsv = shift;
|
||||
|
@ -47,9 +47,6 @@ for the Unicode Character Database, for Version 11.0.0 of the Unicode Standard.
|
||||
#ifndef NS_UNICODE_SCRIPT_CODES
|
||||
#define NS_UNICODE_SCRIPT_CODES
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
|
||||
struct nsCharProps2 {
|
||||
// Currently only 4 bits are defined here, so 4 more could be added without
|
||||
// affecting the storage requirements for this struct. Or we could pack two
|
||||
@ -58,8 +55,6 @@ struct nsCharProps2 {
|
||||
unsigned char mIdType:2;
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
namespace mozilla {
|
||||
namespace unicode {
|
||||
enum class Script : int16_t {
|
||||
|
@ -162,7 +162,7 @@ uint8_t*
|
||||
js::jit::Disassembler::DisassembleHeapAccess(uint8_t* ptr, HeapAccess* access)
|
||||
{
|
||||
VexOperandType type = VEX_PS;
|
||||
uint32_t opcode = OP_HLT;
|
||||
uint32_t opcode = OP_NOP_00;
|
||||
uint8_t modrm = 0;
|
||||
uint8_t sib = 0;
|
||||
uint8_t rex = 0;
|
||||
|
@ -1730,6 +1730,8 @@ MediaPipelineTransmit::PipelineListener::NotifyQueuedChanges(
|
||||
return;
|
||||
}
|
||||
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("Audio");
|
||||
|
||||
if (mDirectConnect) {
|
||||
// ignore non-direct data if we're also getting direct data
|
||||
return;
|
||||
@ -2000,7 +2002,7 @@ private:
|
||||
|
||||
void NotifyPullImpl(StreamTime aDesiredTime)
|
||||
{
|
||||
TRACE();
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("Track %i", mTrackId);
|
||||
uint32_t samplesPer10ms = mRate / 100;
|
||||
|
||||
// mSource's rate is not necessarily the same as the graph rate, since there
|
||||
@ -2179,6 +2181,7 @@ public:
|
||||
// Implement MediaStreamListener
|
||||
void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
|
||||
{
|
||||
TRACE_AUDIO_CALLBACK_COMMENT("Track %i", mTrackId);
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
RefPtr<Image> image = mImage;
|
||||
|
@ -26,4 +26,6 @@ UNIFIED_SOURCES += [
|
||||
'TransportLayerPacketDumper.cpp',
|
||||
]
|
||||
|
||||
DEFINES['TRACING'] = True
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
@ -54,7 +54,7 @@
|
||||
|
||||
<PreferenceCategory android:title="@string/pref_category_media">
|
||||
|
||||
<SwitchPreference android:key="media.autoplay.enabled"
|
||||
<SwitchPreference android:key="media.autoplay.default"
|
||||
android:title="@string/pref_media_autoplay_enabled"
|
||||
android:summary="@string/pref_media_autoplay_enabled_summary" />
|
||||
|
||||
|
@ -14,6 +14,7 @@ import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.widget.AllCapsTextView;
|
||||
import org.mozilla.gecko.widget.FocusableDatePicker;
|
||||
import org.mozilla.gecko.widget.DateTimePicker;
|
||||
import org.mozilla.gecko.widget.FocusableTimePicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
@ -210,7 +211,8 @@ public abstract class PromptInput {
|
||||
DateTimePicker.PickersState.WEEK, mMinValue, mMaxValue);
|
||||
mView = (View)input;
|
||||
} else if (mType.equals("time")) {
|
||||
TimePicker input = new TimePicker(context);
|
||||
// FocusableDatePicker allow us to have priority in responding to scroll events.
|
||||
TimePicker input = new FocusableTimePicker(context);
|
||||
input.setIs24HourView(DateFormat.is24HourFormat(context));
|
||||
|
||||
GregorianCalendar calendar = new GregorianCalendar();
|
||||
|
@ -0,0 +1,49 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
/**
|
||||
* This is based on the platform's {@link TimePicker}.<br>
|
||||
* The only difference is that it will prevent it's parent from receiving touch events.
|
||||
*/
|
||||
public class FocusableTimePicker extends TimePicker {
|
||||
public FocusableTimePicker(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public FocusableTimePicker(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public FocusableTimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public FocusableTimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
ViewParent parentView = getParent();
|
||||
|
||||
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
if (parentView != null) {
|
||||
parentView.requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -114,21 +114,42 @@ public class CrashReporterService extends JobIntentService {
|
||||
Log.i(LOGTAG, "moving " + inFile + " to " + outFile);
|
||||
if (inFile.renameTo(outFile))
|
||||
return true;
|
||||
FileInputStream inStream = null;
|
||||
FileOutputStream outStream = null;
|
||||
try {
|
||||
outFile.createNewFile();
|
||||
Log.i(LOGTAG, "couldn't rename minidump file");
|
||||
// so copy it instead
|
||||
FileChannel inChannel = new FileInputStream(inFile).getChannel();
|
||||
FileChannel outChannel = new FileOutputStream(outFile).getChannel();
|
||||
inStream = new FileInputStream(inFile);
|
||||
outStream = new FileOutputStream(outFile);
|
||||
FileChannel inChannel = inStream.getChannel();
|
||||
FileChannel outChannel = outStream.getChannel();
|
||||
long transferred = inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
inChannel.close();
|
||||
outChannel.close();
|
||||
|
||||
if (transferred > 0)
|
||||
inFile.delete();
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "exception while copying minidump file: ", e);
|
||||
return false;
|
||||
} finally {
|
||||
// always try and close inStream and outStream while taking into
|
||||
// consideration that `.close` throws as well.
|
||||
try {
|
||||
if (inStream != null) {
|
||||
inStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "inStream could not be closed: ", e);
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "outStream could not be closed: ", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -260,12 +281,23 @@ public class CrashReporterService extends JobIntentService {
|
||||
}
|
||||
|
||||
private boolean readStringsFromFile(String filePath, Map<String, String> stringMap) {
|
||||
FileReader fileReader = null;
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(filePath));
|
||||
fileReader = new FileReader(filePath);
|
||||
BufferedReader reader = new BufferedReader(fileReader);
|
||||
return readStringsFromReader(reader, stringMap);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "exception while reading strings: ", e);
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (fileReader != null) {
|
||||
fileReader.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "exception while closing file: ", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,13 +353,13 @@ public class CrashReporterService extends JobIntentService {
|
||||
if (spec == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
final URL url = new URL(URLDecoder.decode(spec, "UTF-8"));
|
||||
final URI uri = new URI(url.getProtocol(), url.getUserInfo(),
|
||||
url.getHost(), url.getPort(),
|
||||
url.getPath(), url.getQuery(), url.getRef());
|
||||
HttpURLConnection conn = (HttpURLConnection) ProxySelector.openConnectionWithProxy(uri);
|
||||
conn = (HttpURLConnection) ProxySelector.openConnectionWithProxy(uri);
|
||||
conn.setRequestMethod("POST");
|
||||
String boundary = generateBoundary();
|
||||
conn.setDoOutput(true);
|
||||
@ -398,6 +430,10 @@ public class CrashReporterService extends JobIntentService {
|
||||
Log.e(LOGTAG, "exception during send: ", e);
|
||||
} catch (URISyntaxException e) {
|
||||
Log.e(LOGTAG, "exception during new URI: ", e);
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1711,7 +1711,7 @@ public class GeckoAppShell
|
||||
final GeckoProfile profile = GeckoThread.getActiveProfile();
|
||||
if (profile != null) {
|
||||
File lock = profile.getFile(".parentlock");
|
||||
return lock.exists() && lock.delete();
|
||||
return lock != null && lock.exists() && lock.delete();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -473,9 +473,11 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
HttpURLConnection urlConnection = null;
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
URI finalURI = new URI(mURL + "&signedRequest=" + URLEncoder.encode(new String(mDrmRequest), "UTF-8"));
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(finalURI);
|
||||
urlConnection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(finalURI);
|
||||
urlConnection.setRequestMethod("POST");
|
||||
if (DEBUG) Log.d(LOGTAG, "Provisioning, posting url =" + finalURI.toString());
|
||||
|
||||
@ -489,8 +491,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
||||
|
||||
int responseCode = urlConnection.getResponseCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader in =
|
||||
new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StringUtils.UTF_8));
|
||||
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StringUtils.UTF_8));
|
||||
String inputLine;
|
||||
StringBuffer response = new StringBuffer();
|
||||
|
||||
@ -508,6 +509,17 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
|
||||
Log.e(LOGTAG, "Got exception during posting provisioning request ...", e);
|
||||
} catch (URISyntaxException e) {
|
||||
Log.e(LOGTAG, "Got exception during creating uri ...", e);
|
||||
} finally {
|
||||
if (urlConnection != null) {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
try {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Exception during closing in ...", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -563,12 +563,14 @@ pref("media.recorder.audio_node.enabled", false);
|
||||
// to keep up under load. Useful for tests but beware of memory consumption!
|
||||
pref("media.recorder.video.frame_drops", true);
|
||||
|
||||
// Whether to autostart a media element with an |autoplay| attribute
|
||||
pref("media.autoplay.enabled", true);
|
||||
// Whether to autostart a media element with an |autoplay| attribute.
|
||||
// ALLOWED=0, BLOCKED=1, PROMPT=2, defined in dom/media/Autoplay.idl
|
||||
pref("media.autoplay.default", 0);
|
||||
|
||||
// If "media.autoplay.enabled" is false, and this pref is true, then audible media
|
||||
// would only be allowed to autoplay after website has been activated by specific
|
||||
// user gestures, but the non-audible media won't be restricted.
|
||||
// If "media.autoplay.default" is not ALLOWED, and this pref is true,
|
||||
// then audible media would only be allowed to autoplay after website has
|
||||
// been activated by specific user gestures, but non-audible
|
||||
// media won't be restricted.
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("media.autoplay.enabled.user-gestures-needed", false);
|
||||
#endif
|
||||
|
@ -154,7 +154,7 @@ public:
|
||||
|
||||
// TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
|
||||
// a busy error, so this handling can be removed.
|
||||
nsresult rv = NS_OK;
|
||||
nsresult rv;
|
||||
do {
|
||||
rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK"));
|
||||
if (rv == NS_ERROR_STORAGE_BUSY)
|
||||
|
@ -30,6 +30,7 @@ config = {
|
||||
"clobber",
|
||||
"download-and-extract",
|
||||
"populate-webroot",
|
||||
"install-chrome",
|
||||
"create-virtualenv",
|
||||
"install",
|
||||
"run-tests",
|
||||
|
@ -29,6 +29,7 @@ config = {
|
||||
"clobber",
|
||||
"download-and-extract",
|
||||
"populate-webroot",
|
||||
"install-chrome",
|
||||
"create-virtualenv",
|
||||
"install",
|
||||
"run-tests",
|
||||
|
@ -170,25 +170,42 @@ class Raptor(TestingMixin, MercurialScript, CodeCoverageMixin):
|
||||
# mozharness/base/script.py.self.platform_name will return one of:
|
||||
# 'linux64', 'linux', 'macosx', 'win64', 'win32'
|
||||
|
||||
base_url = "http://commondatastorage.googleapis.com/chromium-browser-snapshots"
|
||||
|
||||
# note: temporarily use a specified chromium revision number to download; however
|
||||
# in the future we will be using a fetch task to get a new chromium (Bug 1476372)
|
||||
|
||||
if 'mac' in self.platform_name():
|
||||
chrome_archive_file = "googlechrome.dmg"
|
||||
chrome_url = "https://dl.google.com/chrome/mac/stable/GGRO/%s" % chrome_archive_file
|
||||
self.chrome_path = os.path.join(self.chrome_dest, 'Google Chrome.app',
|
||||
'Contents', 'MacOS', 'Google Chrome')
|
||||
# for now hardcoding a revision; but change this to update to newer version; from:
|
||||
# http://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/LAST_CHANGE
|
||||
chromium_rev = "575625"
|
||||
chrome_archive_file = "chrome-mac.zip"
|
||||
chrome_url = "%s/Mac/%s/%s" % (base_url, chromium_rev, chrome_archive_file)
|
||||
self.chrome_path = os.path.join(self.chrome_dest, 'chrome-mac', 'Chromium.app',
|
||||
'Contents', 'MacOS', 'Chromium')
|
||||
|
||||
elif 'linux' in self.platform_name():
|
||||
chrome_archive_file = "google-chrome-stable_current_amd64.deb"
|
||||
chrome_url = "https://dl.google.com/linux/direct/%s" % chrome_archive_file
|
||||
self.chrome_path = os.path.join(self.chrome_dest, 'opt', 'google',
|
||||
'chrome', 'google-chrome')
|
||||
# for now hardcoding a revision; but change this to update to newer version; from:
|
||||
# http://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/LAST_CHANGE
|
||||
chromium_rev = "575640"
|
||||
chrome_archive_file = "chrome-linux.zip"
|
||||
chrome_url = "%s/Linux_x64/%s/%s" % (base_url, chromium_rev, chrome_archive_file)
|
||||
self.chrome_path = os.path.join(self.chrome_dest, 'chrome-linux', 'chrome')
|
||||
|
||||
else:
|
||||
# windows 7/10
|
||||
# for now hardcoding a revision; but change this to update to newer version; from:
|
||||
# http://commondatastorage.googleapis.com/chromium-browser-snapshots/Win_x64/LAST_CHANGE
|
||||
chromium_rev = "575637"
|
||||
chrome_archive_file = "chrome-win32.zip" # same zip name for win32/64
|
||||
|
||||
# url is different for win32/64
|
||||
if '64' in self.platform_name():
|
||||
chrome_archive_file = "standalonesetup64.exe"
|
||||
chrome_url = "%s/Win_x64/%s/%s" % (base_url, chromium_rev, chrome_archive_file)
|
||||
else:
|
||||
chrome_archive_file = "standalonesetup.exe"
|
||||
chrome_url = "https://dl.google.com/chrome/install/%s" % chrome_archive_file
|
||||
chrome_url = "%s/Win_x32/%s/%s" % (base_url, chromium_rev, chrome_archive_file)
|
||||
|
||||
self.chrome_path = os.path.join(self.chrome_dest, 'chrome-win32', 'Chrome.exe')
|
||||
|
||||
chrome_archive = os.path.join(self.chrome_dest, chrome_archive_file)
|
||||
|
||||
@ -205,28 +222,7 @@ class Raptor(TestingMixin, MercurialScript, CodeCoverageMixin):
|
||||
self.download_file(chrome_url, parent_dir=self.chrome_dest)
|
||||
|
||||
commands = []
|
||||
|
||||
if 'mac' in self.platform_name():
|
||||
# open the chrome dmg to have it mounted
|
||||
commands.append(["open", chrome_archive_file])
|
||||
|
||||
# then extract/copy app from mnt to our folder
|
||||
commands.append(["cp", "-r", "/Volumes/Google Chrome/Google Chrome.app", "."])
|
||||
|
||||
elif 'linux' in self.platform_name():
|
||||
# on linux in order to avoid needing sudo, we unpack the google chrome deb
|
||||
# manually using ar, and then unpack the contents of the ar after that
|
||||
commands.append(["ar", "x", chrome_archive_file])
|
||||
|
||||
# now we have the google chrome .deb file unpacked using ar, we need to
|
||||
# unpack the tar.xz file that was inside the .deb
|
||||
commands.append(['tar', '-xJf',
|
||||
os.path.join(self.chrome_dest, 'data.tar.xz'),
|
||||
'-C', self.chrome_dest])
|
||||
|
||||
else:
|
||||
# TODO: Bug 1473389 - finish this for windows 7 (x32) and winows 10 (x64)
|
||||
pass
|
||||
commands.append(['unzip', '-q', '-o', chrome_archive_file, '-d', self.chrome_dest])
|
||||
|
||||
# now run the commands to unpack / install google chrome
|
||||
for next_command in commands:
|
||||
|
@ -87,7 +87,7 @@ Damp.prototype = {
|
||||
// as it slow down next executions almost like a cold start.
|
||||
|
||||
// See minimizeMemoryUsage code to justify the 3 iterations and the setTimeout:
|
||||
// https://searchfox.org/mozilla-central/source/xpcom/base/nsMemoryReporterManager.cpp#2574-2585
|
||||
// https://searchfox.org/mozilla-central/rev/33c21c060b7f3a52477a73d06ebcb2bf313c4431/xpcom/base/nsMemoryReporterManager.cpp#2574-2585,2591-2594
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// See minimizeMemoryUsage code here to justify the GC+CC+GC:
|
||||
// https://searchfox.org/mozilla-central/rev/be78e6ea9b10b1f5b2b3b013f01d86e1062abb2b/dom/base/nsJSEnvironment.cpp#341-349
|
||||
|
@ -55,7 +55,6 @@ const {
|
||||
LocaleData,
|
||||
NoCloneSpreadArgs,
|
||||
SchemaAPIInterface,
|
||||
defineLazyGetter,
|
||||
withHandlingUserInput,
|
||||
} = ExtensionCommon;
|
||||
|
||||
@ -611,10 +610,6 @@ class BrowserExtensionContent extends EventEmitter {
|
||||
this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
|
||||
Services.cpmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);
|
||||
|
||||
defineLazyGetter(this, "scripts", () => {
|
||||
return data.contentScripts.map(scriptData => new ExtensionContent.Script(this, scriptData));
|
||||
});
|
||||
|
||||
this.webAccessibleResources = data.webAccessibleResources.map(res => new MatchGlob(res));
|
||||
this.permissions = data.permissions;
|
||||
this.optionalPermissions = data.optionalPermissions;
|
||||
@ -1034,6 +1029,9 @@ class ChildAPIManager {
|
||||
|
||||
close() {
|
||||
this.messageManager.sendAsyncMessage("API:CloseProxyContext", {childId: this.id});
|
||||
this.messageManager.removeMessageListener("API:CallResult", this);
|
||||
MessageChannel.removeListener(this.messageManager, "API:RunListener", this);
|
||||
|
||||
if (this.updatePermissions) {
|
||||
this.context.extension.off("add-permissions", this.updatePermissions);
|
||||
this.context.extension.off("remove-permissions", this.updatePermissions);
|
||||
|
@ -274,6 +274,15 @@ defineLazyGetter(BrowserExtensionContent.prototype, "authorCSSCode", function()
|
||||
|
||||
// Represents a content script.
|
||||
class Script {
|
||||
/**
|
||||
* @param {BrowserExtensionContent} extension
|
||||
* @param {WebExtensionContentScript|object} matcher
|
||||
* An object with a "matchesWindow" method and content script execution
|
||||
* details. This is usually a plain WebExtensionContentScript object,
|
||||
* except when the script is run via `tabs.executeScript`. In this
|
||||
* case, the object may have some extra properties:
|
||||
* wantReturnValue, removeCSS, cssOrigin, jsCode
|
||||
*/
|
||||
constructor(extension, matcher) {
|
||||
this.extension = extension;
|
||||
this.matcher = matcher;
|
||||
@ -528,17 +537,6 @@ class ContentScriptContextChild extends BaseContext {
|
||||
|
||||
this.isExtensionPage = contentPrincipal.equals(extensionPrincipal);
|
||||
|
||||
let principal;
|
||||
if (ssm.isSystemPrincipal(contentPrincipal)) {
|
||||
// Make sure we don't hand out the system principal by accident.
|
||||
// also make sure that the null principal has the right origin attributes
|
||||
principal = ssm.createNullPrincipal(attrs);
|
||||
} else if (this.isExtensionPage) {
|
||||
principal = contentPrincipal;
|
||||
} else {
|
||||
principal = [contentPrincipal, extensionPrincipal];
|
||||
}
|
||||
|
||||
if (this.isExtensionPage) {
|
||||
// This is an iframe with content script API enabled and its principal
|
||||
// should be the contentWindow itself. We create a sandbox with the
|
||||
@ -553,6 +551,14 @@ class ContentScriptContextChild extends BaseContext {
|
||||
isWebExtensionContentScript: true,
|
||||
});
|
||||
} else {
|
||||
let principal;
|
||||
if (ssm.isSystemPrincipal(contentPrincipal)) {
|
||||
// Make sure we don't hand out the system principal by accident.
|
||||
// Also make sure that the null principal has the right origin attributes.
|
||||
principal = ssm.createNullPrincipal(attrs);
|
||||
} else {
|
||||
principal = [contentPrincipal, extensionPrincipal];
|
||||
}
|
||||
// This metadata is required by the Developer Tools, in order for
|
||||
// the content script to be associated with both the extension and
|
||||
// the tab holding the content page.
|
||||
|
@ -195,8 +195,8 @@ DocumentManager = {
|
||||
});
|
||||
},
|
||||
|
||||
initExtension(extension) {
|
||||
this.injectExtensionScripts(extension);
|
||||
initExtension(policy) {
|
||||
this.injectExtensionScripts(policy);
|
||||
},
|
||||
|
||||
// Listeners
|
||||
@ -209,11 +209,11 @@ DocumentManager = {
|
||||
|
||||
// Script loading
|
||||
|
||||
injectExtensionScripts(extension) {
|
||||
injectExtensionScripts(policy) {
|
||||
for (let window of this.enumerateWindows()) {
|
||||
let runAt = {document_start: [], document_end: [], document_idle: []};
|
||||
|
||||
for (let script of extension.contentScripts) {
|
||||
for (let script of policy.contentScripts) {
|
||||
if (script.matchesWindow(window)) {
|
||||
runAt[script.runAt].push(script);
|
||||
}
|
||||
@ -296,7 +296,7 @@ DocumentManager = {
|
||||
|
||||
ExtensionManager = {
|
||||
// WeakMap<WebExtensionPolicy, Map<string, WebExtensionContentScript>>
|
||||
registeredContentScripts: new DefaultWeakMap((extension) => new Map()),
|
||||
registeredContentScripts: new DefaultWeakMap((policy) => new Map()),
|
||||
|
||||
init() {
|
||||
MessageChannel.setupMessageManagers([Services.cpmm]);
|
||||
|
@ -17,7 +17,7 @@ Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
// Make sure media pre-loading is enabled on Android so that our <audio> and
|
||||
// <video> elements trigger the expected requests.
|
||||
Services.prefs.setBoolPref("media.autoplay.enabled", true);
|
||||
Services.prefs.setIntPref("media.autoplay.default", Ci.nsIAutoplay.ALLOWED);
|
||||
Services.prefs.setIntPref("media.preload.default", 3);
|
||||
|
||||
// Increase the length of the code samples included in CSP reports so that we
|
||||
|
@ -0,0 +1,233 @@
|
||||
"use strict";
|
||||
|
||||
const server = createHttpServer();
|
||||
server.registerDirectory("/data/", do_get_file("data"));
|
||||
|
||||
const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
|
||||
|
||||
// ExtensionContent.jsm needs to know when it's running from xpcshell,
|
||||
// to use the right timeout for content scripts executed at document_idle.
|
||||
ExtensionTestUtils.mockAppInfo();
|
||||
|
||||
// Each of these tests do the following:
|
||||
// 1. Load document to create an extension context (instance of BaseContext).
|
||||
// 2. Get weak reference to that context.
|
||||
// 3. Unload the document.
|
||||
// 4. Force GC and check that the weak reference has been invalidated.
|
||||
|
||||
async function reloadTopContext(contentPage) {
|
||||
await contentPage.spawn(null, async () => {
|
||||
let {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm", {});
|
||||
let windowNukeObserved = TestUtils.topicObserved("inner-window-nuked");
|
||||
info(`Reloading top-level document`);
|
||||
this.content.location.reload();
|
||||
await windowNukeObserved;
|
||||
info(`Reloaded top-level document`);
|
||||
});
|
||||
}
|
||||
|
||||
async function assertContextReleased(contentPage, description) {
|
||||
await contentPage.spawn(description, async assertionDescription => {
|
||||
// Force GC, see https://searchfox.org/mozilla-central/rev/b0275bc977ad7fda615ef34b822bba938f2b16fd/testing/talos/talos/tests/devtools/addon/content/damp.js#84-98
|
||||
// and https://searchfox.org/mozilla-central/rev/33c21c060b7f3a52477a73d06ebcb2bf313c4431/xpcom/base/nsMemoryReporterManager.cpp#2574-2585,2591-2594
|
||||
let gcCount = 0;
|
||||
while (gcCount < 30 && this.contextWeakRef.get() !== null) {
|
||||
++gcCount;
|
||||
Cu.forceGC();
|
||||
Cu.forceCC();
|
||||
Cu.forceGC();
|
||||
await new Promise(resolve => this.content.setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
// The above loop needs to be repeated at most 3 times according to MinimizeMemoryUsage:
|
||||
// https://searchfox.org/mozilla-central/rev/6f86cc3479f80ace97f62634e2c82a483d1ede40/xpcom/base/nsMemoryReporterManager.cpp#2644-2647
|
||||
Assert.lessOrEqual(gcCount, 3, `Context should have been GCd within a few GC attempts.`);
|
||||
|
||||
// Each test will set this.contextWeakRef before unloading the document.
|
||||
Assert.ok(!this.contextWeakRef.get(), assertionDescription);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_ContentScriptContextChild_in_child_frame() {
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ["http://*/*/file_iframe.html"],
|
||||
js: ["content_script.js"],
|
||||
all_frames: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
files: {
|
||||
"content_script.js": "browser.test.sendMessage('contentScriptLoaded');",
|
||||
},
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
|
||||
let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_toplevel.html`);
|
||||
await extension.awaitMessage("contentScriptLoaded");
|
||||
|
||||
await contentPage.spawn(extension.id, async extensionId => {
|
||||
let {DocumentManager} = ChromeUtils.import("resource://gre/modules/ExtensionContent.jsm", {});
|
||||
let frame = this.content.document.querySelector("iframe[src*='file_iframe.html']");
|
||||
let context = DocumentManager.getContext(extensionId, frame.contentWindow);
|
||||
|
||||
Assert.ok(context, "Got content script context");
|
||||
|
||||
this.contextWeakRef = Cu.getWeakReference(context);
|
||||
frame.remove();
|
||||
});
|
||||
|
||||
await assertContextReleased(contentPage, "ContentScriptContextChild should have been released");
|
||||
|
||||
await contentPage.close();
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_ContentScriptContextChild_in_toplevel() {
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ["http://*/*/file_sample.html"],
|
||||
js: ["content_script.js"],
|
||||
all_frames: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
files: {
|
||||
"content_script.js": "browser.test.sendMessage('contentScriptLoaded');",
|
||||
},
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
|
||||
let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_sample.html`);
|
||||
await extension.awaitMessage("contentScriptLoaded");
|
||||
|
||||
await contentPage.spawn(extension.id, async extensionId => {
|
||||
let {DocumentManager} = ChromeUtils.import("resource://gre/modules/ExtensionContent.jsm", {});
|
||||
let context = DocumentManager.getContext(extensionId, this.content);
|
||||
|
||||
Assert.ok(context, "Got content script context");
|
||||
|
||||
this.contextWeakRef = Cu.getWeakReference(context);
|
||||
});
|
||||
|
||||
await reloadTopContext(contentPage);
|
||||
await extension.awaitMessage("contentScriptLoaded");
|
||||
await assertContextReleased(contentPage, "ContentScriptContextChild should have been released");
|
||||
|
||||
await contentPage.close();
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_ExtensionPageContextChild_in_child_frame() {
|
||||
let extensionData = {
|
||||
files: {
|
||||
"iframe.html": `
|
||||
<!DOCTYPE html><meta charset="utf8">
|
||||
<script src="script.js"></script>
|
||||
`,
|
||||
"toplevel.html": `
|
||||
<!DOCTYPE html><meta charset="utf8">
|
||||
<iframe src="iframe.html"></iframe>
|
||||
`,
|
||||
"script.js": "browser.test.sendMessage('extensionPageLoaded');",
|
||||
},
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
|
||||
let contentPage = await ExtensionTestUtils.loadContentPage(`moz-extension://${extension.uuid}/toplevel.html`, {
|
||||
extension,
|
||||
remote: extension.extension.remote,
|
||||
});
|
||||
await extension.awaitMessage("extensionPageLoaded");
|
||||
|
||||
await contentPage.spawn(extension.id, async extensionId => {
|
||||
let {ExtensionPageChild} = ChromeUtils.import("resource://gre/modules/ExtensionPageChild.jsm", {});
|
||||
|
||||
let frame = this.content.document.querySelector("iframe[src*='iframe.html']");
|
||||
let innerWindowID =
|
||||
frame.contentWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.currentInnerWindowID;
|
||||
let context = ExtensionPageChild.extensionContexts.get(innerWindowID);
|
||||
|
||||
Assert.ok(context, "Got extension page context for child frame");
|
||||
|
||||
this.contextWeakRef = Cu.getWeakReference(context);
|
||||
frame.remove();
|
||||
});
|
||||
|
||||
await assertContextReleased(contentPage, "ExtensionPageContextChild should have been released");
|
||||
|
||||
await contentPage.close();
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_ExtensionPageContextChild_in_toplevel() {
|
||||
let extensionData = {
|
||||
files: {
|
||||
"toplevel.html": `
|
||||
<!DOCTYPE html><meta charset="utf8">
|
||||
<script src="script.js"></script>
|
||||
`,
|
||||
"script.js": "browser.test.sendMessage('extensionPageLoaded');",
|
||||
},
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
|
||||
let contentPage = await ExtensionTestUtils.loadContentPage(`moz-extension://${extension.uuid}/toplevel.html`, {
|
||||
extension,
|
||||
remote: extension.extension.remote,
|
||||
});
|
||||
await extension.awaitMessage("extensionPageLoaded");
|
||||
|
||||
await contentPage.spawn(extension.id, async extensionId => {
|
||||
let {ExtensionPageChild} = ChromeUtils.import("resource://gre/modules/ExtensionPageChild.jsm", {});
|
||||
|
||||
let innerWindowID = this.content
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.currentInnerWindowID;
|
||||
let context = ExtensionPageChild.extensionContexts.get(innerWindowID);
|
||||
|
||||
Assert.ok(context, "Got extension page context for top-level document");
|
||||
|
||||
this.contextWeakRef = Cu.getWeakReference(context);
|
||||
});
|
||||
|
||||
await reloadTopContext(contentPage);
|
||||
await extension.awaitMessage("extensionPageLoaded");
|
||||
// For some unknown reason, the context cannot forcidbly be released by the
|
||||
// garbage collector unless we wait for a short while.
|
||||
await contentPage.spawn(null, async () => {
|
||||
let start = Date.now();
|
||||
// The treshold was found after running this subtest only, 300 times
|
||||
// in a release build (100 of xpcshell, xpcshell-e10s and xpcshell-remote).
|
||||
// With treshold 8, almost half of the tests complete after a 17-18 ms delay.
|
||||
// With treshold 7, over half of the tests complete after a 13-14 ms delay,
|
||||
// with 12 failures in 300 tests runs.
|
||||
// Let's double that number to have a safety margin.
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
await new Promise(resolve => this.content.setTimeout(resolve, 0));
|
||||
}
|
||||
info(`Going to GC after waiting for ${Date.now() - start} ms.`);
|
||||
});
|
||||
await assertContextReleased(contentPage, "ExtensionPageContextChild should have been released");
|
||||
|
||||
await contentPage.close();
|
||||
await extension.unload();
|
||||
});
|
@ -8,6 +8,7 @@ skip-if = debug # Bug 1407501
|
||||
skip-if = (os == "android" && debug) || (os == "win" && debug) # Windows: Bug 1438796
|
||||
[test_ext_contentscript_xrays.js]
|
||||
[test_ext_contentScripts_register.js]
|
||||
[test_ext_contexts_gc.js]
|
||||
skip-if = os == "android"
|
||||
[test_ext_adoption_with_xrays.js]
|
||||
[test_ext_shadowdom.js]
|
||||
|
@ -47,6 +47,7 @@ add_task(async function test_urlbar_new_URL() {
|
||||
add_task(async function test_urlbar_fragment_enter() {
|
||||
await withTestPage(function(aBrowser) {
|
||||
gURLBar.focus();
|
||||
gURLBar.select();
|
||||
EventUtils.synthesizeKey("KEY_ArrowRight");
|
||||
EventUtils.sendString("#fragment");
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
|
@ -192,5 +192,5 @@ nsXRemoteService::EnsureAtoms(void)
|
||||
sMozUserAtom = XAtoms[i++];
|
||||
sMozProfileAtom = XAtoms[i++];
|
||||
sMozProgramAtom = XAtoms[i++];
|
||||
sMozCommandLineAtom = XAtoms[i++];
|
||||
sMozCommandLineAtom = XAtoms[i];
|
||||
}
|
||||
|
@ -159,5 +159,13 @@ KeyedStackCapturer::Clear()
|
||||
mStacks.Clear();
|
||||
}
|
||||
|
||||
size_t
|
||||
KeyedStackCapturer::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
|
||||
size_t n = 0;
|
||||
n += mStackInfos.SizeOfExcludingThis(aMallocSizeOf);
|
||||
n += mStacks.SizeOfExcludingThis();
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
@ -46,6 +46,8 @@ public:
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Describes how often a stack was captured.
|
||||
|
@ -363,24 +363,6 @@ browser.usage:
|
||||
- 'main'
|
||||
- 'content'
|
||||
|
||||
# The following section contains the session restore scalars.
|
||||
browser.session.restore:
|
||||
worker_restart_count:
|
||||
bug_numbers:
|
||||
- 1402267
|
||||
description: >
|
||||
A counter incremented every time the SessionFile worker is restarted due
|
||||
to too many failures, as defined in the browser.sessionstore.max_write_failures
|
||||
pref.
|
||||
expires: "64"
|
||||
kind: uint
|
||||
notification_emails:
|
||||
- mdeboer@mozilla.com
|
||||
- session-restore-telemetry-alerts@mozilla.com
|
||||
release_channel_collection: opt-out
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
extensions.updates:
|
||||
rdf:
|
||||
bug_numbers:
|
||||
|
@ -148,7 +148,6 @@ public:
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
static void DoStackCapture(const nsACString& aKey);
|
||||
#endif
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
struct Stat {
|
||||
uint32_t hitCount;
|
||||
uint32_t totalTime;
|
||||
@ -220,10 +219,70 @@ NS_IMETHODIMP
|
||||
TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
|
||||
nsISupports* aData, bool aAnonymize)
|
||||
{
|
||||
MOZ_COLLECT_REPORT(
|
||||
"explicit/telemetry", KIND_HEAP, UNITS_BYTES,
|
||||
SizeOfIncludingThis(TelemetryMallocSizeOf),
|
||||
"Memory used by the telemetry system.");
|
||||
mozilla::MallocSizeOf aMallocSizeOf = TelemetryMallocSizeOf;
|
||||
|
||||
#define COLLECT_REPORT(name, size, desc) \
|
||||
MOZ_COLLECT_REPORT(name, KIND_HEAP, UNITS_BYTES, size, desc)
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/impl", aMallocSizeOf(this),
|
||||
"Memory used by the Telemetry core implemenation");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/histogram/shallow",
|
||||
TelemetryHistogram::GetMapShallowSizesOfExcludingThis(aMallocSizeOf),
|
||||
"Memory used by the Telemetry Histogram implementation");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/scalar/shallow",
|
||||
TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf),
|
||||
"Memory used by the Telemetry Scalar implemenation");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/WebRTC",
|
||||
mWebrtcTelemetry.SizeOfExcludingThis(aMallocSizeOf),
|
||||
"Memory used by WebRTC Telemetry");
|
||||
|
||||
{ // Scope for mHashMutex lock
|
||||
MutexAutoLock lock(mHashMutex);
|
||||
COLLECT_REPORT("explicit/telemetry/PrivateSQL",
|
||||
mPrivateSQL.SizeOfExcludingThis(aMallocSizeOf),
|
||||
"Memory used by the PrivateSQL Telemetry");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/SanitizedSQL",
|
||||
mSanitizedSQL.SizeOfExcludingThis(aMallocSizeOf),
|
||||
"Memory used by the SanitizedSQL Telemetry");
|
||||
}
|
||||
|
||||
if (sTelemetryIOObserver) {
|
||||
COLLECT_REPORT("explicit/telemetry/IOObserver",
|
||||
sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf),
|
||||
"Memory used by the Telemetry IO Observer");
|
||||
}
|
||||
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
COLLECT_REPORT("explicit/telemetry/StackCapturer",
|
||||
mStackCapturer.SizeOfExcludingThis(aMallocSizeOf),
|
||||
"Memory used by the Telemetry Stack capturer");
|
||||
#endif
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/LateWritesStacks",
|
||||
mLateWritesStacks.SizeOfExcludingThis(),
|
||||
"Memory used by the Telemetry LateWrites Stack capturer");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/Callbacks",
|
||||
mCallbacks.ShallowSizeOfExcludingThis(aMallocSizeOf),
|
||||
"Memory used by the Telemetry Callbacks array (shallow)");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/histogram/data",
|
||||
TelemetryHistogram::GetHistogramSizesOfIncludingThis(aMallocSizeOf),
|
||||
"Memory used by Telemetry Histogram data");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/scalar/data",
|
||||
TelemetryScalar::GetScalarSizesOfIncludingThis(aMallocSizeOf),
|
||||
"Memory used by Telemetry Scalar data");
|
||||
|
||||
COLLECT_REPORT("explicit/telemetry/event/data",
|
||||
TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf),
|
||||
"Memory used by Telemetry Event data");
|
||||
|
||||
#undef COLLECT_REPORT
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1741,33 +1800,6 @@ TelemetryImpl::FlushBatchedChildTelemetry()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
size_t
|
||||
TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
|
||||
{
|
||||
size_t n = aMallocSizeOf(this);
|
||||
|
||||
n += TelemetryHistogram::GetMapShallowSizesOfExcludingThis(aMallocSizeOf);
|
||||
n += TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf);
|
||||
n += mWebrtcTelemetry.SizeOfExcludingThis(aMallocSizeOf);
|
||||
{ // Scope for mHashMutex lock
|
||||
MutexAutoLock lock(mHashMutex);
|
||||
n += mPrivateSQL.SizeOfExcludingThis(aMallocSizeOf);
|
||||
n += mSanitizedSQL.SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
// It's a bit gross that we measure this other stuff that lives outside of
|
||||
// TelemetryImpl... oh well.
|
||||
if (sTelemetryIOObserver) {
|
||||
n += sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
n += TelemetryHistogram::GetHistogramSizesofIncludingThis(aMallocSizeOf);
|
||||
n += TelemetryScalar::GetScalarSizesOfIncludingThis(aMallocSizeOf);
|
||||
n += TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
@ -200,6 +200,8 @@ public:
|
||||
|
||||
bool IsExpired() const { return mIsExpired; }
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
|
||||
private:
|
||||
typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
|
||||
typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
|
||||
@ -1038,6 +1040,15 @@ KeyedHistogram::Clear()
|
||||
mHistogramMap.Clear();
|
||||
}
|
||||
|
||||
size_t
|
||||
KeyedHistogram::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
|
||||
{
|
||||
size_t n = 0;
|
||||
n += aMallocSizeOf(this);
|
||||
n += mHistogramMap.SizeOfIncludingThis(aMallocSizeOf);
|
||||
return n;
|
||||
}
|
||||
|
||||
nsresult
|
||||
KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args)
|
||||
{
|
||||
@ -2560,12 +2571,44 @@ TelemetryHistogram::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf
|
||||
}
|
||||
|
||||
size_t
|
||||
TelemetryHistogram::GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf
|
||||
TelemetryHistogram::GetHistogramSizesOfIncludingThis(mozilla::MallocSizeOf
|
||||
aMallocSizeOf)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
||||
// TODO
|
||||
return 0;
|
||||
|
||||
size_t n = 0;
|
||||
|
||||
// If we allocated the array, let's count the number of pointers in there and
|
||||
// each entry's size.
|
||||
if (gKeyedHistogramStorage) {
|
||||
n += HistogramCount * size_t(ProcessID::Count) * sizeof(KeyedHistogram*);
|
||||
for (size_t i = 0; i < HistogramCount * size_t(ProcessID::Count); ++i) {
|
||||
if (gKeyedHistogramStorage[i] && gKeyedHistogramStorage[i] != gExpiredKeyedHistogram) {
|
||||
n += gKeyedHistogramStorage[i]->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we allocated the array, let's count the number of pointers in there.
|
||||
if (gHistogramStorage) {
|
||||
n += HistogramCount * size_t(ProcessID::Count) * sizeof(Histogram*);
|
||||
for (size_t i = 0; i < HistogramCount * size_t(ProcessID::Count); ++i) {
|
||||
if (gHistogramStorage[i] && gHistogramStorage[i] != gExpiredHistogram) {
|
||||
n += gHistogramStorage[i]->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We only allocate the expired (keyed) histogram once.
|
||||
if (gExpiredKeyedHistogram) {
|
||||
n += gExpiredKeyedHistogram->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
if (gExpiredHistogram) {
|
||||
n += gExpiredHistogram->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -80,7 +80,7 @@ size_t
|
||||
GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
|
||||
size_t
|
||||
GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
GetHistogramSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
|
||||
// These functions are only meant to be used for GeckoView persistence.
|
||||
// They are responsible for updating in-memory probes with the data persisted
|
||||
|
@ -316,3 +316,59 @@ TEST_F(TelemetryTestFixture, ScalarEventSummary_Dynamic) {
|
||||
// which all end up in the same place.
|
||||
CheckKeyedUintScalar(kScalarName, kLongestEvent, cx.GetJSContext(), scalarsSnapshot, 2);
|
||||
}
|
||||
|
||||
TEST_F(TelemetryTestFixture, WrongScalarOperator) {
|
||||
AutoJSContextWithGlobal cx(mCleanGlobal);
|
||||
|
||||
// Make sure we don't get scalars from other tests.
|
||||
Unused << mTelemetry->ClearScalars();
|
||||
|
||||
const uint32_t expectedValue = 1172015;
|
||||
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, expectedValue);
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, NS_LITERAL_STRING(EXPECTED_STRING));
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, true);
|
||||
|
||||
TelemetryScalar::DeserializationStarted();
|
||||
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, 1447);
|
||||
Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, 1447);
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, true);
|
||||
TelemetryScalar::ApplyPendingOperations();
|
||||
|
||||
JS::RootedValue scalarsSnapshot(cx.GetJSContext());
|
||||
GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
|
||||
CheckStringScalar("telemetry.test.string_kind", cx.GetJSContext(), scalarsSnapshot, EXPECTED_STRING);
|
||||
CheckBoolScalar("telemetry.test.boolean_kind", cx.GetJSContext(), scalarsSnapshot, true);
|
||||
CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), scalarsSnapshot, expectedValue);
|
||||
}
|
||||
|
||||
TEST_F(TelemetryTestFixture, WrongKeyedScalarOperator) {
|
||||
AutoJSContextWithGlobal cx(mCleanGlobal);
|
||||
|
||||
// Make sure we don't get scalars from other tests.
|
||||
Unused << mTelemetry->ClearScalars();
|
||||
|
||||
const uint32_t kExpectedUint = 1172017;
|
||||
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
|
||||
NS_LITERAL_STRING("key1"), kExpectedUint);
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
|
||||
NS_LITERAL_STRING("key2"), true);
|
||||
|
||||
TelemetryScalar::DeserializationStarted();
|
||||
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
|
||||
NS_LITERAL_STRING("key1"), false);
|
||||
Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
|
||||
NS_LITERAL_STRING("key2"), static_cast<uint32_t>(13));
|
||||
|
||||
TelemetryScalar::ApplyPendingOperations();
|
||||
|
||||
JS::RootedValue scalarsSnapshot(cx.GetJSContext());
|
||||
GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
|
||||
CheckKeyedUintScalar("telemetry.test.keyed_unsigned_int", "key1",
|
||||
cx.GetJSContext(), scalarsSnapshot, kExpectedUint);
|
||||
CheckKeyedBoolScalar("telemetry.test.keyed_boolean_kind", "key2",
|
||||
cx.GetJSContext(), scalarsSnapshot, true);
|
||||
}
|
||||
|
@ -246,8 +246,10 @@ Classifier::Open(nsIFile& aCacheDirectory)
|
||||
void
|
||||
Classifier::Close()
|
||||
{
|
||||
// Close will be called by PreShutdown, so it is important to note that
|
||||
// Close will be called by PreShutdown, set |mUpdateInterrupted| here
|
||||
// to abort an ongoing update if possible. It is important to note that
|
||||
// things put here should not affect an ongoing update thread.
|
||||
mUpdateInterrupted = true;
|
||||
mIsClosed = true;
|
||||
DropStores();
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ const PAGE_A1_A2_B3 = "https://example.com/browser/toolkit/content/tests/browser
|
||||
|
||||
function setup_test_preference() {
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
["media.autoplay.enabled", false],
|
||||
["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true]
|
||||
]});
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ function setup_test_preference(enableUserGesture) {
|
||||
let state = enableUserGesture ? "enable" : "disable";
|
||||
info(`- set pref : ${state} user gesture -`);
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
["media.autoplay.enabled", false],
|
||||
["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", enableUserGesture]
|
||||
]});
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ const VIDEO_PAGE = "https://example.com/browser/toolkit/content/tests/browser/fi
|
||||
|
||||
add_task(() => {
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
["media.autoplay.enabled", false],
|
||||
["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.PROMPT],
|
||||
["media.autoplay.enabled.user-gestures-needed", true],
|
||||
["media.autoplay.ask-permission", true],
|
||||
["media.autoplay.block-event.enabled", true],
|
||||
|
@ -16,7 +16,7 @@ var UserGestureTests = [
|
||||
|
||||
function setup_test_preference() {
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
["media.autoplay.enabled", false],
|
||||
["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
|
||||
["media.autoplay.enabled.user-gestures-needed", true],
|
||||
["media.navigator.permission.fake", true]
|
||||
]});
|
||||
|
@ -84,7 +84,7 @@ add_task(async function block_autoplay_media() {
|
||||
// a new document being loaded into the tab; the new document should have
|
||||
// to satisfy the autoplay requirements on its own.
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["media.autoplay.enabled", false],
|
||||
["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.PROMPT],
|
||||
["media.autoplay.enabled.user-gestures-needed", true],
|
||||
["media.autoplay.ask-permission", true],
|
||||
]});
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "mozilla/XREAppData.h"
|
||||
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "prtime.h"
|
||||
|
||||
using namespace mozilla;
|
||||
@ -81,7 +82,7 @@ ProfileResetCleanup(nsIToolkitProfile* aOldProfile)
|
||||
if (!sbs) return NS_ERROR_FAILURE;
|
||||
|
||||
nsCOMPtr<nsIStringBundle> sb;
|
||||
rv = sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
|
||||
Unused << sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
|
||||
if (!sb) return NS_ERROR_FAILURE;
|
||||
|
||||
NS_ConvertUTF8toUTF16 appName(gAppData->name);
|
||||
@ -92,6 +93,7 @@ ProfileResetCleanup(nsIToolkitProfile* aOldProfile)
|
||||
static const char* kResetBackupDirectory = "resetBackupDirectory";
|
||||
rv = sb->FormatStringFromName(kResetBackupDirectory, params, 2,
|
||||
resetBackupDirectoryName);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
// Get info to copy the old root profile dir to the desktop as a backup.
|
||||
nsCOMPtr<nsIFile> backupDest, containerDest, profileDest;
|
||||
|
@ -222,13 +222,12 @@ nsOSHelperAppService::LookUpTypeAndDescription(const nsAString& aFileExtension,
|
||||
bool aUserData) {
|
||||
LOG(("-- LookUpTypeAndDescription for extension '%s'\n",
|
||||
NS_LossyConvertUTF16toASCII(aFileExtension).get()));
|
||||
nsresult rv = NS_OK;
|
||||
nsAutoString mimeFileName;
|
||||
|
||||
const char* filenamePref = aUserData ?
|
||||
"helpers.private_mime_types_file" : "helpers.global_mime_types_file";
|
||||
|
||||
rv = GetFileLocation(filenamePref, nullptr, mimeFileName);
|
||||
nsresult rv = GetFileLocation(filenamePref, nullptr, mimeFileName);
|
||||
if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
|
||||
rv = GetTypeAndDescriptionFromMimetypesFile(mimeFileName,
|
||||
aFileExtension,
|
||||
@ -315,15 +314,15 @@ nsOSHelperAppService::GetTypeAndDescriptionFromMimetypesFile(const nsAString& aF
|
||||
NS_LossyConvertUTF16toASCII(aFilename).get()));
|
||||
LOG(("Using extension '%s'\n",
|
||||
NS_LossyConvertUTF16toASCII(aFileExtension).get()));
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIFileInputStream> mimeFile;
|
||||
nsCOMPtr<nsILineInputStream> mimeTypes;
|
||||
bool netscapeFormat;
|
||||
nsAutoString buf;
|
||||
nsAutoCString cBuf;
|
||||
bool more = false;
|
||||
rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), getter_AddRefs(mimeTypes),
|
||||
cBuf, &netscapeFormat, &more);
|
||||
nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile),
|
||||
getter_AddRefs(mimeTypes),
|
||||
cBuf, &netscapeFormat, &more);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
@ -438,10 +437,10 @@ nsOSHelperAppService::LookUpExtensionsAndDescription(const nsAString& aMajorType
|
||||
LOG(("-- LookUpExtensionsAndDescription for type '%s/%s'\n",
|
||||
NS_LossyConvertUTF16toASCII(aMajorType).get(),
|
||||
NS_LossyConvertUTF16toASCII(aMinorType).get()));
|
||||
nsresult rv = NS_OK;
|
||||
nsAutoString mimeFileName;
|
||||
|
||||
rv = GetFileLocation("helpers.private_mime_types_file", nullptr, mimeFileName);
|
||||
nsresult rv = GetFileLocation("helpers.private_mime_types_file",
|
||||
nullptr, mimeFileName);
|
||||
if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
|
||||
rv = GetExtensionsAndDescriptionFromMimetypesFile(mimeFileName,
|
||||
aMajorType,
|
||||
@ -482,17 +481,15 @@ nsOSHelperAppService::GetExtensionsAndDescriptionFromMimetypesFile(const nsAStri
|
||||
LOG(("Using type '%s/%s'\n",
|
||||
NS_LossyConvertUTF16toASCII(aMajorType).get(),
|
||||
NS_LossyConvertUTF16toASCII(aMinorType).get()));
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIFileInputStream> mimeFile;
|
||||
nsCOMPtr<nsILineInputStream> mimeTypes;
|
||||
bool netscapeFormat;
|
||||
nsAutoCString cBuf;
|
||||
nsAutoString buf;
|
||||
bool more = false;
|
||||
rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), getter_AddRefs(mimeTypes),
|
||||
cBuf, &netscapeFormat, &more);
|
||||
|
||||
nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile),
|
||||
getter_AddRefs(mimeTypes),
|
||||
cBuf, &netscapeFormat, &more);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
@ -887,7 +884,6 @@ nsOSHelperAppService::DoLookUpHandlerAndDescription(const nsAString& aMajorType,
|
||||
LOG(("-- LookUpHandlerAndDescription for type '%s/%s'\n",
|
||||
NS_LossyConvertUTF16toASCII(aMajorType).get(),
|
||||
NS_LossyConvertUTF16toASCII(aMinorType).get()));
|
||||
nsresult rv = NS_OK;
|
||||
nsAutoString mailcapFileName;
|
||||
|
||||
const char * filenamePref = aUserData ?
|
||||
@ -895,7 +891,7 @@ nsOSHelperAppService::DoLookUpHandlerAndDescription(const nsAString& aMajorType,
|
||||
const char * filenameEnvVar = aUserData ?
|
||||
"PERSONAL_MAILCAP" : "MAILCAP";
|
||||
|
||||
rv = GetFileLocation(filenamePref, filenameEnvVar, mailcapFileName);
|
||||
nsresult rv = GetFileLocation(filenamePref, filenameEnvVar, mailcapFileName);
|
||||
if (NS_SUCCEEDED(rv) && !mailcapFileName.IsEmpty()) {
|
||||
rv = GetHandlerAndDescriptionFromMailcapFile(mailcapFileName,
|
||||
aMajorType,
|
||||
|
@ -22,6 +22,16 @@ NSArray *filteredProviderNames = @[
|
||||
NSString* const remindersServiceName =
|
||||
@"com.apple.reminders.RemindersShareExtension";
|
||||
|
||||
// These are some undocumented constants also used by Safari
|
||||
// to let us open the preferences window
|
||||
NSString* const extensionPrefPanePath =
|
||||
@"/System/Library/PreferencePanes/Extensions.prefPane";
|
||||
const UInt32 openSharingSubpaneDescriptorType = 'ptru';
|
||||
NSString* const openSharingSubpaneActionKey = @"action";
|
||||
NSString* const openSharingSubpaneActionValue = @"revealExtensionPoint";
|
||||
NSString* const openSharingSubpaneProtocolKey = @"protocol";
|
||||
NSString* const openSharingSubpaneProtocolValue = @"com.apple.share-services";
|
||||
|
||||
// Expose the id so we can pass reference through to JS and back
|
||||
@interface NSSharingService (ExposeName)
|
||||
- (id)name;
|
||||
@ -128,6 +138,40 @@ nsMacSharingService::GetSharingProviders(const nsAString& aPageUrl,
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMacSharingService::OpenSharingPreferences()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
NSURL* prefPaneURL =
|
||||
[NSURL fileURLWithPath:extensionPrefPanePath isDirectory:YES];
|
||||
NSDictionary* args = @{
|
||||
openSharingSubpaneActionKey: openSharingSubpaneActionValue,
|
||||
openSharingSubpaneProtocolKey: openSharingSubpaneProtocolValue
|
||||
};
|
||||
NSData* data =
|
||||
[NSPropertyListSerialization
|
||||
dataWithPropertyList:args
|
||||
format:NSPropertyListXMLFormat_v1_0
|
||||
options:0
|
||||
error:nil];
|
||||
NSAppleEventDescriptor* descriptor =
|
||||
[[NSAppleEventDescriptor alloc]
|
||||
initWithDescriptorType:openSharingSubpaneDescriptorType
|
||||
data:data];
|
||||
|
||||
[[NSWorkspace sharedWorkspace] openURLs:@[ prefPaneURL ]
|
||||
withAppBundleIdentifier:nil
|
||||
options:NSWorkspaceLaunchAsync
|
||||
additionalEventParamDescriptor:descriptor
|
||||
launchIdentifiers:NULL];
|
||||
|
||||
[descriptor release];
|
||||
|
||||
return NS_OK;
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMacSharingService::ShareUrl(const nsAString& aServiceName,
|
||||
const nsAString& aPageUrl,
|
||||
|
@ -22,4 +22,9 @@ interface nsIMacSharingService : nsISupports
|
||||
void shareUrl(in AString serviceName,
|
||||
in AString pageUrl,
|
||||
in AString pageTitle);
|
||||
|
||||
/**
|
||||
* Open the MacOS preferences window to the sharing panel
|
||||
*/
|
||||
void openSharingPreferences();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user