Commit 26f16f3b authored by Daniel Varga's avatar Daniel Varga
Browse files

Merge autoland to mozilla-central. a=merge

parents 65f9687e fd2da88b
......@@ -240,7 +240,13 @@ function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSec
}
function denyGUMRequest(aData) {
Services.obs.notifyObservers(null, "getUserMedia:response:deny", aData.callID);
let subject;
if (aData.noOSPermission) {
subject = "getUserMedia:response:noOSPermission";
} else {
subject = "getUserMedia:response:deny";
}
Services.obs.notifyObservers(null, subject, aData.callID);
if (!aData.windowID)
return;
......
......@@ -96,6 +96,10 @@ support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
[browser_urlbarOneOffs_searchSuggestions.js]
support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
[browser_urlbarOneOffs_settings.js]
support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
......
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
let gMaxResults;
add_task(async function init() {
Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true);
gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
// Add a search suggestion engine and move it to the front so that it appears
// as the first one-off.
let engine = await SearchTestUtils.promiseNewSearchEngine(
getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
Services.search.moveEngine(engine, 0);
registerCleanupFunction(async function() {
await hidePopup();
await PlacesUtils.history.clear();
});
await PlacesUtils.history.clear();
let visits = [];
for (let i = 0; i < gMaxResults; i++) {
visits.push({
uri: makeURI("http://example.com/browser_urlbarOneOffs.js/?" + i),
// TYPED so that the visit shows up when the urlbar's drop-down arrow is
// pressed.
transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
});
}
await PlacesTestUtils.addVisits(visits);
});
async function selectSettings(activateFn) {
await BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, async browser => {
gURLBar.focus();
EventUtils.synthesizeKey("KEY_ArrowDown");
await promisePopupShown(gURLBar.popup);
await waitForAutocompleteResultAt(gMaxResults - 1);
let promiseHidden = promisePopupHidden(gURLBar.popup);
let prefPaneLoaded = TestUtils.topicObserved("sync-pane-loaded", () => true);
activateFn();
await prefPaneLoaded;
await promiseHidden;
Assert.equal(gBrowser.contentWindow.history.state, "paneSearch",
"Should have opened the search preferences pane");
});
}
add_task(async function test_open_settings_with_enter() {
await selectSettings(() => {
EventUtils.synthesizeKey("KEY_ArrowUp");
Assert.equal(gURLBar.popup.oneOffSearchButtons.selectedButton.getAttribute("anonid"),
"search-settings-compact", "Should have selected the settings button");
EventUtils.synthesizeKey("KEY_Enter");
});
});
add_task(async function test_open_settings_with_click() {
await selectSettings(() => {
gURLBar.popup.oneOffSearchButtons.settingsButton.click();
});
});
async function hidePopup() {
EventUtils.synthesizeKey("KEY_Escape");
await promisePopupHidden(gURLBar.popup);
}
......@@ -652,9 +652,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
return;
}
BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(
event, this.userSelectionBehavior);
// Determine whether to use the selected one-off search button. In
// one-off search buttons parlance, "selected" means that the button
// has been navigated to via the keyboard. So we want to use it if
......@@ -673,6 +670,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
return;
}
BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(
event, this.userSelectionBehavior);
let where = openUILinkWhere || this._whereToOpen(event);
let url = this.value;
......
......@@ -23,6 +23,10 @@ XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
return Services.strings.createBundle("chrome://branding/locale/brand.properties");
});
XPCOMUtils.defineLazyServiceGetter(this, "OSPermissions",
"@mozilla.org/ospermissionrequest;1",
"nsIOSPermissionRequest");
var webrtcUI = {
peerConnectionBlockers: new Set(),
emitter: new EventEmitter(),
......@@ -300,6 +304,67 @@ function denyRequest(aBrowser, aRequest) {
windowID: aRequest.windowID});
}
//
// Deny the request because the browser does not have access to the
// camera or microphone due to OS security restrictions. The user may
// have granted camera/microphone access to the site, but not have
// allowed the browser access in OS settings.
//
function denyRequestNoPermission(aBrowser, aRequest) {
aBrowser.messageManager.sendAsyncMessage("webrtc:Deny",
{callID: aRequest.callID,
windowID: aRequest.windowID,
noOSPermission: true});
}
//
// Check if we have permission to access the camera and or microphone at the
// OS level. Triggers a request to access the device if access is needed and
// the permission state has not yet been determined.
//
async function checkOSPermission(camNeeded, micNeeded) {
let camStatus = {}, micStatus = {};
OSPermissions.getMediaCapturePermissionState(camStatus, micStatus);
if (camNeeded) {
let camPermission = camStatus.value;
let camAccessible = await checkAndGetOSPermission(camPermission,
OSPermissions.requestVideoCapturePermission);
if (!camAccessible) {
return false;
}
}
if (micNeeded) {
let micPermission = micStatus.value;
let micAccessible = await checkAndGetOSPermission(micPermission,
OSPermissions.requestAudioCapturePermission);
if (!micAccessible) {
return false;
}
}
return true;
}
//
// Given a device's permission, return true if the device is accessible. If
// the device's permission is not yet determined, request access to the device.
// |requestPermissionFunc| must return a promise that resolves with true
// if the device is accessible and false otherwise.
//
async function checkAndGetOSPermission(devicePermission,
requestPermissionFunc) {
if (devicePermission == OSPermissions.PERMISSION_STATE_DENIED ||
devicePermission == OSPermissions.PERMISSION_STATE_RESTRICTED) {
return false;
}
if (devicePermission == OSPermissions.PERMISSION_STATE_NOTDETERMINED) {
let deviceAllowed = await requestPermissionFunc();
if (!deviceAllowed) {
return false;
}
}
return true;
}
function getHostOrExtensionName(uri, href) {
let host;
try {
......@@ -517,10 +582,19 @@ function prompt(aBrowser, aRequest) {
browser._devicePermissionURIs = browser._devicePermissionURIs || [];
browser._devicePermissionURIs.push(uri);
let mm = browser.messageManager;
mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
windowID: aRequest.windowID,
devices: allowedDevices});
let camNeeded = videoDevices.length > 0;
let micNeeded = audioDevices.length > 0;
checkOSPermission(camNeeded, micNeeded).then((havePermission) => {
if (havePermission) {
let mm = browser.messageManager;
mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
windowID: aRequest.windowID,
devices: allowedDevices});
} else {
denyRequestNoPermission(browser, aRequest);
}
});
this.remove();
return true;
}
......@@ -717,7 +791,7 @@ function prompt(aBrowser, aRequest) {
if (!sharingAudio)
listDevices(micMenupopup, audioDevices);
this.mainAction.callback = function(aState) {
this.mainAction.callback = async function(aState) {
let remember = aState && aState.checkboxChecked;
let allowedDevices = [];
let perms = Services.perms;
......@@ -784,6 +858,14 @@ function prompt(aBrowser, aRequest) {
aBrowser._devicePermissionURIs.push(uri);
}
let camNeeded = videoDevices.length > 0;
let micNeeded = audioDevices.length > 0;
let havePermission = await checkOSPermission(camNeeded, micNeeded);
if (!havePermission) {
denyRequestNoPermission(notification.browser, aRequest);
return;
}
let mm = notification.browser.messageManager;
mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
windowID: aRequest.windowID,
......
......@@ -30,7 +30,7 @@ const RESPONSE_PREVIEW = L10N.getStr("responsePreview");
const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view";
/*
/**
* Response panel component
* Displays the GET parameters and POST data of a request
*/
......@@ -53,8 +53,7 @@ class ResponsePanel extends Component {
},
};
this.updateImageDimemsions = this.updateImageDimemsions.bind(this);
this.isJSON = this.isJSON.bind(this);
this.updateImageDimensions = this.updateImageDimensions.bind(this);
}
componentDidMount() {
......@@ -67,7 +66,7 @@ class ResponsePanel extends Component {
fetchNetworkUpdatePacket(connector.requestData, request, ["responseContent"]);
}
updateImageDimemsions({ target }) {
updateImageDimensions({ target }) {
this.setState({
imageDimensions: {
width: target.naturalWidth,
......@@ -76,20 +75,45 @@ class ResponsePanel extends Component {
});
}
// Handle json, which we tentatively identify by checking the MIME type
// for "json" after any word boundary. This works for the standard
// "application/json", and also for custom types like "x-bigcorp-json".
// Additionally, we also directly parse the response text content to
// verify whether it's json or not, to handle responses incorrectly
// labeled as text/plain instead.
/**
* This method checks that the response is base64 encoded by
* comparing these 2 values:
* 1. The original response
* 2. The value of doing a base64 decode on the
* response and then base64 encoding the result.
* If the values are different or an error is thrown,
* the method will return false.
*/
isBase64(response) {
try {
return btoa(atob(response)) == response;
} catch (err) {
return false;
}
}
/**
* Handle json, which we tentatively identify by checking the
* MIME type for "json" after any word boundary. This works
* for the standard "application/json", and also for custom
* types like "x-bigcorp-json". Additionally, we also
* directly parse the response text content to verify whether
* it's json or not, to handle responses incorrectly labeled
* as text/plain instead.
*/
isJSON(mimeType, response) {
let json, error;
try {
json = JSON.parse(response);
} catch (err) {
try {
json = JSON.parse(atob(response));
} catch (err64) {
if (this.isBase64(response)) {
try {
json = JSON.parse(atob(response));
} catch (err64) {
error = err;
}
} else {
error = err;
}
}
......@@ -150,7 +174,7 @@ class ResponsePanel extends Component {
img({
className: "response-image",
src: formDataURI(mimeType, encoding, text),
onLoad: this.updateImageDimemsions,
onLoad: this.updateImageDimensions,
}),
div({ className: "response-summary" },
div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_NAME),
......
......@@ -18,6 +18,7 @@ support-files =
html_json-b64.html
html_json-basic.html
html_json-custom-mime-test-page.html
html_json-empty.html
html_json-long-test-page.html
html_json-malformed-test-page.html
html_json-text-mime-test-page.html
......@@ -133,6 +134,7 @@ skip-if = (os == 'mac') # Bug 1479782
[browser_net_headers_sorted.js]
[browser_net_image-tooltip.js]
[browser_net_json-b64.js]
[browser_net_json-empty.js]
[browser_net_json-null.js]
[browser_net_json-long.js]
[browser_net_json-malformed.js]
......
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests if empty JSON responses are properly displayed.
*/
add_task(async function() {
const { tab, monitor } = await initNetMonitor(JSON_EMPTY_URL + "?name=empty");
info("Starting test... ");
const { document, store, windowRequire } = monitor.panelWin;
const { L10N } = windowRequire("devtools/client/netmonitor/src/utils/l10n");
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
store.dispatch(Actions.batchEnable(false));
// Execute requests.
await performRequests(monitor, tab, 1);
const onResponsePanelReady = waitForDOM(document, "#response-panel .CodeMirror-code");
store.dispatch(Actions.toggleNetworkDetails());
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector("#response-tab"));
await onResponsePanelReady;
const tabpanel = document.querySelector("#response-panel");
is(tabpanel.querySelectorAll(".tree-section").length, 2,
"There should be 2 tree sections displayed in this tabpanel.");
is(tabpanel.querySelectorAll(".empty-notice").length, 0,
"The empty notice should not be displayed in this tabpanel.");
is(tabpanel.querySelector(".response-error-header") === null, true,
"The response error header doesn't have the intended visibility.");
is(tabpanel.querySelector(".CodeMirror-code") === null, false,
"The response editor has the intended visibility.");
is(tabpanel.querySelector(".response-image-box") === null, true,
"The response image box doesn't have the intended visibility.");
const jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
"The response json view has the intended visibility.");
await teardown(monitor);
});
......@@ -20,11 +20,11 @@ add_task(async function() {
// Execute requests.
await performRequests(monitor, tab, 1);
const onReponsePanelReady = waitForDOM(document, "#response-panel .CodeMirror-code");
const onResponsePanelReady = waitForDOM(document, "#response-panel .CodeMirror-code");
store.dispatch(Actions.toggleNetworkDetails());
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector("#response-tab"));
await onReponsePanelReady;
await onResponsePanelReady;
checkResponsePanelDisplaysJSON();
......
......@@ -61,6 +61,7 @@ const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html
const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html";
const JSON_B64_URL = EXAMPLE_URL + "html_json-b64.html";
const JSON_BASIC_URL = EXAMPLE_URL + "html_json-basic.html";
const JSON_EMPTY_URL = EXAMPLE_URL + "html_json-empty.html";
const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
......
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>
<body>
<p>Empty JSON test page</p>
<script type="text/javascript">
/* exported performRequests */
"use strict";
function get(address, callback) {
const xhr = new XMLHttpRequest();
xhr.open("GET", address, true);
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
callback();
}
};
xhr.send(null);
}
function performRequests() {
// Forward the query parameter for this page to sjs_json-test-server
get("sjs_json-test-server.sjs" + window.location.search, function() {
// Done.
});
}
</script>
</body>
</html>
......@@ -21,5 +21,8 @@ function handleRequest(request, response) {
case "nogrip":
response.write("{\"obj\": {\"type\": \"string\" }}");
break;
case "empty":
response.write("{}");
break;
}
}
......@@ -622,7 +622,7 @@ Element::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
if (bindingURL) {
nsCOMPtr<nsIURI> uri = bindingURL->GetURI();
nsCOMPtr<nsIPrincipal> principal = bindingURL->mExtraData->GetPrincipal();
nsCOMPtr<nsIPrincipal> principal = bindingURL->mExtraData->Principal();
// We have a binding that must be installed.
bool dummy;
......
......@@ -3799,7 +3799,7 @@ nsIDocument::DefaultStyleAttrURLData()
mCachedURLData->BaseURI() != baseURI ||
mCachedURLData->GetReferrer() != docURI ||
mCachedURLData->GetReferrerPolicy() != policy ||
mCachedURLData->GetPrincipal() != principal) {
mCachedURLData->Principal() != principal) {
mCachedURLData = new URLExtraData(baseURI, docURI, principal, policy);
}
return mCachedURLData;
......
......@@ -2260,6 +2260,7 @@ MediaManager::Get() {
obs->AddObserver(sSingleton, "getUserMedia:privileged:allow", false);
obs->AddObserver(sSingleton, "getUserMedia:response:allow", false);
obs->AddObserver(sSingleton, "getUserMedia:response:deny", false);
obs->AddObserver(sSingleton, "getUserMedia:response:noOSPermission", false);
obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
}
// else MediaManager won't work properly and will leak (see bug 837874)
......@@ -3634,6 +3635,7 @@ MediaManager::Shutdown()
obs->RemoveObserver(this, "getUserMedia:privileged:allow");
obs->RemoveObserver(this, "getUserMedia:response:allow");
obs->RemoveObserver(this, "getUserMedia:response:deny");
obs->RemoveObserver(this, "getUserMedia:response:noOSPermission");
obs->RemoveObserver(this, "getUserMedia:revoke");
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
......@@ -3769,12 +3771,30 @@ MediaManager::SendPendingGUMRequest()
}
}
bool
IsGUMResponseNoAccess(const char* aTopic, MediaMgrError::Name& aErrorName)
{
if (!strcmp(aTopic, "getUserMedia:response:deny")) {
aErrorName = MediaMgrError::Name::NotAllowedError;
return true;
}
if (!strcmp(aTopic, "getUserMedia:response:noOSPermission")) {
aErrorName = MediaMgrError::Name::NotFoundError;
return true;
}
return false;
}
nsresult
MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(NS_IsMainThread());
MediaMgrError::Name gumNoAccessError = MediaMgrError::Name::NotAllowedError;
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
if (branch) {
......@@ -3860,12 +3880,12 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
MediaManager::PostTask(task.forget());
return NS_OK;
} else if (!strcmp(aTopic, "getUserMedia:response:deny")) {
} else if (IsGUMResponseNoAccess(aTopic, gumNoAccessError)) {
nsString key(aData);
RefPtr<GetUserMediaTask> task;
mActiveCallbacks.Remove(key, getter_AddRefs(task));
if (task) {
task->Denied(MediaMgrError::Name::NotAllowedError);
task->Denied(gumNoAccessError);
nsTArray<nsString>* array;
if (!mCallIds.Get(task->GetWindowID(), &array)) {
return NS_OK;
......
......@@ -3514,7 +3514,6 @@ PluginInstanceChild::PaintRectWithAlphaExtraction(const nsIntRect& aRect,
RefPtr<gfxImageSurface> blackImage;
gfxRect targetRect(rect.x, rect.y, rect.width, rect.height);
IntSize targetSize(rect.width, rect.height);
gfxPoint deviceOffset = -targetRect.TopLeft();
// We always use a temporary "white image"
whiteImage = new gfxImageSurface(targetSize, SurfaceFormat::X8R8G8B8_UINT32);
......@@ -3549,6 +3548,7 @@ PluginInstanceChild::PaintRectWithAlphaExtraction(const nsIntRect& aRect,
blackImage = image->GetSubimage(targetRect);
#else
gfxPoint deviceOffset = -targetRect.TopLeft();
// Paint onto white background
whiteImage->SetDeviceOffset(deviceOffset);
PaintRectToSurface(rect, whiteImage, Color(1.f, 1.f, 1.f));
......
......@@ -124,7 +124,6 @@ PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
, mChildPluginHWND(nullptr)
, mChildPluginsParentHWND(nullptr)
, mPluginWndProc(nullptr)
, mNestedEventState(false)
#endif // defined(XP_WIN)
#if defined(XP_MACOSX)
, mShWidth(0)
......
......@@ -402,7 +402,6 @@ private:
HWND mChildPluginHWND;
HWND mChildPluginsParentHWND;
WNDPROC mPluginWndProc;
bool mNestedEventState;
#endif // defined(XP_WIN)