зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to m-i
This commit is contained in:
Коммит
b3c440155b
|
@ -536,6 +536,9 @@ pref("privacy.panicButton.enabled", true);
|
|||
|
||||
pref("privacy.firstparty.isolate", false);
|
||||
|
||||
// Time until temporary permissions expire, in ms
|
||||
pref("privacy.temporary_permission_expire_time_ms", 3600000);
|
||||
|
||||
pref("network.proxy.share_proxy_settings", false); // use the same proxy settings for all protocols
|
||||
|
||||
// simple gestures support
|
||||
|
|
|
@ -1127,6 +1127,10 @@ var gBrowserInit = {
|
|||
gIdentityHandler.refreshForInsecureLoginForms();
|
||||
});
|
||||
|
||||
gBrowser.addEventListener("PermissionStateChange", function() {
|
||||
gIdentityHandler.refreshIdentityBlock();
|
||||
});
|
||||
|
||||
let uriToLoad = this._getUriToLoad();
|
||||
if (uriToLoad && uriToLoad != "about:blank") {
|
||||
if (uriToLoad instanceof Ci.nsIArray) {
|
||||
|
@ -3225,6 +3229,10 @@ function BrowserReloadWithFlags(reloadFlags) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Reset temporary permissions on the current tab. This is done here
|
||||
// because we only want to reset permissions on user reload.
|
||||
SitePermissions.clearTemporaryPermissions(gBrowser.selectedBrowser);
|
||||
|
||||
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
|
@ -3327,7 +3335,7 @@ var PrintPreviewListener = {
|
|||
|
||||
getPrintPreviewBrowser() {
|
||||
if (!this._printPreviewTab) {
|
||||
let browser = gBrowser.selectedTab.linkedBrowser;
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let preferredRemoteType = browser.remoteType;
|
||||
this._tabBeforePrintPreview = gBrowser.selectedTab;
|
||||
this._printPreviewTab = gBrowser.loadOneTab("about:blank",
|
||||
|
@ -4643,8 +4651,12 @@ var XULBrowserWindow = {
|
|||
let uri = gBrowser.currentURI;
|
||||
let spec = uri.spec;
|
||||
if (this._state == aState &&
|
||||
this._lastLocation == spec)
|
||||
this._lastLocation == spec) {
|
||||
// Switching to a tab of the same URL doesn't change most security
|
||||
// information, but tab specific permissions may be different.
|
||||
gIdentityHandler.refreshIdentityBlock();
|
||||
return;
|
||||
}
|
||||
this._state = aState;
|
||||
this._lastLocation = spec;
|
||||
|
||||
|
@ -7021,16 +7033,16 @@ var gIdentityHandler = {
|
|||
let hasGrantedPermissions = false;
|
||||
|
||||
// show permission icons
|
||||
for (let permission of SitePermissions.getAllByURI(this._uri)) {
|
||||
if (permission.state === SitePermissions.BLOCK) {
|
||||
let permissions = SitePermissions.getAllForBrowser(gBrowser.selectedBrowser);
|
||||
for (let permission of permissions) {
|
||||
if (permission.state == SitePermissions.BLOCK) {
|
||||
|
||||
let icon = permissionAnchors[permission.id];
|
||||
if (icon) {
|
||||
icon.setAttribute("showing", "true");
|
||||
}
|
||||
|
||||
} else if (permission.state === SitePermissions.ALLOW ||
|
||||
permission.state === SitePermissions.SESSION) {
|
||||
} else if (permission.state != SitePermissions.UNKNOWN) {
|
||||
hasGrantedPermissions = true;
|
||||
}
|
||||
}
|
||||
|
@ -7365,9 +7377,9 @@ var gIdentityHandler = {
|
|||
while (this._permissionList.hasChildNodes())
|
||||
this._permissionList.removeChild(this._permissionList.lastChild);
|
||||
|
||||
let uri = gBrowser.currentURI;
|
||||
let permissions =
|
||||
SitePermissions.getAllPermissionDetailsForBrowser(gBrowser.selectedBrowser);
|
||||
|
||||
let permissions = SitePermissions.getPermissionDetailsByURI(uri);
|
||||
if (this._sharingState) {
|
||||
// If WebRTC device or screen permissions are in use, we need to find
|
||||
// the associated permission item to set the inUse field to true.
|
||||
|
@ -7385,7 +7397,8 @@ var gIdentityHandler = {
|
|||
// If the permission item we were looking for doesn't exist,
|
||||
// the user has temporarily allowed sharing and we need to add
|
||||
// an item in the permissions array to reflect this.
|
||||
let permission = SitePermissions.getPermissionItem(id);
|
||||
let permission =
|
||||
SitePermissions.getPermissionDetails(id, SitePermissions.SCOPE_REQUEST);
|
||||
permission.inUse = true;
|
||||
permissions.push(permission);
|
||||
}
|
||||
|
@ -7441,14 +7454,21 @@ var gIdentityHandler = {
|
|||
let stateLabel = document.createElement("label");
|
||||
stateLabel.setAttribute("flex", "1");
|
||||
stateLabel.setAttribute("class", "identity-popup-permission-state-label");
|
||||
stateLabel.textContent = SitePermissions.getStateLabel(
|
||||
aPermission.id, aPermission.state, aPermission.inUse || false);
|
||||
let {state, scope} = aPermission;
|
||||
// If the user did not permanently allow this device but it is currently
|
||||
// used, set the variables to display a "temporarily allowed" info.
|
||||
if (state != SitePermissions.ALLOW && aPermission.inUse) {
|
||||
state = SitePermissions.ALLOW;
|
||||
scope = SitePermissions.SCOPE_REQUEST;
|
||||
}
|
||||
stateLabel.textContent = SitePermissions.getStateLabel(state, scope);
|
||||
|
||||
let button = document.createElement("button");
|
||||
button.setAttribute("class", "identity-popup-permission-remove-button");
|
||||
let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
|
||||
button.setAttribute("tooltiptext", tooltiptext);
|
||||
button.addEventListener("command", () => {
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
// Only resize the window if the reload hint was previously hidden.
|
||||
this._handleHeightChange(() => this._permissionList.removeChild(container),
|
||||
this._permissionReloadHint.hasAttribute("hidden"));
|
||||
|
@ -7461,21 +7481,24 @@ var gIdentityHandler = {
|
|||
// If we set persistent permissions or the sharing has
|
||||
// started due to existing persistent permissions, we need
|
||||
// to handle removing these even for frames with different hostnames.
|
||||
let uris = gBrowser.selectedBrowser._devicePermissionURIs || [];
|
||||
let uris = browser._devicePermissionURIs || [];
|
||||
for (let uri of uris) {
|
||||
// It's not possible to stop sharing one of camera/microphone
|
||||
// without the other.
|
||||
for (let id of ["camera", "microphone"]) {
|
||||
if (this._sharingState[id] &&
|
||||
SitePermissions.get(uri, id) == SitePermissions.ALLOW)
|
||||
SitePermissions.remove(uri, id);
|
||||
if (this._sharingState[id]) {
|
||||
let perm = SitePermissions.get(uri, id);
|
||||
if (perm.state == SitePermissions.ALLOW &&
|
||||
perm.scope == SitePermissions.SCOPE_PERSISTENT) {
|
||||
SitePermissions.remove(uri, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
mm.sendAsyncMessage("webrtc:StopSharing", windowId);
|
||||
browser.messageManager.sendAsyncMessage("webrtc:StopSharing", windowId);
|
||||
}
|
||||
SitePermissions.remove(gBrowser.currentURI, aPermission.id);
|
||||
SitePermissions.remove(gBrowser.currentURI, aPermission.id, browser);
|
||||
|
||||
this._permissionReloadHint.removeAttribute("hidden");
|
||||
|
||||
|
@ -7483,15 +7506,21 @@ var gIdentityHandler = {
|
|||
let histogram = Services.telemetry.getKeyedHistogramById("WEB_PERMISSION_CLEARED");
|
||||
|
||||
let permissionType = 0;
|
||||
if (aPermission.state == SitePermissions.ALLOW) {
|
||||
if (aPermission.state == SitePermissions.ALLOW &&
|
||||
aPermission.scope == SitePermissions.SCOPE_PERSISTENT) {
|
||||
// 1 : clear permanently allowed permission
|
||||
permissionType = 1;
|
||||
} else if (aPermission.state == SitePermissions.BLOCK) {
|
||||
} else if (aPermission.state == SitePermissions.BLOCK &&
|
||||
aPermission.scope == SitePermissions.SCOPE_PERSISTENT) {
|
||||
// 2 : clear permanently blocked permission
|
||||
permissionType = 2;
|
||||
} else if (aPermission.state == SitePermissions.ALLOW) {
|
||||
// 3 : clear temporary allowed permission
|
||||
permissionType = 3;
|
||||
} else if (aPermission.state == SitePermissions.BLOCK) {
|
||||
// 4 : clear temporary blocked permission
|
||||
permissionType = 4;
|
||||
}
|
||||
// 3 : TODO clear temporary allowed permission
|
||||
// 4 : TODO clear temporary blocked permission
|
||||
|
||||
histogram.add("(all)", permissionType);
|
||||
histogram.add(aPermission.id, permissionType);
|
||||
|
|
|
@ -73,17 +73,17 @@ function initRow(aPartId) {
|
|||
|
||||
var checkbox = document.getElementById(aPartId + "Def");
|
||||
var command = document.getElementById("cmd_" + aPartId + "Toggle");
|
||||
var perm = SitePermissions.get(gPermURI, aPartId);
|
||||
var {state} = SitePermissions.get(gPermURI, aPartId);
|
||||
|
||||
if (perm) {
|
||||
if (state != SitePermissions.UNKNOWN) {
|
||||
checkbox.checked = false;
|
||||
command.removeAttribute("disabled");
|
||||
} else {
|
||||
checkbox.checked = true;
|
||||
command.setAttribute("disabled", "true");
|
||||
perm = SitePermissions.getDefault(aPartId);
|
||||
state = SitePermissions.getDefault(aPartId);
|
||||
}
|
||||
setRadioState(aPartId, perm);
|
||||
setRadioState(aPartId, state);
|
||||
|
||||
if (aPartId == "indexedDB") {
|
||||
initIndexedDBRow();
|
||||
|
@ -135,7 +135,7 @@ function createRow(aPartId) {
|
|||
for (let state of SitePermissions.getAvailableStates(aPartId)) {
|
||||
let radio = document.createElement("radio");
|
||||
radio.setAttribute("id", aPartId + "#" + state);
|
||||
radio.setAttribute("label", SitePermissions.getStateLabel(aPartId, state));
|
||||
radio.setAttribute("label", SitePermissions.getStateLabel(state));
|
||||
radio.setAttribute("command", commandId);
|
||||
radiogroup.appendChild(radio);
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ function setPluginsRadioState() {
|
|||
if (permissionEntry.hasAttribute("permString")) {
|
||||
let permString = permissionEntry.getAttribute("permString");
|
||||
let permission = SitePermissions.get(gPermURI, permString);
|
||||
setRadioState(permString, permission);
|
||||
setRadioState(permString, permission.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2872,6 +2872,8 @@
|
|||
webrtcUI.swapBrowserForNotification(otherBrowser, ourBrowser);
|
||||
}
|
||||
|
||||
SitePermissions.copyTemporaryPermissions(otherBrowser, ourBrowser);
|
||||
|
||||
// If the other tab is pending (i.e. has not been restored, yet)
|
||||
// then do not switch docShells but retrieve the other tab's state
|
||||
// and apply it to our tab.
|
||||
|
|
|
@ -420,6 +420,11 @@ support-files =
|
|||
close_beforeunload.html
|
||||
[browser_tabs_isActive.js]
|
||||
[browser_tabs_owner.js]
|
||||
[browser_temporary_permissions.js]
|
||||
support-files =
|
||||
permissions.html
|
||||
temporary_permissions_subframe.html
|
||||
[browser_temporary_permissions_navigation.js]
|
||||
[browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
|
||||
run-if = e10s
|
||||
[browser_trackingUI_1.js]
|
||||
|
|
|
@ -90,7 +90,7 @@ add_task(function* testIdentityIcon() {
|
|||
ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
|
||||
"identity-box doesn't signal granted permissions");
|
||||
|
||||
SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.SESSION);
|
||||
SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.ALLOW_COOKIES_FOR_SESSION);
|
||||
|
||||
ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
|
||||
"identity-box signals granted permissions");
|
||||
|
@ -179,7 +179,6 @@ add_task(function* testPermissionIcons() {
|
|||
|
||||
SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
|
||||
SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.BLOCK);
|
||||
SitePermissions.set(gBrowser.currentURI, "microphone", SitePermissions.SESSION);
|
||||
|
||||
let geoIcon = gIdentityHandler._identityBox
|
||||
.querySelector(".blocked-permission-icon[data-permission-id='geo']");
|
||||
|
@ -190,11 +189,6 @@ add_task(function* testPermissionIcons() {
|
|||
ok(!cameraIcon.hasAttribute("showing"),
|
||||
"allowed permission icon is not shown");
|
||||
|
||||
let microphoneIcon = gIdentityHandler._identityBox
|
||||
.querySelector(".blocked-permission-icon[data-permission-id='microphone']");
|
||||
ok(!microphoneIcon.hasAttribute("showing"),
|
||||
"allowed permission icon is not shown");
|
||||
|
||||
SitePermissions.remove(gBrowser.currentURI, "geo");
|
||||
|
||||
ok(!geoIcon.hasAttribute("showing"),
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/SitePermissions.jsm", this);
|
||||
Cu.import("resource:///modules/E10SUtils.jsm");
|
||||
|
||||
const SUBFRAME_PAGE = "https://example.com/browser/browser/base/content/test/general/temporary_permissions_subframe.html";
|
||||
|
||||
// Test that setting temp permissions triggers a change in the identity block.
|
||||
add_task(function* testTempPermissionChangeEvents() {
|
||||
let uri = NetUtil.newURI("https://example.com");
|
||||
let id = "geo";
|
||||
|
||||
yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
let geoIcon = document.querySelector(".blocked-permission-icon[data-permission-id=geo]");
|
||||
|
||||
Assert.notEqual(geoIcon.boxObject.width, 0, "geo anchor should be visible");
|
||||
|
||||
SitePermissions.remove(uri, id, browser);
|
||||
|
||||
Assert.equal(geoIcon.boxObject.width, 0, "geo anchor should not be visible");
|
||||
});
|
||||
});
|
||||
|
||||
// Test that temp permissions are persisted through moving tabs to new windows.
|
||||
add_task(function* testTempPermissionOnTabMove() {
|
||||
let uri = NetUtil.newURI("https://example.com");
|
||||
let id = "geo";
|
||||
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
|
||||
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, tab.linkedBrowser);
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, tab.linkedBrowser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
let promiseWin = BrowserTestUtils.waitForNewWindow();
|
||||
gBrowser.replaceTabWithWindow(tab);
|
||||
let win = yield promiseWin;
|
||||
tab = win.gBrowser.selectedTab;
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, tab.linkedBrowser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
SitePermissions.remove(uri, id, tab.linkedBrowser);
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
// Test that temp permissions don't affect other tabs of the same URI.
|
||||
add_task(function* testTempPermissionMultipleTabs() {
|
||||
let uri = NetUtil.newURI("https://example.com");
|
||||
let id = "geo";
|
||||
|
||||
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
|
||||
let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
|
||||
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, tab2.linkedBrowser);
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, tab2.linkedBrowser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, tab1.linkedBrowser), {
|
||||
state: SitePermissions.UNKNOWN,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
});
|
||||
|
||||
let geoIcon = document.querySelector(".blocked-permission-icon[data-permission-id=geo]");
|
||||
|
||||
Assert.notEqual(geoIcon.boxObject.width, 0, "geo anchor should be visible");
|
||||
|
||||
yield BrowserTestUtils.switchTab(gBrowser, tab1);
|
||||
|
||||
Assert.equal(geoIcon.boxObject.width, 0, "geo anchor should not be visible");
|
||||
|
||||
SitePermissions.remove(uri, id, tab2.linkedBrowser);
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
yield BrowserTestUtils.removeTab(tab2);
|
||||
});
|
||||
|
||||
// Test that temp blocked permissions requested by subframes (with a different URI) affect the whole page.
|
||||
add_task(function* testTempPermissionSubframes() {
|
||||
let uri = NetUtil.newURI("https://example.com");
|
||||
let id = "geo";
|
||||
|
||||
yield BrowserTestUtils.withNewTab(SUBFRAME_PAGE, function*(browser) {
|
||||
let popupshown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
|
||||
|
||||
// Request a permission;
|
||||
yield ContentTask.spawn(browser, uri.host, function(host) {
|
||||
E10SUtils.wrapHandlingUserInput(content, true, function() {
|
||||
let frame = content.document.getElementById("frame");
|
||||
let frameDoc = frame.contentWindow.document;
|
||||
|
||||
// Make sure that the origin of our test page is different.
|
||||
Assert.notEqual(frameDoc.location.host, host);
|
||||
|
||||
frameDoc.getElementById("geo").click();
|
||||
});
|
||||
});
|
||||
|
||||
yield popupshown;
|
||||
|
||||
let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
|
||||
|
||||
let notification = PopupNotifications.panel.firstChild;
|
||||
EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
|
||||
|
||||
yield popuphidden;
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/SitePermissions.jsm", this);
|
||||
|
||||
// Test that temporary permissions are removed on user initiated reload only.
|
||||
add_task(function* testTempPermissionOnReload() {
|
||||
let uri = NetUtil.newURI("https://example.com");
|
||||
let id = "geo";
|
||||
|
||||
yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
|
||||
let reloadButton = document.getElementById("urlbar-reload-button");
|
||||
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
|
||||
|
||||
let reloaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
// Reload through the page (should not remove the temp permission).
|
||||
yield ContentTask.spawn(browser, {}, () => content.document.location.reload());
|
||||
|
||||
yield reloaded;
|
||||
yield BrowserTestUtils.waitForCondition(() => {
|
||||
return reloadButton.disabled == false;
|
||||
});
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
// Reload as a user (should remove the temp permission).
|
||||
EventUtils.synthesizeMouseAtCenter(reloadButton, {});
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.UNKNOWN,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
});
|
||||
|
||||
SitePermissions.remove(uri, id, browser);
|
||||
});
|
||||
});
|
||||
|
||||
// Test that temporary permissions are persisted through navigation in a tab.
|
||||
add_task(function* testTempPermissionOnNavigation() {
|
||||
let uri = NetUtil.newURI("https://example.com/");
|
||||
let id = "geo";
|
||||
|
||||
yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
let loaded = BrowserTestUtils.browserLoaded(browser, false, "https://example.org/");
|
||||
|
||||
// Navigate to another domain.
|
||||
yield ContentTask.spawn(browser, {}, () => content.document.location = "https://example.org/");
|
||||
|
||||
yield loaded;
|
||||
|
||||
// The temporary permissions for the current URI should be reset.
|
||||
Assert.deepEqual(SitePermissions.get(browser.currentURI, id, browser), {
|
||||
state: SitePermissions.UNKNOWN,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
});
|
||||
|
||||
loaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
|
||||
|
||||
// Navigate to the original domain.
|
||||
yield ContentTask.spawn(browser, {}, () => content.document.location = "https://example.com/");
|
||||
|
||||
yield loaded;
|
||||
|
||||
// The temporary permissions for the original URI should still exist.
|
||||
Assert.deepEqual(SitePermissions.get(browser.currentURI, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
SitePermissions.remove(uri, id, browser);
|
||||
});
|
||||
});
|
||||
|
|
@ -9,5 +9,6 @@
|
|||
<body>
|
||||
<!-- This page could eventually request permissions from content
|
||||
and make sure that chrome responds appropriately -->
|
||||
<button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Temporary Permissions Subframe Test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="frame" src="https://example.org/browser/browser/base/content/test/general/permissions.html" />
|
||||
</body>
|
||||
</html>
|
|
@ -119,6 +119,34 @@ var gTests = [
|
|||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
|
||||
// Verify that we set 'Temporarily blocked' permissions.
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let blockedPerms = document.getElementById("blocked-permissions-container");
|
||||
|
||||
let {state, scope} = SitePermissions.get(null, "camera", browser);
|
||||
Assert.equal(state, SitePermissions.BLOCK);
|
||||
Assert.equal(scope, SitePermissions.SCOPE_TEMPORARY);
|
||||
ok(blockedPerms.querySelector(".blocked-permission-icon.camera-icon[showing=true]"),
|
||||
"the blocked camera icon is shown");
|
||||
|
||||
({state, scope} = SitePermissions.get(null, "microphone", browser));
|
||||
Assert.equal(state, SitePermissions.BLOCK);
|
||||
Assert.equal(scope, SitePermissions.SCOPE_TEMPORARY);
|
||||
ok(blockedPerms.querySelector(".blocked-permission-icon.microphone-icon[showing=true]"),
|
||||
"the blocked microphone icon is shown");
|
||||
|
||||
info("requesting devices again to check temporarily blocked permissions");
|
||||
promise = promiseMessage(permissionError);
|
||||
yield promiseRequestDevice(true, true);
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
|
||||
SitePermissions.remove(browser.currentURI, "camera", browser);
|
||||
SitePermissions.remove(browser.currentURI, "microphone", browser);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -181,7 +209,7 @@ var gTests = [
|
|||
let elt = id => document.getElementById(id);
|
||||
|
||||
function* checkPerm(aRequestAudio, aRequestVideo,
|
||||
aExpectedAudioPerm, aExpectedVideoPerm, aNever) {
|
||||
aExpectedAudioPerm, aExpectedVideoPerm, aNever) {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(aRequestAudio, aRequestVideo);
|
||||
yield promise;
|
||||
|
@ -280,6 +308,9 @@ var gTests = [
|
|||
});
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
SitePermissions.remove(null, "camera", browser);
|
||||
SitePermissions.remove(null, "microphone", browser);
|
||||
} else {
|
||||
let expectedMessage = aExpectStream ? "ok" : permissionError;
|
||||
let promise = promiseMessage(expectedMessage);
|
||||
|
|
|
@ -335,6 +335,8 @@ var gTests = [
|
|||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
|
||||
SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -355,6 +357,8 @@ var gTests = [
|
|||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
|
||||
SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -499,10 +503,10 @@ var gTests = [
|
|||
{
|
||||
desc: "Only persistent block is possible for screen sharing",
|
||||
run: function* checkPersistentPermissions() {
|
||||
let Perms = Services.perms;
|
||||
let uri = gBrowser.selectedBrowser.documentURI;
|
||||
let devicePerms = Perms.testExactPermission(uri, "screen");
|
||||
is(devicePerms, Perms.UNKNOWN_ACTION,
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let uri = browser.documentURI;
|
||||
let devicePerms = SitePermissions.get(uri, "screen", browser);
|
||||
is(devicePerms.state, SitePermissions.UNKNOWN,
|
||||
"starting without screen persistent permissions");
|
||||
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
|
@ -533,7 +537,10 @@ var gTests = [
|
|||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
|
||||
is(Perms.testExactPermission(uri, "screen"), Perms.DENY_ACTION,
|
||||
let permission = SitePermissions.get(uri, "screen", browser);
|
||||
is(permission.state, SitePermissions.BLOCK,
|
||||
"screen sharing is blocked");
|
||||
is(permission.scope, SitePermissions.SCOPE_PERSISTENT,
|
||||
"screen sharing is persistently blocked");
|
||||
|
||||
// Request screensharing again, expect an immediate failure.
|
||||
|
@ -543,7 +550,7 @@ var gTests = [
|
|||
yield expectObserverCalled("recording-window-ended");
|
||||
|
||||
// Now set the permission to allow and expect a prompt.
|
||||
Perms.add(uri, "screen", Perms.ALLOW_ACTION);
|
||||
SitePermissions.set(uri, "screen", SitePermissions.ALLOW);
|
||||
|
||||
// Request devices and expect a prompt despite the saved 'Allow' permission.
|
||||
promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
|
@ -564,7 +571,7 @@ var gTests = [
|
|||
});
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
Perms.remove(uri, "screen");
|
||||
SitePermissions.remove(uri, "screen", browser);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource:///modules/SitePermissions.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
|
|
|
@ -6,6 +6,7 @@ allow = Allow
|
|||
allowForSession = Allow for Session
|
||||
allowTemporarily = Allow Temporarily
|
||||
block = Block
|
||||
blockTemporarily = Block Temporarily
|
||||
alwaysAsk = Always Ask
|
||||
|
||||
permission.cookie.label = Set Cookies
|
||||
|
|
|
@ -65,6 +65,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
|
||||
"resource:///modules/SitePermissions.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
|
@ -214,17 +216,10 @@ this.PermissionPromptPrototype = {
|
|||
* The label that will be displayed for this choice.
|
||||
* accessKey (string):
|
||||
* The access key character that will be used for this choice.
|
||||
* action (Ci.nsIPermissionManager action, optional)
|
||||
* The nsIPermissionManager action that will be associated with
|
||||
* this choice. For example, Ci.nsIPermissionManager.DENY_ACTION.
|
||||
* action (SitePermissions state)
|
||||
* The action that will be associated with this choice.
|
||||
* This should be either SitePermissions.ALLOW or SitePermissions.BLOCK.
|
||||
*
|
||||
* If omitted, the nsIPermissionManager will not be written to
|
||||
* when this choice is chosen.
|
||||
* expireType (Ci.nsIPermissionManager expiration policy, optional)
|
||||
* The nsIPermissionManager expiration policy that will be associated
|
||||
* with this choice. For example, Ci.nsIPermissionManager.EXPIRE_SESSION.
|
||||
*
|
||||
* If action is not set, expireType will be ignored.
|
||||
* callback (function, optional)
|
||||
* A callback function that will fire if the user makes this choice, with
|
||||
* a single parameter, state. State is an Object that contains the property
|
||||
|
@ -271,16 +266,16 @@ this.PermissionPromptPrototype = {
|
|||
// If we're reading and setting permissions, then we need
|
||||
// to check to see if we already have a permission setting
|
||||
// for this particular principal.
|
||||
let result =
|
||||
Services.perms.testExactPermissionFromPrincipal(this.principal,
|
||||
this.permissionKey);
|
||||
let {state} = SitePermissions.get(requestingURI,
|
||||
this.permissionKey,
|
||||
this.browser);
|
||||
|
||||
if (result == Ci.nsIPermissionManager.DENY_ACTION) {
|
||||
if (state == SitePermissions.BLOCK) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
if (state == SitePermissions.ALLOW) {
|
||||
this.allow();
|
||||
return;
|
||||
}
|
||||
|
@ -289,14 +284,6 @@ this.PermissionPromptPrototype = {
|
|||
// Transform the PermissionPrompt actions into PopupNotification actions.
|
||||
let popupNotificationActions = [];
|
||||
for (let promptAction of this.promptActions) {
|
||||
// Don't offer action in PB mode if the action remembers permission
|
||||
// for more than a session.
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) &&
|
||||
promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
|
||||
promptAction.action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let action = {
|
||||
label: promptAction.label,
|
||||
accessKey: promptAction.accessKey,
|
||||
|
@ -306,17 +293,31 @@ this.PermissionPromptPrototype = {
|
|||
}
|
||||
|
||||
if (this.permissionKey) {
|
||||
// Remember permissions.
|
||||
if (state && state.checkboxChecked && promptAction.action) {
|
||||
Services.perms.addFromPrincipal(this.principal,
|
||||
this.permissionKey,
|
||||
promptAction.action,
|
||||
promptAction.expireType);
|
||||
|
||||
// Permanently store permission.
|
||||
if (state && state.checkboxChecked) {
|
||||
let scope = SitePermissions.SCOPE_PERSISTENT;
|
||||
// Only remember permission for session if in PB mode.
|
||||
if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
|
||||
scope = SitePermissions.SCOPE_SESSION;
|
||||
}
|
||||
SitePermissions.set(this.principal.URI,
|
||||
this.permissionKey,
|
||||
promptAction.action,
|
||||
scope);
|
||||
} else if (promptAction.action == SitePermissions.BLOCK) {
|
||||
// Temporarily store BLOCK permissions only.
|
||||
// SitePermissions does not consider subframes when storing temporary
|
||||
// permissions on a tab, thus storing ALLOW could be exploited.
|
||||
SitePermissions.set(this.principal.URI,
|
||||
this.permissionKey,
|
||||
promptAction.action,
|
||||
SitePermissions.SCOPE_TEMPORARY,
|
||||
this.browser);
|
||||
}
|
||||
|
||||
// Grant permission if action is null or ALLOW_ACTION.
|
||||
if (!promptAction.action ||
|
||||
promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
// Grant permission if action is ALLOW.
|
||||
if (promptAction.action == SitePermissions.ALLOW) {
|
||||
this.allow();
|
||||
} else {
|
||||
this.cancel();
|
||||
|
@ -479,8 +480,7 @@ GeolocationPermissionPrompt.prototype = {
|
|||
label: gBrowserBundle.GetStringFromName("geolocation.allowLocation"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("geolocation.allowLocation.accesskey"),
|
||||
action: null,
|
||||
expireType: null,
|
||||
action: SitePermissions.ALLOW,
|
||||
callback(state) {
|
||||
if (state && state.checkboxChecked) {
|
||||
secHistogram.add(ALWAYS_SHARE);
|
||||
|
@ -492,10 +492,7 @@ GeolocationPermissionPrompt.prototype = {
|
|||
label: gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation.accesskey"),
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal) ?
|
||||
Ci.nsIPermissionManager.EXPIRE_SESSION :
|
||||
null,
|
||||
action: SitePermissions.BLOCK,
|
||||
callback(state) {
|
||||
if (state && state.checkboxChecked) {
|
||||
secHistogram.add(NEVER_SHARE);
|
||||
|
@ -579,19 +576,13 @@ DesktopNotificationPermissionPrompt.prototype = {
|
|||
label: gBrowserBundle.GetStringFromName("webNotifications.allow"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("webNotifications.allow.accesskey"),
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: PrivateBrowsingUtils.isBrowserPrivate(this.browser) ?
|
||||
Ci.nsIPermissionManager.EXPIRE_SESSION :
|
||||
null,
|
||||
action: SitePermissions.ALLOW,
|
||||
},
|
||||
{
|
||||
label: gBrowserBundle.GetStringFromName("webNotifications.dontAllow"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("webNotifications.dontAllow.accesskey"),
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: PrivateBrowsingUtils.isBrowserPrivate(this.browser) ?
|
||||
Ci.nsIPermissionManager.EXPIRE_SESSION :
|
||||
null,
|
||||
action: SitePermissions.BLOCK,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
|
@ -5,35 +5,160 @@
|
|||
this.EXPORTED_SYMBOLS = [ "SitePermissions" ];
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var gStringBundle =
|
||||
Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
|
||||
|
||||
this.SitePermissions = {
|
||||
/**
|
||||
* A helper module to manage temporarily blocked permissions.
|
||||
*
|
||||
* Permissions are keyed by browser, so methods take a Browser
|
||||
* element to identify the corresponding permission set.
|
||||
*
|
||||
* This uses a WeakMap to key browsers, so that entries are
|
||||
* automatically cleared once the browser stops existing
|
||||
* (once there are no other references to the browser object);
|
||||
*/
|
||||
const TemporaryBlockedPermissions = {
|
||||
// This is a three level deep map with the following structure:
|
||||
//
|
||||
// Browser => {
|
||||
// <prePath>: {
|
||||
// <permissionID>: {Number} <timeStamp>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Only the top level browser elements are stored via WeakMap. The WeakMap
|
||||
// value is an object with URI prePaths as keys. The keys of that object
|
||||
// are ids that identify permissions that were set for the specific URI.
|
||||
// The final value is an object containing the timestamp of when the permission
|
||||
// was set (in order to invalidate after a certain amount of time has passed).
|
||||
_stateByBrowser: new WeakMap(),
|
||||
|
||||
// Private helper method that bundles some shared behavior for
|
||||
// get() and getAll(), e.g. deleting permissions when they have expired.
|
||||
_get(entry, prePath, id, timeStamp) {
|
||||
if (timeStamp == null) {
|
||||
delete entry[prePath][id];
|
||||
return null;
|
||||
}
|
||||
if (timeStamp + SitePermissions.temporaryPermissionExpireTime < Date.now()) {
|
||||
delete entry[prePath][id];
|
||||
return null;
|
||||
}
|
||||
return {id, state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_TEMPORARY};
|
||||
},
|
||||
|
||||
// Sets a new permission for the specified browser.
|
||||
set(browser, id) {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
if (!this._stateByBrowser.has(browser)) {
|
||||
this._stateByBrowser.set(browser, {});
|
||||
}
|
||||
let entry = this._stateByBrowser.get(browser);
|
||||
let prePath = browser.currentURI.prePath;
|
||||
if (!entry[prePath]) {
|
||||
entry[prePath] = {};
|
||||
}
|
||||
entry[prePath][id] = Date.now();
|
||||
},
|
||||
|
||||
// Removes a permission with the specified id for the specified browser.
|
||||
remove(browser, id) {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
let entry = this._stateByBrowser.get(browser);
|
||||
let prePath = browser.currentURI.prePath;
|
||||
if (entry && entry[prePath]) {
|
||||
delete entry[prePath][id];
|
||||
}
|
||||
},
|
||||
|
||||
// Gets a permission with the specified id for the specified browser.
|
||||
get(browser, id) {
|
||||
if (!browser || !browser.currentURI) {
|
||||
return null;
|
||||
}
|
||||
let entry = this._stateByBrowser.get(browser);
|
||||
let prePath = browser.currentURI.prePath;
|
||||
if (entry && entry[prePath]) {
|
||||
let permission = entry[prePath][id];
|
||||
return this._get(entry, prePath, id, permission);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Gets all permissions for the specified browser.
|
||||
// Note that only permissions that apply to the current URI
|
||||
// of the passed browser element will be returned.
|
||||
getAll(browser) {
|
||||
let permissions = [];
|
||||
let entry = this._stateByBrowser.get(browser);
|
||||
let prePath = browser.currentURI.prePath;
|
||||
if (entry && entry[prePath]) {
|
||||
let timeStamps = entry[prePath];
|
||||
for (let id of Object.keys(timeStamps)) {
|
||||
let permission = this._get(entry, prePath, id, timeStamps[id]);
|
||||
// _get() returns null when the permission has expired.
|
||||
if (permission) {
|
||||
permissions.push(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
return permissions;
|
||||
},
|
||||
|
||||
// Clears all permissions for the specified browser.
|
||||
// Unlike other methods, this does NOT clear only for
|
||||
// the currentURI but the whole browser state.
|
||||
clear(browser) {
|
||||
this._stateByBrowser.delete(browser);
|
||||
},
|
||||
|
||||
// Copies the temporary permission state of one browser
|
||||
// into a new entry for the other browser.
|
||||
copy(browser, newBrowser) {
|
||||
let entry = this._stateByBrowser.get(browser);
|
||||
if (entry) {
|
||||
this._stateByBrowser.set(newBrowser, entry);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.SitePermissions = {
|
||||
// Permission states.
|
||||
UNKNOWN: Services.perms.UNKNOWN_ACTION,
|
||||
ALLOW: Services.perms.ALLOW_ACTION,
|
||||
BLOCK: Services.perms.DENY_ACTION,
|
||||
SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
|
||||
ALLOW_COOKIES_FOR_SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
|
||||
|
||||
/* Returns all custom permissions for a given URI, the return
|
||||
* type is a list of objects with the keys:
|
||||
* - id: the permissionId of the permission
|
||||
* - state: a constant representing the current permission state
|
||||
* (e.g. SitePermissions.ALLOW)
|
||||
// Permission scopes.
|
||||
SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
|
||||
SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
|
||||
SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
|
||||
SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
|
||||
|
||||
/**
|
||||
* Gets all custom permissions for a given URI.
|
||||
* Install addon permission is excluded, check bug 1303108.
|
||||
*
|
||||
* To receive a more detailed, albeit less performant listing see
|
||||
* SitePermissions.getPermissionDetailsByURI().
|
||||
*
|
||||
* install addon permission is excluded, check bug 1303108
|
||||
* @return {Array} a list of objects with the keys:
|
||||
* - id: the permissionId of the permission
|
||||
* - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
|
||||
* - state: a constant representing the current permission state
|
||||
* (e.g. SitePermissions.ALLOW)
|
||||
*/
|
||||
getAllByURI(aURI) {
|
||||
getAllByURI(uri) {
|
||||
let result = [];
|
||||
if (!this.isSupportedURI(aURI)) {
|
||||
if (!this.isSupportedURI(uri)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let permissions = Services.perms.getAllForURI(aURI);
|
||||
let permissions = Services.perms.getAllForURI(uri);
|
||||
while (permissions.hasMoreElements()) {
|
||||
let permission = permissions.getNext();
|
||||
|
||||
|
@ -43,8 +168,13 @@ this.SitePermissions = {
|
|||
if (permission.type == "install") {
|
||||
continue;
|
||||
}
|
||||
let scope = this.SCOPE_PERSISTENT;
|
||||
if (permission.expireType == Services.perms.EXPIRE_SESSION) {
|
||||
scope = this.SCOPE_SESSION;
|
||||
}
|
||||
result.push({
|
||||
id: permission.type,
|
||||
scope,
|
||||
state: permission.capability,
|
||||
});
|
||||
}
|
||||
|
@ -53,137 +183,330 @@ this.SitePermissions = {
|
|||
return result;
|
||||
},
|
||||
|
||||
/* Returns an object representing the aId permission. It contains the
|
||||
* following keys:
|
||||
* - id: the permissionID of the permission
|
||||
* - label: the localized label
|
||||
* - state: a constant representing the aState permission state
|
||||
* (e.g. SitePermissions.ALLOW), or the default if aState is omitted
|
||||
* - availableStates: an array of all available states for that permission,
|
||||
* represented as objects with the keys:
|
||||
* - id: the state constant
|
||||
* - label: the translated label of that state
|
||||
/**
|
||||
* Returns detailed information on the specified permission.
|
||||
*
|
||||
* @param {String} id
|
||||
* The permissionID of the permission.
|
||||
* @param {SitePermissions scope} scope
|
||||
* The current scope of the permission.
|
||||
* @param {SitePermissions state} state (optional)
|
||||
* The current state of the permission.
|
||||
* Will default to the default state if omitted.
|
||||
*
|
||||
* @return {Object} an object with the keys:
|
||||
* - id: the permissionID of the permission
|
||||
* - label: the localized label
|
||||
* - state: the passed in state argument
|
||||
* - scope: the passed in scope argument
|
||||
* - availableStates: an array of all available states for that permission,
|
||||
* represented as objects with the keys:
|
||||
* - id: the state constant
|
||||
* - label: the translated label of that state
|
||||
*/
|
||||
getPermissionItem(aId, aState) {
|
||||
let availableStates = this.getAvailableStates(aId).map(state => {
|
||||
return { id: state, label: this.getStateLabel(aId, state) };
|
||||
getPermissionDetails(id, scope, state = this.getDefault(id)) {
|
||||
let availableStates = this.getAvailableStates(id).map(val => {
|
||||
return { id: val, label: this.getStateLabel(val) };
|
||||
});
|
||||
if (aState == undefined)
|
||||
aState = this.getDefault(aId);
|
||||
return {id: aId, label: this.getPermissionLabel(aId),
|
||||
state: aState, availableStates};
|
||||
return {id, label: this.getPermissionLabel(id), state, scope, availableStates};
|
||||
},
|
||||
|
||||
/* Returns a list of objects representing all permissions that are currently
|
||||
* set for the given URI. See getPermissionItem for the content of each object.
|
||||
/**
|
||||
* Returns all custom permissions for a given browser.
|
||||
*
|
||||
* To receive a more detailed, albeit less performant listing see
|
||||
* SitePermissions.getAllPermissionDetailsForBrowser().
|
||||
*
|
||||
* @param {Browser} browser
|
||||
* The browser to fetch permission for.
|
||||
*
|
||||
* @return {Array} a list of objects with the keys:
|
||||
* - id: the permissionId of the permission
|
||||
* - state: a constant representing the current permission state
|
||||
* (e.g. SitePermissions.ALLOW)
|
||||
* - scope: a constant representing how long the permission will
|
||||
* be kept.
|
||||
*/
|
||||
getPermissionDetailsByURI(aURI) {
|
||||
let permissions = [];
|
||||
for (let {state, id} of this.getAllByURI(aURI)) {
|
||||
permissions.push(this.getPermissionItem(id, state));
|
||||
getAllForBrowser(browser) {
|
||||
let permissions = {};
|
||||
|
||||
for (let permission of TemporaryBlockedPermissions.getAll(browser)) {
|
||||
permission.scope = this.SCOPE_TEMPORARY;
|
||||
permissions[permission.id] = permission;
|
||||
}
|
||||
|
||||
return permissions;
|
||||
for (let permission of this.getAllByURI(browser.currentURI)) {
|
||||
permissions[permission.id] = permission;
|
||||
}
|
||||
|
||||
return Object.values(permissions);
|
||||
},
|
||||
|
||||
/* Checks whether a UI for managing permissions should be exposed for a given
|
||||
/**
|
||||
* Returns a list of objects with detailed information on all permissions
|
||||
* that are currently set for the given browser.
|
||||
*
|
||||
* @param {Browser} browser
|
||||
* The browser to fetch permission for.
|
||||
*
|
||||
* @return {Array} a list of objects. See getPermissionDetails for the content of each object.
|
||||
*/
|
||||
getAllPermissionDetailsForBrowser(browser) {
|
||||
return this.getAllForBrowser(browser).map(({id, scope, state}) =>
|
||||
this.getPermissionDetails(id, scope, state));
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether a UI for managing permissions should be exposed for a given
|
||||
* URI. This excludes file URIs, for instance, as they don't have a host,
|
||||
* even though nsIPermissionManager can still handle them.
|
||||
*
|
||||
* @param {nsIURI} uri
|
||||
* The URI to check.
|
||||
*
|
||||
* @return {boolean} if the URI is supported.
|
||||
*/
|
||||
isSupportedURI(aURI) {
|
||||
return aURI.schemeIs("http") || aURI.schemeIs("https");
|
||||
isSupportedURI(uri) {
|
||||
return uri && (uri.schemeIs("http") || uri.schemeIs("https"));
|
||||
},
|
||||
|
||||
/* Returns an array of all permission IDs.
|
||||
/**
|
||||
* Gets an array of all permission IDs.
|
||||
*
|
||||
* @return {Array<String>} an array of all permission IDs.
|
||||
*/
|
||||
listPermissions() {
|
||||
return Object.keys(gPermissionObject);
|
||||
},
|
||||
|
||||
/* Returns an array of permission states to be exposed to the user for a
|
||||
/**
|
||||
* Returns an array of permission states to be exposed to the user for a
|
||||
* permission with the given ID.
|
||||
*
|
||||
* @param {string} permissionID
|
||||
* The ID to get permission states for.
|
||||
*
|
||||
* @return {Array<SitePermissions state>} an array of all permission states.
|
||||
*/
|
||||
getAvailableStates(aPermissionID) {
|
||||
if (aPermissionID in gPermissionObject &&
|
||||
gPermissionObject[aPermissionID].states)
|
||||
return gPermissionObject[aPermissionID].states;
|
||||
getAvailableStates(permissionID) {
|
||||
if (permissionID in gPermissionObject &&
|
||||
gPermissionObject[permissionID].states)
|
||||
return gPermissionObject[permissionID].states;
|
||||
|
||||
if (this.getDefault(aPermissionID) == this.UNKNOWN)
|
||||
if (this.getDefault(permissionID) == this.UNKNOWN)
|
||||
return [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ];
|
||||
|
||||
return [ SitePermissions.ALLOW, SitePermissions.BLOCK ];
|
||||
},
|
||||
|
||||
/* Returns the default state of a particular permission.
|
||||
/**
|
||||
* Returns the default state of a particular permission.
|
||||
*
|
||||
* @param {string} permissionID
|
||||
* The ID to get the default for.
|
||||
*
|
||||
* @return {SitePermissions.state} the default state.
|
||||
*/
|
||||
getDefault(aPermissionID) {
|
||||
if (aPermissionID in gPermissionObject &&
|
||||
gPermissionObject[aPermissionID].getDefault)
|
||||
return gPermissionObject[aPermissionID].getDefault();
|
||||
getDefault(permissionID) {
|
||||
if (permissionID in gPermissionObject &&
|
||||
gPermissionObject[permissionID].getDefault)
|
||||
return gPermissionObject[permissionID].getDefault();
|
||||
|
||||
return this.UNKNOWN;
|
||||
},
|
||||
|
||||
/* Returns the state of a particular permission for a given URI.
|
||||
/**
|
||||
* Returns the state and scope of a particular permission for a given URI.
|
||||
*
|
||||
* @param {nsIURI} uri
|
||||
* The URI to check.
|
||||
* @param {String} permissionID
|
||||
* The id of the permission.
|
||||
* @param {Browser} browser (optional)
|
||||
* The browser object to check for temporary permissions.
|
||||
*
|
||||
* @return {Object} an object with the keys:
|
||||
* - state: The current state of the permission
|
||||
* (e.g. SitePermissions.ALLOW)
|
||||
* - scope: The scope of the permission
|
||||
* (e.g. SitePermissions.SCOPE_PERSISTENT)
|
||||
*/
|
||||
get(aURI, aPermissionID) {
|
||||
if (!this.isSupportedURI(aURI))
|
||||
return this.UNKNOWN;
|
||||
get(uri, permissionID, browser) {
|
||||
let result = { state: this.UNKNOWN, scope: this.SCOPE_PERSISTENT };
|
||||
if (this.isSupportedURI(uri)) {
|
||||
let permission = null;
|
||||
if (permissionID in gPermissionObject &&
|
||||
gPermissionObject[permissionID].exactHostMatch) {
|
||||
permission = Services.perms.getPermissionObjectForURI(uri, permissionID, true);
|
||||
} else {
|
||||
permission = Services.perms.getPermissionObjectForURI(uri, permissionID, false);
|
||||
}
|
||||
|
||||
let state;
|
||||
if (aPermissionID in gPermissionObject &&
|
||||
gPermissionObject[aPermissionID].exactHostMatch)
|
||||
state = Services.perms.testExactPermission(aURI, aPermissionID);
|
||||
else
|
||||
state = Services.perms.testPermission(aURI, aPermissionID);
|
||||
return state;
|
||||
if (permission) {
|
||||
result.state = permission.capability;
|
||||
if (permission.expireType == Services.perms.EXPIRE_SESSION) {
|
||||
result.scope = this.SCOPE_SESSION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.state) {
|
||||
// If there's no persistent permission saved, check if we have something
|
||||
// set temporarily.
|
||||
let value = TemporaryBlockedPermissions.get(browser, permissionID);
|
||||
|
||||
if (value) {
|
||||
result.state = value.state;
|
||||
result.scope = this.SCOPE_TEMPORARY;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* Sets the state of a particular permission for a given URI.
|
||||
/**
|
||||
* Sets the state of a particular permission for a given URI or browser.
|
||||
*
|
||||
* @param {nsIURI} uri
|
||||
* The URI to set the permission for.
|
||||
* Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
|
||||
* @param {String} permissionID
|
||||
* The id of the permission.
|
||||
* @param {SitePermissions state} state
|
||||
* The state of the permission.
|
||||
* @param {SitePermissions scope} scope (optional)
|
||||
* The scope of the permission. Defaults to SCOPE_PERSISTENT.
|
||||
* @param {Browser} browser (optional)
|
||||
* The browser object to set temporary permissions on.
|
||||
* This needs to be provided if the scope is SCOPE_TEMPORARY!
|
||||
*/
|
||||
set(aURI, aPermissionID, aState) {
|
||||
if (!this.isSupportedURI(aURI))
|
||||
return;
|
||||
|
||||
if (aState == this.UNKNOWN) {
|
||||
this.remove(aURI, aPermissionID);
|
||||
set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
|
||||
if (state == this.UNKNOWN) {
|
||||
this.remove(uri, permissionID, browser);
|
||||
return;
|
||||
}
|
||||
|
||||
Services.perms.add(aURI, aPermissionID, aState);
|
||||
if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
|
||||
throw "ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission";
|
||||
}
|
||||
|
||||
// Save temporary permissions.
|
||||
if (scope == this.SCOPE_TEMPORARY) {
|
||||
// We do not support setting temp ALLOW for security reasons.
|
||||
// In its current state, this permission could be exploited by subframes
|
||||
// on the same page. This is because for BLOCK we ignore the request
|
||||
// URI and only consider the current browser URI, to avoid notification spamming.
|
||||
//
|
||||
// If you ever consider removing this line, you likely want to implement
|
||||
// a more fine-grained TemporaryBlockedPermissions that temporarily blocks for the
|
||||
// entire browser, but temporarily allows only for specific frames.
|
||||
if (state != this.BLOCK) {
|
||||
throw "'Block' is the only permission we can save temporarily on a browser";
|
||||
}
|
||||
|
||||
if (!browser) {
|
||||
throw "TEMPORARY scoped permissions require a browser object";
|
||||
}
|
||||
|
||||
TemporaryBlockedPermissions.set(browser, permissionID);
|
||||
|
||||
browser.dispatchEvent(new browser.ownerGlobal
|
||||
.CustomEvent("PermissionStateChange"));
|
||||
} else if (this.isSupportedURI(uri)) {
|
||||
let perms_scope = Services.perms.EXPIRE_NEVER;
|
||||
if (scope == this.SCOPE_SESSION) {
|
||||
perms_scope = Services.perms.EXPIRE_SESSION;
|
||||
}
|
||||
|
||||
Services.perms.add(uri, permissionID, state, perms_scope);
|
||||
}
|
||||
},
|
||||
|
||||
/* Removes the saved state of a particular permission for a given URI.
|
||||
/**
|
||||
* Removes the saved state of a particular permission for a given URI and/or browser.
|
||||
*
|
||||
* @param {nsIURI} uri
|
||||
* The URI to remove the permission for.
|
||||
* @param {String} permissionID
|
||||
* The id of the permission.
|
||||
* @param {Browser} browser (optional)
|
||||
* The browser object to remove temporary permissions on.
|
||||
*/
|
||||
remove(aURI, aPermissionID) {
|
||||
if (!this.isSupportedURI(aURI))
|
||||
return;
|
||||
remove(uri, permissionID, browser) {
|
||||
if (this.isSupportedURI(uri))
|
||||
Services.perms.remove(uri, permissionID);
|
||||
|
||||
Services.perms.remove(aURI, aPermissionID);
|
||||
// TemporaryBlockedPermissions.get() deletes expired permissions automatically,
|
||||
if (TemporaryBlockedPermissions.get(browser, permissionID)) {
|
||||
// If it exists but has not expired, remove it explicitly.
|
||||
TemporaryBlockedPermissions.remove(browser, permissionID);
|
||||
// Send a PermissionStateChange event only if the permission hasn't expired.
|
||||
browser.dispatchEvent(new browser.ownerGlobal
|
||||
.CustomEvent("PermissionStateChange"));
|
||||
}
|
||||
},
|
||||
|
||||
/* Returns the localized label for the permission with the given ID, to be
|
||||
/**
|
||||
* Clears all permissions that were temporarily saved.
|
||||
*
|
||||
* @param {Browser} browser
|
||||
* The browser object to clear.
|
||||
*/
|
||||
clearTemporaryPermissions(browser) {
|
||||
TemporaryBlockedPermissions.clear(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy all permissions that were temporarily saved on one
|
||||
* browser object to a new browser.
|
||||
*
|
||||
* @param {Browser} browser
|
||||
* The browser object to copy from.
|
||||
* @param {Browser} newBrowser
|
||||
* The browser object to copy to.
|
||||
*/
|
||||
copyTemporaryPermissions(browser, newBrowser) {
|
||||
TemporaryBlockedPermissions.copy(browser, newBrowser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the localized label for the permission with the given ID, to be
|
||||
* used in a UI for managing permissions.
|
||||
*
|
||||
* @param {string} permissionID
|
||||
* The permission to get the label for.
|
||||
*
|
||||
* @return {String} the localized label.
|
||||
*/
|
||||
getPermissionLabel(aPermissionID) {
|
||||
let labelID = gPermissionObject[aPermissionID].labelID || aPermissionID;
|
||||
getPermissionLabel(permissionID) {
|
||||
let labelID = gPermissionObject[permissionID].labelID || permissionID;
|
||||
return gStringBundle.GetStringFromName("permission." + labelID + ".label");
|
||||
},
|
||||
|
||||
/* Returns the localized label for the given permission state, to be used in
|
||||
/**
|
||||
* Returns the localized label for the given permission state, to be used in
|
||||
* a UI for managing permissions.
|
||||
*
|
||||
* @param {SitePermissions state} state
|
||||
* The state to get the label for.
|
||||
* @param {SitePermissions scope} scope (optional)
|
||||
* The scope to get the label for.
|
||||
*
|
||||
* @return {String} the localized label.
|
||||
*/
|
||||
getStateLabel(aPermissionID, aState, aInUse = false) {
|
||||
switch (aState) {
|
||||
getStateLabel(state, scope = null) {
|
||||
switch (state) {
|
||||
case this.UNKNOWN:
|
||||
if (aInUse)
|
||||
return gStringBundle.GetStringFromName("allowTemporarily");
|
||||
return gStringBundle.GetStringFromName("alwaysAsk");
|
||||
case this.ALLOW:
|
||||
if (scope && scope != this.SCOPE_PERSISTENT)
|
||||
return gStringBundle.GetStringFromName("allowTemporarily");
|
||||
return gStringBundle.GetStringFromName("allow");
|
||||
case this.SESSION:
|
||||
case this.ALLOW_COOKIES_FOR_SESSION:
|
||||
return gStringBundle.GetStringFromName("allowForSession");
|
||||
case this.BLOCK:
|
||||
if (scope && scope != this.SCOPE_PERSISTENT)
|
||||
return gStringBundle.GetStringFromName("blockTemporarily");
|
||||
return gStringBundle.GetStringFromName("block");
|
||||
default:
|
||||
return null;
|
||||
|
@ -222,13 +545,13 @@ var gPermissionObject = {
|
|||
},
|
||||
|
||||
"cookie": {
|
||||
states: [ SitePermissions.ALLOW, SitePermissions.SESSION, SitePermissions.BLOCK ],
|
||||
states: [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ],
|
||||
getDefault() {
|
||||
if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == 2)
|
||||
return SitePermissions.BLOCK;
|
||||
|
||||
if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == 2)
|
||||
return SitePermissions.SESSION;
|
||||
return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
|
||||
|
||||
return SitePermissions.ALLOW;
|
||||
}
|
||||
|
@ -266,4 +589,6 @@ var gPermissionObject = {
|
|||
"indexedDB": {}
|
||||
};
|
||||
|
||||
const kPermissionIDs = Object.keys(gPermissionObject);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(SitePermissions, "temporaryPermissionExpireTime",
|
||||
"privacy.temporary_permission_expire_time_ms", 3600 * 1000);
|
||||
|
||||
|
|
|
@ -24,6 +24,10 @@ skip-if = !e10s
|
|||
support-files =
|
||||
../../components/uitour/test/uitour.html
|
||||
../../components/uitour/UITour-lib.js
|
||||
[browser_SitePermissions.js]
|
||||
[browser_SitePermissions_combinations.js]
|
||||
[browser_SitePermissions_expiry.js]
|
||||
[browser_SitePermissions_tab_urls.js]
|
||||
[browser_taskbar_preview.js]
|
||||
skip-if = os != "win"
|
||||
[browser_UnsubmittedCrashHandler.js]
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
Cu.import("resource://gre/modules/Integration.jsm", this);
|
||||
Cu.import("resource:///modules/PermissionUI.jsm", this);
|
||||
Cu.import("resource:///modules/SitePermissions.jsm", this);
|
||||
|
||||
/**
|
||||
* Given a <xul:browser> at some non-internal web page,
|
||||
|
@ -221,8 +222,7 @@ add_task(function* test_with_permission_key() {
|
|||
let mainAction = {
|
||||
label: "Allow",
|
||||
accessKey: "M",
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expiryType: Ci.nsIPermissionManager.EXPIRE_SESSION,
|
||||
action: SitePermissions.ALLOW,
|
||||
callback() {
|
||||
allowed = true;
|
||||
}
|
||||
|
@ -232,8 +232,7 @@ add_task(function* test_with_permission_key() {
|
|||
let secondaryAction = {
|
||||
label: "Deny",
|
||||
accessKey: "D",
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expiryType: Ci.nsIPermissionManager.EXPIRE_SESSION,
|
||||
action: SitePermissions.BLOCK,
|
||||
callback() {
|
||||
denied = true;
|
||||
}
|
||||
|
@ -242,7 +241,7 @@ add_task(function* test_with_permission_key() {
|
|||
let mockRequest = makeMockPermissionRequest(browser);
|
||||
let principal = mockRequest.principal;
|
||||
registerCleanupFunction(function() {
|
||||
Services.perms.removeFromPrincipal(principal, kTestPermissionKey);
|
||||
SitePermissions.remove(principal.URI, kTestPermissionKey);
|
||||
});
|
||||
|
||||
let TestPrompt = {
|
||||
|
@ -269,20 +268,28 @@ add_task(function* test_with_permission_key() {
|
|||
PopupNotifications.getNotification(kTestNotificationID, browser);
|
||||
Assert.ok(notification, "Should have gotten the notification");
|
||||
|
||||
let curPerm =
|
||||
Services.perms.testPermissionFromPrincipal(principal,
|
||||
kTestPermissionKey);
|
||||
Assert.equal(curPerm, Ci.nsIPermissionManager.UNKNOWN_ACTION,
|
||||
let curPerm = SitePermissions.get(principal.URI, kTestPermissionKey, browser);
|
||||
Assert.equal(curPerm.state, SitePermissions.UNKNOWN,
|
||||
"Should be no permission set to begin with.");
|
||||
|
||||
// First test denying the permission request.
|
||||
// First test denying the permission request without the checkbox checked.
|
||||
let popupNotification = getPopupNotificationNode();
|
||||
popupNotification.checkbox.checked = false;
|
||||
|
||||
Assert.equal(notification.secondaryActions.length, 1,
|
||||
"There should only be 1 secondary action");
|
||||
yield clickSecondaryAction();
|
||||
curPerm = Services.perms.testPermissionFromPrincipal(principal,
|
||||
kTestPermissionKey);
|
||||
Assert.equal(curPerm, Ci.nsIPermissionManager.DENY_ACTION,
|
||||
"Should have denied the action");
|
||||
curPerm = SitePermissions.get(principal.URI, kTestPermissionKey, browser);
|
||||
Assert.deepEqual(curPerm, {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
}, "Should have denied the action temporarily");
|
||||
// Try getting the permission without passing the browser object (should fail).
|
||||
curPerm = SitePermissions.get(principal.URI, kTestPermissionKey);
|
||||
Assert.deepEqual(curPerm, {
|
||||
state: SitePermissions.UNKNOWN,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
}, "Should have made no permanent permission entry");
|
||||
Assert.ok(denied, "The secondaryAction callback should have fired");
|
||||
Assert.ok(!allowed, "The mainAction callback should not have fired");
|
||||
Assert.ok(mockRequest._cancelled,
|
||||
|
@ -291,7 +298,7 @@ add_task(function* test_with_permission_key() {
|
|||
"The request should not have been allowed");
|
||||
|
||||
// Clear the permission and pretend we never denied
|
||||
Services.perms.removeFromPrincipal(principal, kTestPermissionKey);
|
||||
SitePermissions.remove(principal.URI, kTestPermissionKey, browser);
|
||||
denied = false;
|
||||
mockRequest._cancelled = false;
|
||||
|
||||
|
@ -301,12 +308,40 @@ add_task(function* test_with_permission_key() {
|
|||
TestPrompt.prompt();
|
||||
yield shownPromise;
|
||||
|
||||
// Next test allowing the permission request.
|
||||
// Test denying the permission request.
|
||||
Assert.equal(notification.secondaryActions.length, 1,
|
||||
"There should only be 1 secondary action");
|
||||
yield clickSecondaryAction();
|
||||
curPerm = SitePermissions.get(principal.URI, kTestPermissionKey);
|
||||
Assert.deepEqual(curPerm, {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT
|
||||
}, "Should have denied the action");
|
||||
Assert.ok(denied, "The secondaryAction callback should have fired");
|
||||
Assert.ok(!allowed, "The mainAction callback should not have fired");
|
||||
Assert.ok(mockRequest._cancelled,
|
||||
"The request should have been cancelled");
|
||||
Assert.ok(!mockRequest._allowed,
|
||||
"The request should not have been allowed");
|
||||
|
||||
// Clear the permission and pretend we never denied
|
||||
SitePermissions.remove(principal.URI, kTestPermissionKey);
|
||||
denied = false;
|
||||
mockRequest._cancelled = false;
|
||||
|
||||
// Bring the PopupNotification back up now...
|
||||
shownPromise =
|
||||
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
|
||||
TestPrompt.prompt();
|
||||
yield shownPromise;
|
||||
|
||||
// Test allowing the permission request.
|
||||
yield clickMainAction();
|
||||
curPerm = Services.perms.testPermissionFromPrincipal(principal,
|
||||
kTestPermissionKey);
|
||||
Assert.equal(curPerm, Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
"Should have allowed the action");
|
||||
curPerm = SitePermissions.get(principal.URI, kTestPermissionKey);
|
||||
Assert.deepEqual(curPerm, {
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT
|
||||
}, "Should have allowed the action");
|
||||
Assert.ok(!denied, "The secondaryAction callback should not have fired");
|
||||
Assert.ok(allowed, "The mainAction callback should have fired");
|
||||
Assert.ok(!mockRequest._cancelled,
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/SitePermissions.jsm", this);
|
||||
|
||||
// This asserts that SitePermissions.set can not save ALLOW permissions
|
||||
// temporarily on a tab.
|
||||
add_task(function* testTempAllowThrows() {
|
||||
let uri = Services.io.newURI("https://example.com");
|
||||
let id = "notifications";
|
||||
|
||||
yield BrowserTestUtils.withNewTab(uri.spec, function(browser) {
|
||||
Assert.throws(function() {
|
||||
SitePermissions.set(uri, id, SitePermissions.ALLOW, SitePermissions.SCOPE_TEMPORARY, browser);
|
||||
}, "'Block' is the only permission we can save temporarily on a tab");
|
||||
});
|
||||
});
|
||||
|
||||
// This tests the SitePermissions.getAllPermissionDetailsForBrowser function.
|
||||
add_task(function* testGetAllPermissionDetailsForBrowser() {
|
||||
let uri = Services.io.newURI("https://example.com");
|
||||
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
|
||||
|
||||
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
|
||||
SitePermissions.set(uri, "cookie", SitePermissions.ALLOW_COOKIES_FOR_SESSION);
|
||||
SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
|
||||
SitePermissions.set(uri, "geo", SitePermissions.ALLOW, SitePermissions.SCOPE_SESSION);
|
||||
|
||||
let permissions = SitePermissions.getAllPermissionDetailsForBrowser(tab.linkedBrowser);
|
||||
|
||||
let camera = permissions.find(({id}) => id === "camera");
|
||||
Assert.deepEqual(camera, {
|
||||
id: "camera",
|
||||
label: "Use the Camera",
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
availableStates: [
|
||||
{ id: SitePermissions.UNKNOWN, label: "Always Ask" },
|
||||
{ id: SitePermissions.ALLOW, label: "Allow" },
|
||||
{ id: SitePermissions.BLOCK, label: "Block" },
|
||||
]
|
||||
});
|
||||
|
||||
// check that removed permissions (State.UNKNOWN) are skipped
|
||||
SitePermissions.remove(uri, "camera");
|
||||
permissions = SitePermissions.getAllPermissionDetailsForBrowser(tab.linkedBrowser);
|
||||
|
||||
camera = permissions.find(({id}) => id === "camera");
|
||||
Assert.equal(camera, undefined);
|
||||
|
||||
// check that different available state values are represented
|
||||
|
||||
let cookie = permissions.find(({id}) => id === "cookie");
|
||||
Assert.deepEqual(cookie, {
|
||||
id: "cookie",
|
||||
label: "Set Cookies",
|
||||
state: SitePermissions.ALLOW_COOKIES_FOR_SESSION,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
availableStates: [
|
||||
{ id: SitePermissions.ALLOW, label: "Allow" },
|
||||
{ id: SitePermissions.ALLOW_COOKIES_FOR_SESSION, label: "Allow for Session" },
|
||||
{ id: SitePermissions.BLOCK, label: "Block" },
|
||||
]
|
||||
});
|
||||
|
||||
let popup = permissions.find(({id}) => id === "popup");
|
||||
Assert.deepEqual(popup, {
|
||||
id: "popup",
|
||||
label: "Open Pop-up Windows",
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
availableStates: [
|
||||
{ id: SitePermissions.ALLOW, label: "Allow" },
|
||||
{ id: SitePermissions.BLOCK, label: "Block" },
|
||||
]
|
||||
});
|
||||
|
||||
let geo = permissions.find(({id}) => id === "geo");
|
||||
Assert.deepEqual(geo, {
|
||||
id: "geo",
|
||||
label: "Access Your Location",
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_SESSION,
|
||||
availableStates: [
|
||||
{ id: SitePermissions.UNKNOWN, label: "Always Ask" },
|
||||
{ id: SitePermissions.ALLOW, label: "Allow" },
|
||||
{ id: SitePermissions.BLOCK, label: "Block" },
|
||||
]
|
||||
});
|
||||
|
||||
SitePermissions.remove(uri, "cookie");
|
||||
SitePermissions.remove(uri, "popup");
|
||||
SitePermissions.remove(uri, "geo");
|
||||
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/SitePermissions.jsm", this);
|
||||
|
||||
// This function applies combinations of different permissions and
|
||||
// checks how they override each other.
|
||||
function* checkPermissionCombinations(combinations) {
|
||||
let uri = Services.io.newURI("https://example.com");
|
||||
|
||||
yield BrowserTestUtils.withNewTab(uri.spec, function(browser) {
|
||||
let id = "geo";
|
||||
for (let {reverse, states, result} of combinations) {
|
||||
let loop = () => {
|
||||
for (let [state, scope] of states) {
|
||||
SitePermissions.set(uri, id, state, scope, browser);
|
||||
}
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), result);
|
||||
SitePermissions.remove(uri, id, browser);
|
||||
};
|
||||
|
||||
loop();
|
||||
|
||||
if (reverse) {
|
||||
states.reverse();
|
||||
loop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test that passing null as scope becomes SCOPE_PERSISTENT.
|
||||
add_task(function* testDefaultScope() {
|
||||
yield checkPermissionCombinations([{
|
||||
states: [
|
||||
[SitePermissions.ALLOW, null],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
},
|
||||
}]);
|
||||
});
|
||||
|
||||
// Test that "wide" scopes like PERSISTENT always override "narrower" ones like TAB.
|
||||
add_task(function* testScopeOverrides() {
|
||||
yield checkPermissionCombinations([
|
||||
{
|
||||
// The behavior of SCOPE_SESSION is not in line with the general behavior
|
||||
// because of the legacy nsIPermissionManager implementation.
|
||||
states: [
|
||||
[SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
|
||||
[SitePermissions.BLOCK, SitePermissions.SCOPE_SESSION],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_SESSION,
|
||||
},
|
||||
}, {
|
||||
states: [
|
||||
[SitePermissions.BLOCK, SitePermissions.SCOPE_SESSION],
|
||||
[SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
},
|
||||
|
||||
}, {
|
||||
reverse: true,
|
||||
states: [
|
||||
[SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY],
|
||||
[SitePermissions.ALLOW, SitePermissions.SCOPE_SESSION],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_SESSION,
|
||||
},
|
||||
}, {
|
||||
reverse: true,
|
||||
states: [
|
||||
[SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY],
|
||||
[SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
},
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
// Test that clearing a temporary permission also removes a
|
||||
// persistent permission that was set for the same URL.
|
||||
add_task(function* testClearTempPermission() {
|
||||
yield checkPermissionCombinations([{
|
||||
states: [
|
||||
[SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY],
|
||||
[SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
|
||||
[SitePermissions.UNKNOWN, SitePermissions.SCOPE_TEMPORARY],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.UNKNOWN,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
},
|
||||
}]);
|
||||
});
|
||||
|
||||
// Test that states override each other when applied with the same scope.
|
||||
add_task(function* testStateOverride() {
|
||||
yield checkPermissionCombinations([
|
||||
{
|
||||
states: [
|
||||
[SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
|
||||
[SitePermissions.BLOCK, SitePermissions.SCOPE_PERSISTENT],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
},
|
||||
}, {
|
||||
states: [
|
||||
[SitePermissions.BLOCK, SitePermissions.SCOPE_PERSISTENT],
|
||||
[SitePermissions.ALLOW, SitePermissions.SCOPE_PERSISTENT],
|
||||
],
|
||||
result: {
|
||||
state: SitePermissions.ALLOW,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
},
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/SitePermissions.jsm", this);
|
||||
|
||||
// This tests the time delay to expire temporary permission entries.
|
||||
add_task(function* testTemporaryPermissionExpiry() {
|
||||
SpecialPowers.pushPrefEnv({set: [
|
||||
["privacy.temporary_permission_expire_time_ms", 100],
|
||||
]});
|
||||
|
||||
let uri = Services.io.newURI("https://example.com")
|
||||
let id = "camera";
|
||||
|
||||
yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
yield new Promise((c) => setTimeout(c, 500));
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.UNKNOWN,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/SitePermissions.jsm", this);
|
||||
|
||||
// This tests the key used to store the URI -> permission map on a tab.
|
||||
add_task(function* testTemporaryPermissionTabURLs() {
|
||||
|
||||
// Prevent showing a dialog for https://name:password@example.com
|
||||
SpecialPowers.pushPrefEnv({set: [
|
||||
["network.http.phishy-userpass-length", 2048],
|
||||
]});
|
||||
|
||||
// This usually takes about 60 seconds on 32bit Linux debug,
|
||||
// due to the combinatory nature of the test that is hard to fix.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let same = [ "https://example.com", "https://example.com/sub/path", "https://example.com:443" ].map(Services.io.newURI);
|
||||
let different = [ "https://example.com", "https://name:password@example.com", "https://test1.example.com", "http://example.com", "http://example.org" ].map(Services.io.newURI);
|
||||
|
||||
let id = "microphone";
|
||||
|
||||
yield BrowserTestUtils.withNewTab("about:blank", function*(browser) {
|
||||
for (let uri of same) {
|
||||
let loaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
|
||||
browser.loadURI(uri.spec);
|
||||
yield loaded;
|
||||
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
|
||||
|
||||
for (let uri2 of same) {
|
||||
let loaded2 = BrowserTestUtils.browserLoaded(browser, false, uri2.spec);
|
||||
browser.loadURI(uri2.spec);
|
||||
yield loaded2;
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri2, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
}, `${uri.spec} should share tab permissions with ${uri2.spec}`);
|
||||
}
|
||||
|
||||
SitePermissions.clearTemporaryPermissions(browser);
|
||||
}
|
||||
|
||||
for (let uri of different) {
|
||||
let loaded = BrowserTestUtils.browserLoaded(browser, false, uri.spec);
|
||||
browser.loadURI(uri.spec);
|
||||
yield loaded;
|
||||
|
||||
SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
|
||||
|
||||
Assert.deepEqual(SitePermissions.get(uri, id, browser), {
|
||||
state: SitePermissions.BLOCK,
|
||||
scope: SitePermissions.SCOPE_TEMPORARY,
|
||||
});
|
||||
|
||||
for (let uri2 of different) {
|
||||
loaded = BrowserTestUtils.browserLoaded(browser, false, uri2.spec);
|
||||
browser.loadURI(uri2.spec);
|
||||
yield loaded;
|
||||
|
||||
if (uri2 != uri) {
|
||||
Assert.deepEqual(SitePermissions.get(uri2, id, browser), {
|
||||
state: SitePermissions.UNKNOWN,
|
||||
scope: SitePermissions.SCOPE_PERSISTENT,
|
||||
}, `${uri.spec} should not share tab permissions with ${uri2.spec}`);
|
||||
}
|
||||
}
|
||||
|
||||
SitePermissions.clearTemporaryPermissions(browser);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -10,6 +10,9 @@ const UNFILTERED_URI_COUNT = "browser.engagement.unfiltered_uri_count";
|
|||
|
||||
const TELEMETRY_SUBSESSION_TOPIC = "internal-telemetry-after-subsession-split";
|
||||
|
||||
// Reset internal URI counter in case URIs were opened by other tests.
|
||||
Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC, "");
|
||||
|
||||
/**
|
||||
* Waits for the web progress listener associated with this tab to fire an
|
||||
* onLocationChange for a non-error page.
|
||||
|
|
|
@ -24,22 +24,22 @@ add_task(function* testGetAllByURI() {
|
|||
|
||||
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
|
||||
Assert.deepEqual(SitePermissions.getAllByURI(uri), [
|
||||
{ id: "camera", state: SitePermissions.ALLOW }
|
||||
{ id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT }
|
||||
]);
|
||||
|
||||
SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
|
||||
SitePermissions.set(uri, "microphone", SitePermissions.ALLOW, SitePermissions.SCOPE_SESSION);
|
||||
SitePermissions.set(uri, "desktop-notification", SitePermissions.BLOCK);
|
||||
|
||||
Assert.deepEqual(SitePermissions.getAllByURI(uri), [
|
||||
{ id: "camera", state: SitePermissions.ALLOW },
|
||||
{ id: "microphone", state: SitePermissions.SESSION },
|
||||
{ id: "desktop-notification", state: SitePermissions.BLOCK }
|
||||
{ id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT },
|
||||
{ id: "microphone", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_SESSION },
|
||||
{ id: "desktop-notification", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT }
|
||||
]);
|
||||
|
||||
SitePermissions.remove(uri, "microphone");
|
||||
Assert.deepEqual(SitePermissions.getAllByURI(uri), [
|
||||
{ id: "camera", state: SitePermissions.ALLOW },
|
||||
{ id: "desktop-notification", state: SitePermissions.BLOCK }
|
||||
{ id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT },
|
||||
{ id: "desktop-notification", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT }
|
||||
]);
|
||||
|
||||
SitePermissions.remove(uri, "camera");
|
||||
|
@ -51,65 +51,3 @@ add_task(function* testGetAllByURI() {
|
|||
Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
|
||||
SitePermissions.remove(uri, "addon");
|
||||
});
|
||||
|
||||
add_task(function* testGetPermissionDetailsByURI() {
|
||||
// check that it returns an empty array on an invalid URI
|
||||
// like a file URI, which doesn't support site permissions
|
||||
let wrongURI = Services.io.newURI("file:///example.js")
|
||||
Assert.deepEqual(SitePermissions.getPermissionDetailsByURI(wrongURI), []);
|
||||
|
||||
let uri = Services.io.newURI("https://example.com")
|
||||
|
||||
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
|
||||
SitePermissions.set(uri, "cookie", SitePermissions.SESSION);
|
||||
SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
|
||||
|
||||
let permissions = SitePermissions.getPermissionDetailsByURI(uri);
|
||||
|
||||
let camera = permissions.find(({id}) => id === "camera");
|
||||
Assert.deepEqual(camera, {
|
||||
id: "camera",
|
||||
label: "Use the Camera",
|
||||
state: SitePermissions.ALLOW,
|
||||
availableStates: [
|
||||
{ id: SitePermissions.UNKNOWN, label: "Always Ask" },
|
||||
{ id: SitePermissions.ALLOW, label: "Allow" },
|
||||
{ id: SitePermissions.BLOCK, label: "Block" },
|
||||
]
|
||||
});
|
||||
|
||||
// check that removed permissions (State.UNKNOWN) are skipped
|
||||
SitePermissions.remove(uri, "camera");
|
||||
permissions = SitePermissions.getPermissionDetailsByURI(uri);
|
||||
|
||||
camera = permissions.find(({id}) => id === "camera");
|
||||
Assert.equal(camera, undefined);
|
||||
|
||||
// check that different available state values are represented
|
||||
|
||||
let cookie = permissions.find(({id}) => id === "cookie");
|
||||
Assert.deepEqual(cookie, {
|
||||
id: "cookie",
|
||||
label: "Set Cookies",
|
||||
state: SitePermissions.SESSION,
|
||||
availableStates: [
|
||||
{ id: SitePermissions.ALLOW, label: "Allow" },
|
||||
{ id: SitePermissions.SESSION, label: "Allow for Session" },
|
||||
{ id: SitePermissions.BLOCK, label: "Block" },
|
||||
]
|
||||
});
|
||||
|
||||
let popup = permissions.find(({id}) => id === "popup");
|
||||
Assert.deepEqual(popup, {
|
||||
id: "popup",
|
||||
label: "Open Pop-up Windows",
|
||||
state: SitePermissions.BLOCK,
|
||||
availableStates: [
|
||||
{ id: SitePermissions.ALLOW, label: "Allow" },
|
||||
{ id: SitePermissions.BLOCK, label: "Block" },
|
||||
]
|
||||
});
|
||||
|
||||
SitePermissions.remove(uri, "cookie");
|
||||
SitePermissions.remove(uri, "popup");
|
||||
});
|
||||
|
|
|
@ -20,6 +20,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
|||
"resource://gre/modules/PluralForm.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
|
||||
"resource:///modules/SitePermissions.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
|
||||
return Services.strings.createBundle("chrome://branding/locale/brand.properties");
|
||||
|
@ -335,6 +337,17 @@ function getHost(uri, href) {
|
|||
function prompt(aBrowser, aRequest) {
|
||||
let { audioDevices, videoDevices, sharingScreen, sharingAudio,
|
||||
requestTypes } = aRequest;
|
||||
|
||||
// If the user has already denied access once in this tab,
|
||||
// deny again without even showing the notification icon.
|
||||
if ((audioDevices.length && SitePermissions
|
||||
.get(null, "microphone", aBrowser).state == SitePermissions.BLOCK) ||
|
||||
(videoDevices.length && SitePermissions
|
||||
.get(null, sharingScreen ? "screen" : "camera", aBrowser).state == SitePermissions.BLOCK)) {
|
||||
denyRequest(aBrowser, aRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = Services.io.newURI(aRequest.documentURI);
|
||||
let host = getHost(uri);
|
||||
let chromeDoc = aBrowser.ownerDocument;
|
||||
|
@ -375,13 +388,16 @@ function prompt(aBrowser, aRequest) {
|
|||
accessKey: stringBundle.getString("getUserMedia.dontAllow.accesskey"),
|
||||
callback(aState) {
|
||||
denyRequest(notification.browser, aRequest);
|
||||
let scope = SitePermissions.SCOPE_TEMPORARY;
|
||||
if (aState && aState.checkboxChecked) {
|
||||
let perms = Services.perms;
|
||||
if (audioDevices.length)
|
||||
perms.add(uri, "microphone", perms.DENY_ACTION);
|
||||
if (videoDevices.length)
|
||||
perms.add(uri, sharingScreen ? "screen" : "camera", perms.DENY_ACTION);
|
||||
scope = SitePermissions.SCOPE_PERSISTENT;
|
||||
}
|
||||
if (audioDevices.length)
|
||||
SitePermissions.set(uri, "microphone",
|
||||
SitePermissions.BLOCK, scope, notification.browser);
|
||||
if (videoDevices.length)
|
||||
SitePermissions.set(uri, sharingScreen ? "screen" : "camera",
|
||||
SitePermissions.BLOCK, scope, notification.browser);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -438,45 +454,39 @@ function prompt(aBrowser, aRequest) {
|
|||
if (aTopic != "showing")
|
||||
return false;
|
||||
|
||||
// DENY_ACTION is handled immediately by MediaManager, but handling
|
||||
// of ALLOW_ACTION is delayed until the popupshowing event
|
||||
// BLOCK is handled immediately by MediaManager if it has been set
|
||||
// persistently in the permission manager. If it has been set on the tab,
|
||||
// it is handled synchronously before we add the notification.
|
||||
// Handling of ALLOW is delayed until the popupshowing event,
|
||||
// to avoid granting permissions automatically to background tabs.
|
||||
if (aRequest.secure) {
|
||||
let micAllowed =
|
||||
SitePermissions.get(uri, "microphone").state == SitePermissions.ALLOW;
|
||||
let camAllowed =
|
||||
SitePermissions.get(uri, "camera").state == SitePermissions.ALLOW;
|
||||
|
||||
let perms = Services.perms;
|
||||
|
||||
let micPerm = perms.testExactPermission(uri, "microphone");
|
||||
if (micPerm == perms.PROMPT_ACTION)
|
||||
micPerm = perms.UNKNOWN_ACTION;
|
||||
|
||||
let camPerm = perms.testExactPermission(uri, "camera");
|
||||
|
||||
let mediaManagerPerm =
|
||||
perms.testExactPermission(uri, "MediaManagerVideo");
|
||||
if (mediaManagerPerm) {
|
||||
perms.remove(uri, "MediaManagerVideo");
|
||||
}
|
||||
|
||||
if (camPerm == perms.PROMPT_ACTION)
|
||||
camPerm = perms.UNKNOWN_ACTION;
|
||||
|
||||
// Screen sharing shouldn't follow the camera permissions.
|
||||
if (videoDevices.length && sharingScreen)
|
||||
camPerm = perms.UNKNOWN_ACTION;
|
||||
camAllowed = false;
|
||||
|
||||
// We don't check that permissions are set to ALLOW_ACTION in this
|
||||
// test; only that they are set. This is because if audio is allowed
|
||||
// and video is denied persistently, we don't want to show the prompt,
|
||||
// and will grant audio access immediately.
|
||||
if ((!audioDevices.length || micPerm) && (!videoDevices.length || camPerm)) {
|
||||
if ((!audioDevices.length || micAllowed) &&
|
||||
(!videoDevices.length || camAllowed)) {
|
||||
// All permissions we were about to request are already persistently set.
|
||||
let allowedDevices = [];
|
||||
if (videoDevices.length && camPerm == perms.ALLOW_ACTION) {
|
||||
if (videoDevices.length && camAllowed) {
|
||||
allowedDevices.push(videoDevices[0].deviceIndex);
|
||||
Services.perms.add(uri, "MediaManagerVideo",
|
||||
Services.perms.ALLOW_ACTION,
|
||||
Services.perms.EXPIRE_SESSION);
|
||||
}
|
||||
if (audioDevices.length && micPerm == perms.ALLOW_ACTION)
|
||||
if (audioDevices.length && micAllowed)
|
||||
allowedDevices.push(audioDevices[0].deviceIndex);
|
||||
|
||||
// Remember on which URIs we found persistent permissions so that we
|
||||
|
@ -670,21 +680,24 @@ function prompt(aBrowser, aRequest) {
|
|||
// (it's really one-shot, not for the entire session)
|
||||
perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
|
||||
perms.EXPIRE_SESSION);
|
||||
}
|
||||
if (remember) {
|
||||
perms.add(uri, "camera",
|
||||
allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
|
||||
if (remember)
|
||||
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
|
||||
} else {
|
||||
let scope = remember ? SitePermissions.SCOPE_PERSISTENT : SitePermissions.SCOPE_TEMPORARY;
|
||||
SitePermissions.set(uri, "camera", SitePermissions.BLOCK, scope, aBrowser);
|
||||
}
|
||||
}
|
||||
if (audioDevices.length) {
|
||||
if (!sharingAudio) {
|
||||
let audioDeviceIndex = doc.getElementById("webRTC-selectMicrophone-menulist").value;
|
||||
let allowMic = audioDeviceIndex != "-1";
|
||||
if (allowMic)
|
||||
if (allowMic) {
|
||||
allowedDevices.push(audioDeviceIndex);
|
||||
if (remember) {
|
||||
perms.add(uri, "microphone",
|
||||
allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
|
||||
if (remember)
|
||||
SitePermissions.set(uri, "microphone", SitePermissions.ALLOW);
|
||||
} else {
|
||||
let scope = remember ? SitePermissions.SCOPE_PERSISTENT : SitePermissions.SCOPE_TEMPORARY;
|
||||
SitePermissions.set(uri, "microphone", SitePermissions.BLOCK, scope, aBrowser);
|
||||
}
|
||||
} else {
|
||||
// Only one device possible for audio capture.
|
||||
|
|
|
@ -93,11 +93,12 @@ this.ControlCenter = {
|
|||
|
||||
allPermissions: {
|
||||
applyConfig: Task.async(function* () {
|
||||
// there are 3 possible non-default permission states, so we alternate between them
|
||||
let states = [SitePermissions.ALLOW, SitePermissions.BLOCK, SitePermissions.SESSION];
|
||||
// TODO: (Bug 1330601) Rewrite this to consider temporary (TAB) permission states.
|
||||
// There are 2 possible non-default permission states, so we alternate between them.
|
||||
let states = [SitePermissions.ALLOW, SitePermissions.BLOCK];
|
||||
let uri = Services.io.newURI(PERMISSIONS_PAGE)
|
||||
SitePermissions.listPermissions().forEach(function(permission, index) {
|
||||
SitePermissions.set(uri, permission, states[index % 3]);
|
||||
SitePermissions.set(uri, permission, states[index % 2]);
|
||||
});
|
||||
|
||||
yield loadPage(PERMISSIONS_PAGE);
|
||||
|
|
|
@ -2,6 +2,17 @@
|
|||
* 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/. */
|
||||
|
||||
Current version of react-redux: 5.0.1 (last upgrade in bug 1326137)
|
||||
|
||||
How to upgrade:
|
||||
1. git clone https://github.com/reactjs/react-redux - clone the repo
|
||||
2. git checkout v5.0.1 - checkout the right version tag
|
||||
3. npm install - compile the sources to a JS module file
|
||||
4. cp dist/react-redux.js devtools/client/shared/vendor - copy the unminified JS file
|
||||
5. update the import path in the react-redux.js file - see below
|
||||
6. update the current version in this file
|
||||
|
||||
UPDATING THE IMPORT PATHS
|
||||
|
||||
"react-redux" uses UMD style loading to work in many different environments.
|
||||
It assumes that "react" and "redux" are both included via `require("react")`
|
||||
|
|
|
@ -2,14 +2,11 @@
|
|||
* 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/. */
|
||||
|
||||
Current version of redux: 3.6.0 (last upgrade in bug 1326137)
|
||||
|
||||
REDUX_UPGRADING
|
||||
|
||||
Current version of redux : 3.3.0
|
||||
|
||||
1 - grab the unminified version of redux on npm. For release 3.3.0 for instance,
|
||||
https://npmcdn.com/redux@3.3.0/dist/redux.js
|
||||
|
||||
2 - replace the content of devtools/client/shared/vendor
|
||||
|
||||
3 - update the current version in this file
|
||||
How to upgrade:
|
||||
1. git clone https://github.com/reactjs/redux - clone the repo
|
||||
2. git checkout v3.6.0 - checkout the right version tag
|
||||
3. npm install - compile the sources to a JS module file
|
||||
4. cp dist/redux.js devtools/client/shared/vendor - copy the unminified JS file
|
||||
5. update the current version in this file
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -63,15 +63,15 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
var _createStore2 = _interopRequireDefault(_createStore);
|
||||
|
||||
var _combineReducers = __webpack_require__(7);
|
||||
var _combineReducers = __webpack_require__(8);
|
||||
|
||||
var _combineReducers2 = _interopRequireDefault(_combineReducers);
|
||||
|
||||
var _bindActionCreators = __webpack_require__(6);
|
||||
var _bindActionCreators = __webpack_require__(7);
|
||||
|
||||
var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators);
|
||||
|
||||
var _applyMiddleware = __webpack_require__(5);
|
||||
var _applyMiddleware = __webpack_require__(6);
|
||||
|
||||
var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware);
|
||||
|
||||
|
@ -83,7 +83,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
var _warning2 = _interopRequireDefault(_warning);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
/*
|
||||
* This is a dummy function to check if the function name has been altered by minification.
|
||||
|
@ -92,14 +92,14 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
function isCrushed() {}
|
||||
|
||||
if (("development") !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') {
|
||||
(0, _warning2["default"])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
|
||||
(0, _warning2['default'])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
|
||||
}
|
||||
|
||||
exports.createStore = _createStore2["default"];
|
||||
exports.combineReducers = _combineReducers2["default"];
|
||||
exports.bindActionCreators = _bindActionCreators2["default"];
|
||||
exports.applyMiddleware = _applyMiddleware2["default"];
|
||||
exports.compose = _compose2["default"];
|
||||
exports.createStore = _createStore2['default'];
|
||||
exports.combineReducers = _combineReducers2['default'];
|
||||
exports.bindActionCreators = _bindActionCreators2['default'];
|
||||
exports.applyMiddleware = _applyMiddleware2['default'];
|
||||
exports.compose = _compose2['default'];
|
||||
|
||||
/***/ },
|
||||
/* 1 */
|
||||
|
@ -110,25 +110,34 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
exports.__esModule = true;
|
||||
exports["default"] = compose;
|
||||
/**
|
||||
* Composes single-argument functions from right to left.
|
||||
* Composes single-argument functions from right to left. The rightmost
|
||||
* function can take multiple arguments as it provides the signature for
|
||||
* the resulting composite function.
|
||||
*
|
||||
* @param {...Function} funcs The functions to compose.
|
||||
* @returns {Function} A function obtained by composing functions from right to
|
||||
* left. For example, compose(f, g, h) is identical to arg => f(g(h(arg))).
|
||||
* @returns {Function} A function obtained by composing the argument functions
|
||||
* from right to left. For example, compose(f, g, h) is identical to doing
|
||||
* (...args) => f(g(h(...args))).
|
||||
*/
|
||||
|
||||
function compose() {
|
||||
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
|
||||
funcs[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
if (funcs.length === 0) {
|
||||
return function (arg) {
|
||||
return arg;
|
||||
};
|
||||
}
|
||||
|
||||
if (funcs.length === 1) {
|
||||
return funcs[0];
|
||||
}
|
||||
|
||||
var last = funcs[funcs.length - 1];
|
||||
var rest = funcs.slice(0, -1);
|
||||
return function () {
|
||||
if (funcs.length === 0) {
|
||||
return arguments.length <= 0 ? undefined : arguments[0];
|
||||
}
|
||||
|
||||
var last = funcs[funcs.length - 1];
|
||||
var rest = funcs.slice(0, -1);
|
||||
|
||||
return rest.reduceRight(function (composed, f) {
|
||||
return f(composed);
|
||||
}, last.apply(undefined, arguments));
|
||||
|
@ -143,13 +152,17 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
exports.__esModule = true;
|
||||
exports.ActionTypes = undefined;
|
||||
exports["default"] = createStore;
|
||||
exports['default'] = createStore;
|
||||
|
||||
var _isPlainObject = __webpack_require__(4);
|
||||
var _isPlainObject = __webpack_require__(5);
|
||||
|
||||
var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
||||
var _symbolObservable = __webpack_require__(17);
|
||||
|
||||
var _symbolObservable2 = _interopRequireDefault(_symbolObservable);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
/**
|
||||
* These are private action types reserved by Redux.
|
||||
|
@ -172,7 +185,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
* @param {Function} reducer A function that returns the next state tree, given
|
||||
* the current state tree and the action to handle.
|
||||
*
|
||||
* @param {any} [initialState] The initial state. You may optionally specify it
|
||||
* @param {any} [preloadedState] The initial state. You may optionally specify it
|
||||
* to hydrate the state from the server in universal apps, or to restore a
|
||||
* previously serialized user session.
|
||||
* If you use `combineReducers` to produce the root reducer function, this must be
|
||||
|
@ -186,10 +199,12 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
* @returns {Store} A Redux store that lets you read the state, dispatch actions
|
||||
* and subscribe to changes.
|
||||
*/
|
||||
function createStore(reducer, initialState, enhancer) {
|
||||
if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
|
||||
enhancer = initialState;
|
||||
initialState = undefined;
|
||||
function createStore(reducer, preloadedState, enhancer) {
|
||||
var _ref2;
|
||||
|
||||
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
|
||||
enhancer = preloadedState;
|
||||
preloadedState = undefined;
|
||||
}
|
||||
|
||||
if (typeof enhancer !== 'undefined') {
|
||||
|
@ -197,7 +212,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
throw new Error('Expected the enhancer to be a function.');
|
||||
}
|
||||
|
||||
return enhancer(createStore)(reducer, initialState);
|
||||
return enhancer(createStore)(reducer, preloadedState);
|
||||
}
|
||||
|
||||
if (typeof reducer !== 'function') {
|
||||
|
@ -205,7 +220,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
var currentReducer = reducer;
|
||||
var currentState = initialState;
|
||||
var currentState = preloadedState;
|
||||
var currentListeners = [];
|
||||
var nextListeners = currentListeners;
|
||||
var isDispatching = false;
|
||||
|
@ -239,7 +254,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
* However, the next `dispatch()` call, whether nested or not, will use a more
|
||||
* recent snapshot of the subscription list.
|
||||
*
|
||||
* 2. The listener should not expect to see all states changes, as the state
|
||||
* 2. The listener should not expect to see all state changes, as the state
|
||||
* might have been updated multiple times during a nested `dispatch()` before
|
||||
* the listener is called. It is, however, guaranteed that all subscribers
|
||||
* registered before the `dispatch()` started will be called with the latest
|
||||
|
@ -297,7 +312,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
* return something else (for example, a Promise you can await).
|
||||
*/
|
||||
function dispatch(action) {
|
||||
if (!(0, _isPlainObject2["default"])(action)) {
|
||||
if (!(0, _isPlainObject2['default'])(action)) {
|
||||
throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
|
||||
}
|
||||
|
||||
|
@ -343,17 +358,56 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
dispatch({ type: ActionTypes.INIT });
|
||||
}
|
||||
|
||||
/**
|
||||
* Interoperability point for observable/reactive libraries.
|
||||
* @returns {observable} A minimal observable of state changes.
|
||||
* For more information, see the observable proposal:
|
||||
* https://github.com/zenparsing/es-observable
|
||||
*/
|
||||
function observable() {
|
||||
var _ref;
|
||||
|
||||
var outerSubscribe = subscribe;
|
||||
return _ref = {
|
||||
/**
|
||||
* The minimal observable subscription method.
|
||||
* @param {Object} observer Any object that can be used as an observer.
|
||||
* The observer object should have a `next` method.
|
||||
* @returns {subscription} An object with an `unsubscribe` method that can
|
||||
* be used to unsubscribe the observable from the store, and prevent further
|
||||
* emission of values from the observable.
|
||||
*/
|
||||
subscribe: function subscribe(observer) {
|
||||
if (typeof observer !== 'object') {
|
||||
throw new TypeError('Expected the observer to be an object.');
|
||||
}
|
||||
|
||||
function observeState() {
|
||||
if (observer.next) {
|
||||
observer.next(getState());
|
||||
}
|
||||
}
|
||||
|
||||
observeState();
|
||||
var unsubscribe = outerSubscribe(observeState);
|
||||
return { unsubscribe: unsubscribe };
|
||||
}
|
||||
}, _ref[_symbolObservable2['default']] = function () {
|
||||
return this;
|
||||
}, _ref;
|
||||
}
|
||||
|
||||
// When a store is created, an "INIT" action is dispatched so that every
|
||||
// reducer returns their initial state. This effectively populates
|
||||
// the initial state tree.
|
||||
dispatch({ type: ActionTypes.INIT });
|
||||
|
||||
return {
|
||||
return _ref2 = {
|
||||
dispatch: dispatch,
|
||||
subscribe: subscribe,
|
||||
getState: getState,
|
||||
replaceReducer: replaceReducer
|
||||
};
|
||||
}, _ref2[_symbolObservable2['default']] = observable, _ref2;
|
||||
}
|
||||
|
||||
/***/ },
|
||||
|
@ -363,7 +417,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
exports["default"] = warning;
|
||||
exports['default'] = warning;
|
||||
/**
|
||||
* Prints a warning in the console if it exists.
|
||||
*
|
||||
|
@ -377,8 +431,9 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
/* eslint-enable no-console */
|
||||
try {
|
||||
// This error was thrown as a convenience so that you can use this stack
|
||||
// to find the callsite that caused this warning to fire.
|
||||
// This error was thrown as a convenience so that if you enable
|
||||
// "break on all exceptions" in your console,
|
||||
// it would pause the execution at this line.
|
||||
throw new Error(message);
|
||||
/* eslint-disable no-empty */
|
||||
} catch (e) {}
|
||||
|
@ -389,36 +444,45 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
/* 4 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
var isHostObject = __webpack_require__(8),
|
||||
isObjectLike = __webpack_require__(9);
|
||||
var root = __webpack_require__(15);
|
||||
|
||||
/** Built-in value references. */
|
||||
var Symbol = root.Symbol;
|
||||
|
||||
module.exports = Symbol;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 5 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
var baseGetTag = __webpack_require__(9),
|
||||
getPrototype = __webpack_require__(11),
|
||||
isObjectLike = __webpack_require__(16);
|
||||
|
||||
/** `Object#toString` result references. */
|
||||
var objectTag = '[object Object]';
|
||||
|
||||
/** Used for built-in method references. */
|
||||
var objectProto = Object.prototype;
|
||||
var funcProto = Function.prototype,
|
||||
objectProto = Object.prototype;
|
||||
|
||||
/** Used to resolve the decompiled source of functions. */
|
||||
var funcToString = Function.prototype.toString;
|
||||
var funcToString = funcProto.toString;
|
||||
|
||||
/** Used to check objects for own properties. */
|
||||
var hasOwnProperty = objectProto.hasOwnProperty;
|
||||
|
||||
/** Used to infer the `Object` constructor. */
|
||||
var objectCtorString = funcToString.call(Object);
|
||||
|
||||
/**
|
||||
* Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
|
||||
* of values.
|
||||
*/
|
||||
var objectToString = objectProto.toString;
|
||||
|
||||
/** Built-in value references. */
|
||||
var getPrototypeOf = Object.getPrototypeOf;
|
||||
|
||||
/**
|
||||
* Checks if `value` is a plain object, that is, an object created by the
|
||||
* `Object` constructor or one with a `[[Prototype]]` of `null`.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 0.8.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
|
||||
|
@ -441,40 +505,38 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
* // => true
|
||||
*/
|
||||
function isPlainObject(value) {
|
||||
if (!isObjectLike(value) || objectToString.call(value) != objectTag || isHostObject(value)) {
|
||||
if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
|
||||
return false;
|
||||
}
|
||||
var proto = objectProto;
|
||||
if (typeof value.constructor == 'function') {
|
||||
proto = getPrototypeOf(value);
|
||||
}
|
||||
var proto = getPrototype(value);
|
||||
if (proto === null) {
|
||||
return true;
|
||||
}
|
||||
var Ctor = proto.constructor;
|
||||
return (typeof Ctor == 'function' &&
|
||||
Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
|
||||
var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
|
||||
return typeof Ctor == 'function' && Ctor instanceof Ctor &&
|
||||
funcToString.call(Ctor) == objectCtorString;
|
||||
}
|
||||
|
||||
module.exports = isPlainObject;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 5 */
|
||||
/* 6 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
|
||||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||
|
||||
exports.__esModule = true;
|
||||
exports["default"] = applyMiddleware;
|
||||
exports['default'] = applyMiddleware;
|
||||
|
||||
var _compose = __webpack_require__(1);
|
||||
|
||||
var _compose2 = _interopRequireDefault(_compose);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
/**
|
||||
* Creates a store enhancer that applies middleware to the dispatch method
|
||||
|
@ -498,8 +560,8 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
return function (createStore) {
|
||||
return function (reducer, initialState, enhancer) {
|
||||
var store = createStore(reducer, initialState, enhancer);
|
||||
return function (reducer, preloadedState, enhancer) {
|
||||
var store = createStore(reducer, preloadedState, enhancer);
|
||||
var _dispatch = store.dispatch;
|
||||
var chain = [];
|
||||
|
||||
|
@ -512,7 +574,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
chain = middlewares.map(function (middleware) {
|
||||
return middleware(middlewareAPI);
|
||||
});
|
||||
_dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch);
|
||||
_dispatch = _compose2['default'].apply(undefined, chain)(store.dispatch);
|
||||
|
||||
return _extends({}, store, {
|
||||
dispatch: _dispatch
|
||||
|
@ -522,13 +584,13 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
/***/ },
|
||||
/* 6 */
|
||||
/* 7 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
exports["default"] = bindActionCreators;
|
||||
exports['default'] = bindActionCreators;
|
||||
function bindActionCreator(actionCreator, dispatch) {
|
||||
return function () {
|
||||
return dispatch(actionCreator.apply(undefined, arguments));
|
||||
|
@ -578,17 +640,17 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
/***/ },
|
||||
/* 7 */
|
||||
/* 8 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
exports["default"] = combineReducers;
|
||||
exports['default'] = combineReducers;
|
||||
|
||||
var _createStore = __webpack_require__(2);
|
||||
|
||||
var _isPlainObject = __webpack_require__(4);
|
||||
var _isPlainObject = __webpack_require__(5);
|
||||
|
||||
var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
|
||||
|
||||
|
@ -596,29 +658,33 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
var _warning2 = _interopRequireDefault(_warning);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
function getUndefinedStateErrorMessage(key, action) {
|
||||
var actionType = action && action.type;
|
||||
var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
|
||||
|
||||
return 'Reducer "' + key + '" returned undefined handling ' + actionName + '. ' + 'To ignore an action, you must explicitly return the previous state.';
|
||||
return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state.';
|
||||
}
|
||||
|
||||
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action) {
|
||||
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
|
||||
var reducerKeys = Object.keys(reducers);
|
||||
var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'initialState argument passed to createStore' : 'previous state received by the reducer';
|
||||
var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';
|
||||
|
||||
if (reducerKeys.length === 0) {
|
||||
return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';
|
||||
}
|
||||
|
||||
if (!(0, _isPlainObject2["default"])(inputState)) {
|
||||
if (!(0, _isPlainObject2['default'])(inputState)) {
|
||||
return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"');
|
||||
}
|
||||
|
||||
var unexpectedKeys = Object.keys(inputState).filter(function (key) {
|
||||
return !reducers.hasOwnProperty(key);
|
||||
return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];
|
||||
});
|
||||
|
||||
unexpectedKeys.forEach(function (key) {
|
||||
unexpectedKeyCache[key] = true;
|
||||
});
|
||||
|
||||
if (unexpectedKeys.length > 0) {
|
||||
|
@ -663,12 +729,23 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
var finalReducers = {};
|
||||
for (var i = 0; i < reducerKeys.length; i++) {
|
||||
var key = reducerKeys[i];
|
||||
|
||||
if (true) {
|
||||
if (typeof reducers[key] === 'undefined') {
|
||||
(0, _warning2['default'])('No reducer provided for key "' + key + '"');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reducers[key] === 'function') {
|
||||
finalReducers[key] = reducers[key];
|
||||
}
|
||||
}
|
||||
var finalReducerKeys = Object.keys(finalReducers);
|
||||
|
||||
if (true) {
|
||||
var unexpectedKeyCache = {};
|
||||
}
|
||||
|
||||
var sanityError;
|
||||
try {
|
||||
assertReducerSanity(finalReducers);
|
||||
|
@ -677,7 +754,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
return function combination() {
|
||||
var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
|
||||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
var action = arguments[1];
|
||||
|
||||
if (sanityError) {
|
||||
|
@ -685,9 +762,9 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
if (true) {
|
||||
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action);
|
||||
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
|
||||
if (warningMessage) {
|
||||
(0, _warning2["default"])(warningMessage);
|
||||
(0, _warning2['default'])(warningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -710,33 +787,180 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
/***/ },
|
||||
/* 8 */
|
||||
/***/ function(module, exports) {
|
||||
/* 9 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
var Symbol = __webpack_require__(4),
|
||||
getRawTag = __webpack_require__(12),
|
||||
objectToString = __webpack_require__(13);
|
||||
|
||||
/** `Object#toString` result references. */
|
||||
var nullTag = '[object Null]',
|
||||
undefinedTag = '[object Undefined]';
|
||||
|
||||
/** Built-in value references. */
|
||||
var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
|
||||
|
||||
/**
|
||||
* Checks if `value` is a host object in IE < 9.
|
||||
* The base implementation of `getTag` without fallbacks for buggy environments.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
|
||||
* @param {*} value The value to query.
|
||||
* @returns {string} Returns the `toStringTag`.
|
||||
*/
|
||||
function isHostObject(value) {
|
||||
// Many host objects are `Object` objects that can coerce to strings
|
||||
// despite having improperly defined `toString` methods.
|
||||
var result = false;
|
||||
if (value != null && typeof value.toString != 'function') {
|
||||
try {
|
||||
result = !!(value + '');
|
||||
} catch (e) {}
|
||||
function baseGetTag(value) {
|
||||
if (value == null) {
|
||||
return value === undefined ? undefinedTag : nullTag;
|
||||
}
|
||||
return (symToStringTag && symToStringTag in Object(value))
|
||||
? getRawTag(value)
|
||||
: objectToString(value);
|
||||
}
|
||||
|
||||
module.exports = baseGetTag;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 10 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {/** Detect free variable `global` from Node.js. */
|
||||
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
|
||||
|
||||
module.exports = freeGlobal;
|
||||
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
|
||||
|
||||
/***/ },
|
||||
/* 11 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
var overArg = __webpack_require__(14);
|
||||
|
||||
/** Built-in value references. */
|
||||
var getPrototype = overArg(Object.getPrototypeOf, Object);
|
||||
|
||||
module.exports = getPrototype;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 12 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
var Symbol = __webpack_require__(4);
|
||||
|
||||
/** Used for built-in method references. */
|
||||
var objectProto = Object.prototype;
|
||||
|
||||
/** Used to check objects for own properties. */
|
||||
var hasOwnProperty = objectProto.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Used to resolve the
|
||||
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
||||
* of values.
|
||||
*/
|
||||
var nativeObjectToString = objectProto.toString;
|
||||
|
||||
/** Built-in value references. */
|
||||
var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
|
||||
|
||||
/**
|
||||
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to query.
|
||||
* @returns {string} Returns the raw `toStringTag`.
|
||||
*/
|
||||
function getRawTag(value) {
|
||||
var isOwn = hasOwnProperty.call(value, symToStringTag),
|
||||
tag = value[symToStringTag];
|
||||
|
||||
try {
|
||||
value[symToStringTag] = undefined;
|
||||
var unmasked = true;
|
||||
} catch (e) {}
|
||||
|
||||
var result = nativeObjectToString.call(value);
|
||||
if (unmasked) {
|
||||
if (isOwn) {
|
||||
value[symToStringTag] = tag;
|
||||
} else {
|
||||
delete value[symToStringTag];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = isHostObject;
|
||||
module.exports = getRawTag;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 9 */
|
||||
/* 13 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/** Used for built-in method references. */
|
||||
var objectProto = Object.prototype;
|
||||
|
||||
/**
|
||||
* Used to resolve the
|
||||
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
||||
* of values.
|
||||
*/
|
||||
var nativeObjectToString = objectProto.toString;
|
||||
|
||||
/**
|
||||
* Converts `value` to a string using `Object.prototype.toString`.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to convert.
|
||||
* @returns {string} Returns the converted string.
|
||||
*/
|
||||
function objectToString(value) {
|
||||
return nativeObjectToString.call(value);
|
||||
}
|
||||
|
||||
module.exports = objectToString;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 14 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/**
|
||||
* Creates a unary function that invokes `func` with its argument transformed.
|
||||
*
|
||||
* @private
|
||||
* @param {Function} func The function to wrap.
|
||||
* @param {Function} transform The argument transform.
|
||||
* @returns {Function} Returns the new function.
|
||||
*/
|
||||
function overArg(func, transform) {
|
||||
return function(arg) {
|
||||
return func(transform(arg));
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = overArg;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 15 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
var freeGlobal = __webpack_require__(10);
|
||||
|
||||
/** Detect free variable `self`. */
|
||||
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
|
||||
|
||||
/** Used as a reference to the global object. */
|
||||
var root = freeGlobal || freeSelf || Function('return this')();
|
||||
|
||||
module.exports = root;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 16 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/**
|
||||
|
@ -745,6 +969,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 4.0.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
||||
|
@ -763,12 +988,98 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
* // => false
|
||||
*/
|
||||
function isObjectLike(value) {
|
||||
return !!value && typeof value == 'object';
|
||||
return value != null && typeof value == 'object';
|
||||
}
|
||||
|
||||
module.exports = isObjectLike;
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 17 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
module.exports = __webpack_require__(18);
|
||||
|
||||
|
||||
/***/ },
|
||||
/* 18 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global, module) {'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _ponyfill = __webpack_require__(19);
|
||||
|
||||
var _ponyfill2 = _interopRequireDefault(_ponyfill);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
var root; /* global window */
|
||||
|
||||
|
||||
if (typeof self !== 'undefined') {
|
||||
root = self;
|
||||
} else if (typeof window !== 'undefined') {
|
||||
root = window;
|
||||
} else if (typeof global !== 'undefined') {
|
||||
root = global;
|
||||
} else if (true) {
|
||||
root = module;
|
||||
} else {
|
||||
root = Function('return this')();
|
||||
}
|
||||
|
||||
var result = (0, _ponyfill2['default'])(root);
|
||||
exports['default'] = result;
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(20)(module)))
|
||||
|
||||
/***/ },
|
||||
/* 19 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports['default'] = symbolObservablePonyfill;
|
||||
function symbolObservablePonyfill(root) {
|
||||
var result;
|
||||
var _Symbol = root.Symbol;
|
||||
|
||||
if (typeof _Symbol === 'function') {
|
||||
if (_Symbol.observable) {
|
||||
result = _Symbol.observable;
|
||||
} else {
|
||||
result = _Symbol('observable');
|
||||
_Symbol.observable = result;
|
||||
}
|
||||
} else {
|
||||
result = '@@observable';
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/***/ },
|
||||
/* 20 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
module.exports = function(module) {
|
||||
if(!module.webpackPolyfill) {
|
||||
module.deprecate = function() {};
|
||||
module.paths = [];
|
||||
// module.parent = undefined by default
|
||||
module.children = [];
|
||||
module.webpackPolyfill = 1;
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
|
||||
/***/ }
|
||||
/******/ ])
|
||||
});
|
||||
|
|
|
@ -151,8 +151,6 @@ NS_IMPL_ISUPPORTS(TabChildSHistoryListener,
|
|||
nsIPartialSHistoryListener,
|
||||
nsISupportsWeakReference)
|
||||
|
||||
static const CSSSize kDefaultViewportSize(980, 480);
|
||||
|
||||
static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
|
||||
|
||||
typedef nsDataHashtable<nsUint64HashKey, TabChild*> TabChildMap;
|
||||
|
|
|
@ -295,14 +295,13 @@ protected:
|
|||
|
||||
auto s = new S(master);
|
||||
|
||||
MOZ_ASSERT(master->mState != s->GetState() ||
|
||||
master->mState == DECODER_STATE_SEEKING);
|
||||
MOZ_ASSERT(GetState() != s->GetState() ||
|
||||
GetState() == DECODER_STATE_SEEKING);
|
||||
|
||||
SLOG("change state to: %s", ToStateStr(s->GetState()));
|
||||
|
||||
Exit();
|
||||
|
||||
master->mState = s->GetState();
|
||||
master->mStateObj.reset(s);
|
||||
return s->Enter(Move(aArgs)...);
|
||||
}
|
||||
|
@ -2739,7 +2738,6 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoder* aDecoder)
|
|||
|
||||
RefPtr<MediaDecoderStateMachine> self = this;
|
||||
OwnerThread()->Dispatch(NS_NewRunnableFunction([self] () {
|
||||
MOZ_ASSERT(self->mState == DECODER_STATE_DECODING_METADATA);
|
||||
MOZ_ASSERT(!self->mStateObj);
|
||||
auto s = new DecodeMetadataState(self);
|
||||
self->mStateObj.reset(s);
|
||||
|
@ -2768,8 +2766,6 @@ void MediaDecoderStateMachine::MaybeStartPlayback()
|
|||
MOZ_ASSERT(OnTaskQueue());
|
||||
// Should try to start playback only after decoding first frames.
|
||||
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
|
||||
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
|
||||
mState == DECODER_STATE_COMPLETED);
|
||||
|
||||
if (IsPlaying()) {
|
||||
// Logging this case is really spammy - don't do it.
|
||||
|
@ -2837,7 +2833,7 @@ const char*
|
|||
MediaDecoderStateMachine::ToStateStr()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
return ToStateStr(mState);
|
||||
return ToStateStr(mStateObj->GetState());
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::VolumeChanged()
|
||||
|
@ -3181,8 +3177,6 @@ bool MediaDecoderStateMachine::HasLowBufferedData()
|
|||
bool MediaDecoderStateMachine::HasLowBufferedData(int64_t aUsecs)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(mState >= DECODER_STATE_DECODING,
|
||||
"Must have loaded first frame for mBuffered to be valid");
|
||||
|
||||
// If we don't have a duration, mBuffered is probably not going to have
|
||||
// a useful buffered range. Return false here so that we don't get stuck in
|
||||
|
@ -3295,8 +3289,6 @@ RefPtr<ShutdownPromise>
|
|||
MediaDecoderStateMachine::FinishShutdown()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
|
||||
"How did we escape from the shutdown state?");
|
||||
DECODER_LOG("Shutting down state machine task queue");
|
||||
return OwnerThread()->BeginShutdown();
|
||||
}
|
||||
|
@ -3317,14 +3309,6 @@ MediaDecoderStateMachine::ResetDecode(TrackSet aTracks)
|
|||
MOZ_ASSERT(OnTaskQueue());
|
||||
DECODER_LOG("MediaDecoderStateMachine::Reset");
|
||||
|
||||
// We should be resetting because we're seeking, shutting down, or entering
|
||||
// dormant state. We could also be in the process of going dormant, and have
|
||||
// just switched to exiting dormant before we finished entering dormant,
|
||||
// hence the DECODING_NONE case below.
|
||||
MOZ_ASSERT(IsShutdown() ||
|
||||
mState == DECODER_STATE_SEEKING ||
|
||||
mState == DECODER_STATE_DORMANT);
|
||||
|
||||
// Assert that aTracks specifies to reset the video track because we
|
||||
// don't currently support resetting just the audio track.
|
||||
MOZ_ASSERT(aTracks.contains(TrackInfo::kVideoTrack));
|
||||
|
@ -3677,7 +3661,7 @@ MediaDecoderStateMachine::DumpDebugInfo()
|
|||
mStateObj->DumpDebugInfo();
|
||||
DUMP_LOG(
|
||||
"GetMediaTime=%lld GetClock=%lld mMediaSink=%p "
|
||||
"mState=%s mPlayState=%d mSentFirstFrameLoadedEvent=%d IsPlaying=%d "
|
||||
"state=%s mPlayState=%d mSentFirstFrameLoadedEvent=%d IsPlaying=%d "
|
||||
"mAudioStatus=%s mVideoStatus=%s mDecodedAudioEndTime=%lld mDecodedVideoEndTime=%lld "
|
||||
"mAudioCompleted=%d mVideoCompleted=%d",
|
||||
GetMediaTime(), mMediaSink->IsStarted() ? GetClock() : -1, mMediaSink.get(),
|
||||
|
|
|
@ -488,8 +488,6 @@ private:
|
|||
// the decoder, state machine, and main threads.
|
||||
MediaQueue<MediaData> mVideoQueue;
|
||||
|
||||
State mState = DECODER_STATE_DECODING_METADATA;
|
||||
|
||||
UniquePtr<StateObject> mStateObj;
|
||||
|
||||
media::TimeUnit Duration() const { MOZ_ASSERT(OnTaskQueue()); return mDuration.Ref().ref(); }
|
||||
|
|
|
@ -2021,6 +2021,19 @@ nsPermissionManager::TestPermissionFromPrincipal(nsIPrincipal* aPrincipal,
|
|||
return CommonTestPermission(aPrincipal, aType, aPermission, false, true);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPermissionManager::GetPermissionObjectForURI(nsIURI* aURI,
|
||||
const char* aType,
|
||||
bool aExactHostMatch,
|
||||
nsIPermission** aResult)
|
||||
{
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return GetPermissionObject(principal, aType, aExactHostMatch, aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
|
||||
const char* aType,
|
||||
|
|
|
@ -1447,7 +1447,7 @@ gfxPlatform::CreateOffscreenCanvasDrawTarget(const IntSize& aSize, SurfaceFormat
|
|||
already_AddRefed<DrawTarget>
|
||||
gfxPlatform::CreateOffscreenContentDrawTarget(const IntSize& aSize, SurfaceFormat aFormat)
|
||||
{
|
||||
NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend.");
|
||||
NS_ASSERTION(mContentBackend != BackendType::NONE, "No backend.");
|
||||
return CreateDrawTargetForBackend(mContentBackend, aSize, aFormat);
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ fuzzy-if(skiaContent,1,10000) == mask-basic-02.svg mask-basic-02-ref.svg
|
|||
== mask-transformed-02.svg pass.svg
|
||||
== mask-transformed-child-01.svg mask-transformed-child-01-ref.svg
|
||||
# fuzzy because of the differences between clipPath and mask clipping
|
||||
fuzzy(27,28) == mask-and-clipPath.html mask-and-clipPath-ref.html
|
||||
fuzzy(28,28) == mask-and-clipPath.html mask-and-clipPath-ref.html
|
||||
== mask-and-clipPath-2.svg pass.svg
|
||||
fuzzy-if(d2d||skiaContent,1,6400) == mask-type-01.svg mask-type-01-ref.svg
|
||||
fuzzy-if(d2d||skiaContent,1,6400) == mask-type-02.svg mask-type-01-ref.svg
|
||||
|
|
|
@ -172,7 +172,7 @@ fuzzy-if(!d2d,14,2) fuzzy-if(azureQuartz,1,6) fuzzy-if(skiaContent,1,200) == dyn
|
|||
|
||||
# text and masks
|
||||
fuzzy-if(skiaContent&&winWidget,50,224) HTTP(../..) == mask-applied.svg mask-applied-ref.svg
|
||||
fuzzy-if(skiaContent&&winWidget,105,56) HTTP(../..) == mask-content.svg mask-content-ref.svg
|
||||
fuzzy-if(skiaContent&&winWidget,105,112) HTTP(../..) == mask-content.svg mask-content-ref.svg
|
||||
fuzzy-if(skiaContent&&winWidget,53,112) HTTP(../..) == mask-content-2.svg mask-content-2-ref.svg
|
||||
|
||||
# text and clipPaths
|
||||
|
|
|
@ -17,7 +17,7 @@ pref(gfx.font_rendering.opentype_svg.enabled,true) fuzzy-if(gtkWidget,1,2268)
|
|||
pref(gfx.font_rendering.opentype_svg.enabled,true) fuzzy-if(skiaContent,2,200) == svg-glyph-paintnone.svg svg-glyph-paintnone-ref.svg
|
||||
pref(gfx.font_rendering.opentype_svg.enabled,true) fuzzy-if(skiaContent,2,200) == svg-glyph-cachedopacity.svg svg-glyph-cachedopacity-ref.svg
|
||||
pref(gfx.font_rendering.opentype_svg.enabled,true) fuzzy-if(cocoaWidget,255,100) == svg-glyph-objectvalue.svg svg-glyph-objectvalue-ref.svg
|
||||
pref(gfx.font_rendering.opentype_svg.enabled,true) fails == svg-glyph-mask.svg svg-glyph-mask-ref.svg # bug 872483
|
||||
pref(gfx.font_rendering.opentype_svg.enabled,true) skip == svg-glyph-mask.svg svg-glyph-mask-ref.svg # bug 872483
|
||||
pref(gfx.font_rendering.opentype_svg.enabled,true) == svg-glyph-paint-server.svg svg-glyph-paint-server-ref.svg
|
||||
pref(gfx.font_rendering.opentype_svg.enabled,true) == svg-glyph-transform.svg svg-glyph-transform-ref.svg
|
||||
pref(gfx.font_rendering.opentype_svg.enabled,true) == svg-glyph-extents.html svg-glyph-extents-ref.html
|
||||
|
|
|
@ -24,9 +24,9 @@ fuzzy-if(skiaContent||winWidget,1,20000) == mask-image-2.html mask-image-2-ref.h
|
|||
fuzzy-if(skiaContent||winWidget,1,43) == mask-image-3c.html mask-image-3-ref.html
|
||||
fuzzy-if(skiaContent||winWidget,1,43) == mask-image-3d.html mask-image-3-ref.html
|
||||
== mask-image-3e.html mask-image-3-ref.html
|
||||
fuzzy-if(skiaContent,50,50) == mask-image-3f.html mask-image-3-ref.html
|
||||
== mask-image-3g.html mask-image-3-ref.html
|
||||
pref(layout.css.clip-path-shapes.enabled,true) == mask-image-3h.html mask-image-3-ref.html
|
||||
fuzzy-if(skiaContent||winWidget,50,85) == mask-image-3f.html mask-image-3-ref.html
|
||||
fuzzy-if(skiaContent||winWidget,50,85) == mask-image-3g.html mask-image-3-ref.html
|
||||
pref(layout.css.clip-path-shapes.enabled,true) fuzzy-if(skiaContent||winWidget,1,3) == mask-image-3h.html mask-image-3-ref.html
|
||||
fuzzy-if(skiaContent,71,203) == mask-image-3i.html mask-image-3-ref.html
|
||||
== mask-image-4a.html blank.html
|
||||
== mask-image-4b.html blank.html
|
||||
|
|
|
@ -235,8 +235,9 @@ nsSVGMaskFrame::GetMaskForMaskedFrame(MaskParams& aParams)
|
|||
}
|
||||
|
||||
RefPtr<DrawTarget> maskDT =
|
||||
Factory::CreateDrawTarget(BackendType::CAIRO, maskSurfaceSize,
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
|
||||
maskSurfaceSize, SurfaceFormat::B8G8R8A8);
|
||||
|
||||
if (!maskDT || !maskDT->IsValid()) {
|
||||
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
||||
}
|
||||
|
|
|
@ -208,6 +208,23 @@ interface nsIPermissionManager : nsISupports
|
|||
uint32_t testExactPermanentPermission(in nsIPrincipal principal,
|
||||
in string type);
|
||||
|
||||
/**
|
||||
* Get the permission object associated with the given URI and action.
|
||||
* @param uri The URI
|
||||
* @param type A case-sensitive ASCII string identifying the consumer
|
||||
* @param exactHost If true, only the specific host will be matched,
|
||||
* @see testExactPermission. If false, subdomains will
|
||||
* also be searched, @see testPermission.
|
||||
* @returns The matching permission object, or null if no matching object
|
||||
* was found. No matching object is equivalent to UNKNOWN_ACTION.
|
||||
* @note Clients in general should prefer the test* methods unless they
|
||||
* need to know the specific stored details.
|
||||
* @note This method will always return null for the system principal.
|
||||
*/
|
||||
nsIPermission getPermissionObjectForURI(in nsIURI uri,
|
||||
in string type,
|
||||
in boolean exactHost);
|
||||
|
||||
/**
|
||||
* Get the permission object associated with the given principal and action.
|
||||
* @param principal The principal
|
||||
|
|
|
@ -108,14 +108,17 @@ class TestNavigate(WindowManagerMixin, MarionetteTestCase):
|
|||
self.assertTrue(self.marionette.execute_script(
|
||||
"return window.document.getElementById('someDiv') == undefined"))
|
||||
|
||||
@skip("Disabled due to Bug 977899")
|
||||
def test_navigate_frame(self):
|
||||
self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
|
||||
self.marionette.switch_to_frame(0)
|
||||
self.marionette.navigate(self.marionette.absolute_url("empty.html"))
|
||||
self.assertTrue('empty.html' in self.marionette.get_url())
|
||||
self.marionette.switch_to_frame()
|
||||
self.assertTrue('test_iframe.html' in self.marionette.get_url())
|
||||
def test_navigate_in_child_frame_changes_to_top(self):
|
||||
frame_html = self.marionette.absolute_url("frameset.html")
|
||||
|
||||
self.marionette.navigate(frame_html)
|
||||
frame = self.marionette.find_element(By.NAME, "third")
|
||||
self.marionette.switch_to_frame(frame)
|
||||
self.assertRaises(errors.NoSuchElementException,
|
||||
self.marionette.find_element, By.NAME, "third")
|
||||
|
||||
self.marionette.navigate(frame_html)
|
||||
self.marionette.find_element(By.NAME, "third")
|
||||
|
||||
@skip_if_mobile("Bug 1323755 - Socket timeout")
|
||||
def test_invalid_protocol(self):
|
||||
|
|
|
@ -129,16 +129,6 @@ class TestSwitchFrame(MarionetteTestCase):
|
|||
self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
|
||||
self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, -1)
|
||||
|
||||
def test_after_switching_to_child_frame_navigates_changes_top(self):
|
||||
frame_html = self.marionette.absolute_url("frameset.html")
|
||||
self.marionette.navigate(frame_html)
|
||||
frame = self.marionette.find_element(By.NAME, "third")
|
||||
self.marionette.switch_to_frame(frame)
|
||||
self.assertEqual("Unique title", self.marionette.title)
|
||||
test_html = self.marionette.absolute_url("test.html")
|
||||
self.marionette.navigate(test_html)
|
||||
self.assertEqual("Marionette Test", self.marionette.title)
|
||||
|
||||
def test_switch_to_parent_frame(self):
|
||||
frame_html = self.marionette.absolute_url("frameset.html")
|
||||
self.marionette.navigate(frame_html)
|
||||
|
|
|
@ -951,6 +951,10 @@ function get(msg) {
|
|||
let start = new Date().getTime();
|
||||
let {pageTimeout, url, command_id} = msg.json;
|
||||
|
||||
// We need to move to the top frame before navigating
|
||||
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
|
||||
curContainer.frame = content;
|
||||
|
||||
let docShell = curContainer.frame
|
||||
.document
|
||||
.defaultView
|
||||
|
@ -1070,12 +1074,6 @@ function get(msg) {
|
|||
navTimer.initWithCallback(onTimeout, pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
// in Firefox we need to move to the top frame before navigating
|
||||
if (!isB2G) {
|
||||
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
|
||||
curContainer.frame = content;
|
||||
}
|
||||
|
||||
if (loadEventExpected) {
|
||||
addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
}
|
||||
|
|
|
@ -2401,7 +2401,9 @@ nsWindow::OnConfigureEvent(GtkWidget *aWidget, GdkEventConfigure *aEvent)
|
|||
LOG(("configure event [%p] %d %d %d %d\n", (void *)this,
|
||||
aEvent->x, aEvent->y, aEvent->width, aEvent->height));
|
||||
|
||||
mPendingConfigures--;
|
||||
if (mPendingConfigures > 0) {
|
||||
mPendingConfigures--;
|
||||
}
|
||||
|
||||
LayoutDeviceIntRect screenBounds = GetScreenBounds();
|
||||
|
||||
|
@ -4212,7 +4214,7 @@ nsWindow::NativeShow(bool aAction)
|
|||
event.height = allocation.height;
|
||||
|
||||
auto shellClass = GTK_WIDGET_GET_CLASS(mShell);
|
||||
for (int i = 0; i < mPendingConfigures; i++) {
|
||||
for (unsigned int i = 0; i < mPendingConfigures; i++) {
|
||||
Unused << shellClass->configure_event(mShell, &event);
|
||||
}
|
||||
mPendingConfigures = 0;
|
||||
|
|
|
@ -469,7 +469,7 @@ private:
|
|||
|
||||
// Upper bound on pending ConfigureNotify events to be dispatched to the
|
||||
// window. See bug 1225044.
|
||||
int mPendingConfigures;
|
||||
unsigned int mPendingConfigures;
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
RefPtr<mozilla::a11y::Accessible> mRootAccessible;
|
||||
|
|
Загрузка…
Ссылка в новой задаче