This commit is contained in:
Phil Ringnalda 2017-01-16 14:50:05 -08:00
Родитель 3be357c7ba 5a78aec0aa
Коммит b3c440155b
46 изменённых файлов: 2904 добавлений и 909 удалений

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

@ -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")`

17
devtools/client/shared/vendor/REDUX_UPGRADING поставляемый
Просмотреть файл

@ -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

1590
devtools/client/shared/vendor/react-redux.js поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

501
devtools/client/shared/vendor/redux.js поставляемый
Просмотреть файл

@ -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;