Merge mozilla-central to autoland
@ -486,7 +486,6 @@ tags = fullscreen
|
||||
[browser_menuButtonBadgeManager.js]
|
||||
[browser_newTabDrop.js]
|
||||
[browser_newWindowDrop.js]
|
||||
skip-if = true # bug 1323276
|
||||
[browser_csp_block_all_mixedcontent.js]
|
||||
tags = mcb
|
||||
[browser_newwindow_focus.js]
|
||||
|
@ -25,7 +25,7 @@ add_task(function* test_setup() {
|
||||
0);
|
||||
CustomizableUI.ensureWidgetPlacedInWindow("new-window-button", window);
|
||||
registerCleanupFunction(function() {
|
||||
CustomizableUI.reset();
|
||||
CustomizableUI.removeWidgetFromArea("new-window-button");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
const {SessionSaver} = Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", {});
|
||||
const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
|
||||
|
||||
/**
|
||||
@ -84,6 +85,8 @@ add_task(function* dontTemporarilyShowAboutHome() {
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
|
||||
|
||||
yield SessionSaver.run();
|
||||
|
||||
windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
|
||||
win = SessionStore.undoCloseWindow(0);
|
||||
yield windowOpenedPromise;
|
||||
|
@ -8,4 +8,6 @@ support-files =
|
||||
skip-if = (os == "linux" && debug) # linux: bug 976544
|
||||
[browser_devices_get_user_media_anim.js]
|
||||
[browser_devices_get_user_media_in_frame.js]
|
||||
[browser_devices_get_user_media_screen.js]
|
||||
skip-if = (e10s && debug) || (os == "linux" && !debug) # bug 1320754 for e10s debug, and bug 1320994 for linux opt
|
||||
[browser_devices_get_user_media_tear_off_tab.js]
|
||||
|
@ -34,8 +34,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({audio: true, video: true});
|
||||
@ -64,8 +64,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "Microphone",
|
||||
"expected microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true},
|
||||
"expected microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({audio: true});
|
||||
@ -94,7 +94,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "Camera", "expected camera to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {video: true},
|
||||
"expected camera to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true});
|
||||
@ -136,8 +137,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true, audio: true});
|
||||
@ -164,20 +165,13 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true, audio: true});
|
||||
|
||||
info("reloading the web page");
|
||||
promise = promiseObserverCalled("recording-device-events");
|
||||
content.location.reload();
|
||||
yield promise;
|
||||
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield expectNoObserverCalled();
|
||||
yield checkNotSharing();
|
||||
yield reloadAndAssertClosedStreams();
|
||||
}
|
||||
},
|
||||
|
||||
@ -203,23 +197,22 @@ var gTests = [
|
||||
yield promiseMessage(expectedMessage, () => {
|
||||
activateSecondaryAction(aNever ? kActionNever : kActionAlways);
|
||||
});
|
||||
let expected = [];
|
||||
let expected = {};
|
||||
if (expectedMessage == "ok") {
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
if (aRequestVideo)
|
||||
expected.push("Camera");
|
||||
expected.video = true;
|
||||
if (aRequestAudio)
|
||||
expected.push("Microphone");
|
||||
expected = expected.join("And");
|
||||
expected.audio = true;
|
||||
}
|
||||
else {
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
expected = "none";
|
||||
}
|
||||
is((yield getMediaCaptureState()), expected,
|
||||
"expected " + expected + " to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), expected,
|
||||
"expected " + Object.keys(expected).join(" and ") +
|
||||
" to be shared");
|
||||
|
||||
function checkDevicePermissions(aDevice, aExpected) {
|
||||
let Perms = Services.perms;
|
||||
@ -302,14 +295,14 @@ var gTests = [
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
|
||||
// Check what's actually shared.
|
||||
let expected = [];
|
||||
let expected = {};
|
||||
if (aAllowVideo && aRequestVideo)
|
||||
expected.push("Camera");
|
||||
expected.video = true;
|
||||
if (aAllowAudio && aRequestAudio)
|
||||
expected.push("Microphone");
|
||||
expected = expected.join("And");
|
||||
is((yield getMediaCaptureState()), expected,
|
||||
"expected " + expected + " to be shared");
|
||||
expected.audio = true;
|
||||
Assert.deepEqual((yield getMediaCaptureState()), expected,
|
||||
"expected " + Object.keys(expected).join(" and ") +
|
||||
" to be shared");
|
||||
|
||||
yield closeStream();
|
||||
}
|
||||
@ -447,7 +440,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "Camera", "expected camera to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {video: true},
|
||||
"expected camera to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true});
|
||||
|
@ -22,13 +22,15 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
let expected = [];
|
||||
let expected = {};
|
||||
if (aVideo)
|
||||
expected.push("Camera");
|
||||
expected.video = true;
|
||||
if (aAudio)
|
||||
expected.push("Microphone");
|
||||
is((yield getMediaCaptureState()), expected.join("And"),
|
||||
"expected stream to be shared");
|
||||
expected.audio = true;
|
||||
Assert.deepEqual((yield getMediaCaptureState()), expected,
|
||||
"expected " + Object.keys(expected).join(" and ") +
|
||||
" to be shared");
|
||||
|
||||
|
||||
// Check the attribute on the tab, and check there's no visible
|
||||
// sharing icon on the tab
|
||||
|
@ -39,8 +39,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({audio: true, video: true});
|
||||
@ -63,8 +63,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true, audio: true});
|
||||
@ -104,8 +104,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true, audio: true});
|
||||
@ -160,7 +160,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "Microphone", "microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true},
|
||||
"expected microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: false, audio: true});
|
||||
@ -177,8 +178,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield checkSharingUI({video: true, audio: true});
|
||||
yield expectNoObserverCalled();
|
||||
@ -213,20 +214,13 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true, audio: true});
|
||||
|
||||
info("reloading the web page");
|
||||
promise = promiseObserverCalled("recording-device-events");
|
||||
content.location.reload();
|
||||
yield promise;
|
||||
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield expectNoObserverCalled();
|
||||
yield checkNotSharing();
|
||||
yield reloadAndAssertClosedStreams();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,611 @@
|
||||
/* 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/. */
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
const permissionError = "error: NotAllowedError: The request is not allowed " +
|
||||
"by the user agent or the platform in the current context.";
|
||||
|
||||
const notFoundError =
|
||||
"error: NotFoundError: The object can not be found here.";
|
||||
|
||||
var gTests = [
|
||||
|
||||
{
|
||||
desc: "getUserMedia screen only",
|
||||
run: function* checkScreenOnly() {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
|
||||
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
|
||||
"webRTC-shareScreen-notification-icon", "anchored to device icon");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
let iconclass =
|
||||
PopupNotifications.panel.firstChild.getAttribute("iconclass");
|
||||
ok(iconclass.includes("screen-icon"), "panel using screen icon");
|
||||
|
||||
let menulist =
|
||||
document.getElementById("webRTC-selectWindow-menulist");
|
||||
let count = menulist.itemCount;
|
||||
ok(count >= 3,
|
||||
"There should be the 'No Screen' item, a separator and at least one screen");
|
||||
|
||||
let noScreenItem = menulist.getItemAtIndex(0);
|
||||
ok(noScreenItem.hasAttribute("selected"), "the 'No Screen' item is selected");
|
||||
is(menulist.value, -1, "no screen is selected by default");
|
||||
is(menulist.selectedItem, noScreenItem, "'No Screen' is the selected item");
|
||||
|
||||
let separator = menulist.getItemAtIndex(1);
|
||||
is(separator.localName, "menuseparator", "the second item is a separator");
|
||||
|
||||
ok(document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should be hidden while there's no selection");
|
||||
ok(document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is hidden");
|
||||
|
||||
for (let i = 2; i < count; ++i) {
|
||||
let item = menulist.getItemAtIndex(i);
|
||||
is(parseInt(item.getAttribute("value")), i - 2, "the screen item has the correct index");
|
||||
is(item.getAttribute("devicetype"), "Screen", "the devicetype attribute is set correctly");
|
||||
ok(item.scary, "the screen item is marked as scary");
|
||||
}
|
||||
|
||||
// Select a screen, a preview with a scary warning should appear.
|
||||
menulist.getItemAtIndex(2).doCommand();
|
||||
ok(!document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should now be visible");
|
||||
yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden);
|
||||
ok(!document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is visible");
|
||||
ok(!document.getElementById("webRTC-previewWarning").hidden,
|
||||
"the scary warning is visible");
|
||||
|
||||
// Select the 'No Screen' item again, the preview should be hidden.
|
||||
menulist.getItemAtIndex(0).doCommand();
|
||||
ok(document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should now be hidden");
|
||||
ok(document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is hidden");
|
||||
|
||||
// Select the first screen again so that we can have a stream.
|
||||
menulist.getItemAtIndex(2).doCommand();
|
||||
|
||||
let indicator = promiseIndicatorWindow();
|
||||
yield promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {screen: "Screen"},
|
||||
"expected screen to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({screen: "Screen"});
|
||||
yield closeStream();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: "getUserMedia window only",
|
||||
run: function* checkWindowOnly() {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "window");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
|
||||
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
|
||||
"webRTC-shareScreen-notification-icon", "anchored to device icon");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
let iconclass =
|
||||
PopupNotifications.panel.firstChild.getAttribute("iconclass");
|
||||
ok(iconclass.includes("screen-icon"), "panel using screen icon");
|
||||
|
||||
let menulist =
|
||||
document.getElementById("webRTC-selectWindow-menulist");
|
||||
let count = menulist.itemCount;
|
||||
ok(count >= 3,
|
||||
"There should be the 'No Window' item, a separator and at least one window");
|
||||
|
||||
let noWindowItem = menulist.getItemAtIndex(0);
|
||||
ok(noWindowItem.hasAttribute("selected"), "the 'No Window' item is selected");
|
||||
is(menulist.value, -1, "no window is selected by default");
|
||||
is(menulist.selectedItem, noWindowItem, "'No Window' is the selected item");
|
||||
|
||||
let separator = menulist.getItemAtIndex(1);
|
||||
is(separator.localName, "menuseparator", "the second item is a separator");
|
||||
|
||||
ok(document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should be hidden");
|
||||
ok(document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is hidden");
|
||||
|
||||
let scaryIndex, nonScaryIndex;
|
||||
for (let i = 2; i < count; ++i) {
|
||||
let item = menulist.getItemAtIndex(i);
|
||||
is(parseInt(item.getAttribute("value")), i - 2, "the window item has the correct index");
|
||||
is(item.getAttribute("devicetype"), "Window", "the devicetype attribute is set correctly");
|
||||
if (item.scary)
|
||||
scaryIndex = i;
|
||||
else
|
||||
nonScaryIndex = i;
|
||||
}
|
||||
ok(typeof scaryIndex == "number", "there's at least one scary window, as Firefox is running");
|
||||
|
||||
// Select a scary window, a preview with a scary warning should appear.
|
||||
menulist.getItemAtIndex(scaryIndex).doCommand();
|
||||
ok(document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should still be hidden");
|
||||
yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden);
|
||||
ok(!document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is visible");
|
||||
ok(!document.getElementById("webRTC-previewWarning").hidden,
|
||||
"the scary warning is visible");
|
||||
|
||||
// Select the 'No Window' item again, the preview should be hidden.
|
||||
menulist.getItemAtIndex(0).doCommand();
|
||||
ok(document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is hidden");
|
||||
|
||||
// If we have a non-scary window, select it and verify the warning isn't displayed.
|
||||
// A non-scary window may not always exist on test slaves.
|
||||
if (typeof nonScaryIndex == "number") {
|
||||
menulist.getItemAtIndex(nonScaryIndex).doCommand();
|
||||
ok(document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should still be hidden");
|
||||
yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden);
|
||||
ok(!document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is visible");
|
||||
ok(document.getElementById("webRTC-previewWarning").hidden,
|
||||
"the scary warning is hidden");
|
||||
} else {
|
||||
info("no non-scary window available on this test slave");
|
||||
|
||||
// Select the first window again so that we can have a stream.
|
||||
menulist.getItemAtIndex(scaryIndex).doCommand();
|
||||
}
|
||||
|
||||
let indicator = promiseIndicatorWindow();
|
||||
yield promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {screen: "Window"},
|
||||
"expected window to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({screen: "Window"});
|
||||
yield closeStream();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: "getUserMedia application only",
|
||||
run: function* checkAppOnly() {
|
||||
if (AppConstants.platform == "linux") {
|
||||
todo(false, "Bug 1323490 - On Linux 64, this test fails with 'NotFoundError' and causes the next test (screen+audio) to timeout");
|
||||
return;
|
||||
}
|
||||
|
||||
let promptPromise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
let messagePromise = promiseMessageReceived();
|
||||
yield promiseRequestDevice(false, true, null, "application");
|
||||
let message = yield Promise.race([promptPromise, messagePromise]);
|
||||
if (message) {
|
||||
is(message, notFoundError,
|
||||
"NotFoundError, likely because there's no other application running.");
|
||||
return;
|
||||
}
|
||||
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
|
||||
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
|
||||
"webRTC-shareScreen-notification-icon", "anchored to device icon");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
let iconclass =
|
||||
PopupNotifications.panel.firstChild.getAttribute("iconclass");
|
||||
ok(iconclass.includes("screen-icon"), "panel using screen icon");
|
||||
|
||||
let menulist =
|
||||
document.getElementById("webRTC-selectWindow-menulist");
|
||||
let count = menulist.itemCount;
|
||||
ok(count >= 3,
|
||||
"There should be the 'No Application' item, a separator and at least one application");
|
||||
|
||||
let noApplicationItem = menulist.getItemAtIndex(0);
|
||||
ok(noApplicationItem.hasAttribute("selected"), "the 'No Application' item is selected");
|
||||
is(menulist.value, -1, "no app is selected by default");
|
||||
is(menulist.selectedItem, noApplicationItem, "'No Application' is the selected item");
|
||||
|
||||
let separator = menulist.getItemAtIndex(1);
|
||||
is(separator.localName, "menuseparator", "the second item is a separator");
|
||||
|
||||
ok(document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should be hidden");
|
||||
ok(document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is hidden");
|
||||
|
||||
let scaryIndex;
|
||||
for (let i = 2; i < count; ++i) {
|
||||
let item = menulist.getItemAtIndex(i);
|
||||
is(parseInt(item.getAttribute("value")), i - 2, "the app item has the correct index");
|
||||
is(item.getAttribute("devicetype"), "Application", "the devicetype attribute is set correctly");
|
||||
if (item.scary)
|
||||
scaryIndex = i;
|
||||
}
|
||||
ok(scaryIndex === undefined,
|
||||
"Firefox is currently excluding itself from the list of applications, " +
|
||||
"so no scary app should be listed");
|
||||
|
||||
menulist.getItemAtIndex(2).doCommand();
|
||||
ok(document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should still be hidden");
|
||||
yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden);
|
||||
ok(!document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is visible");
|
||||
ok(document.getElementById("webRTC-previewWarning").hidden,
|
||||
"the scary warning is hidden");
|
||||
|
||||
let indicator = promiseIndicatorWindow();
|
||||
yield promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {screen: "Application"},
|
||||
"expected application to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({screen: "Application"});
|
||||
yield closeStream();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: "getUserMedia audio+screen",
|
||||
run: function* checkAudioVideo() {
|
||||
if (AppConstants.platform == "macosx") {
|
||||
todo(false, "Bug 1323481 - On Mac on treeherder, but not locally, requesting microphone + screen never makes the permission prompt appear, and so causes the test to timeout");
|
||||
return;
|
||||
}
|
||||
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(true, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
|
||||
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
|
||||
"webRTC-shareScreen-notification-icon", "anchored to device icon");
|
||||
checkDeviceSelectors(true, false, true);
|
||||
let iconclass =
|
||||
PopupNotifications.panel.firstChild.getAttribute("iconclass");
|
||||
ok(iconclass.includes("screen-icon"), "panel using screen icon");
|
||||
|
||||
let menulist =
|
||||
document.getElementById("webRTC-selectWindow-menulist");
|
||||
let count = menulist.itemCount;
|
||||
ok(count >= 3,
|
||||
"There should be the 'No Screen' item, a separator and at least one screen");
|
||||
|
||||
// Select a screen, a preview with a scary warning should appear.
|
||||
menulist.getItemAtIndex(2).doCommand();
|
||||
ok(!document.getElementById("webRTC-all-windows-shared").hidden,
|
||||
"the 'all windows will be shared' warning should now be visible");
|
||||
yield promiseWaitForCondition(() => !document.getElementById("webRTC-preview").hidden);
|
||||
ok(!document.getElementById("webRTC-preview").hidden,
|
||||
"the preview area is visible");
|
||||
ok(!document.getElementById("webRTC-previewWarning").hidden,
|
||||
"the scary warning is visible");
|
||||
|
||||
let indicator = promiseIndicatorWindow();
|
||||
yield promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
Assert.deepEqual((yield getMediaCaptureState()),
|
||||
{audio: true, screen: "Screen"},
|
||||
"expected screen and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({audio: true, screen: "Screen"});
|
||||
yield closeStream();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
desc: "getUserMedia screen: clicking through without selecting a screen denies",
|
||||
run: function* checkReloading() {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
|
||||
yield promiseMessage(permissionError, () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
desc: "getUserMedia screen, user clicks \"Don't Allow\"",
|
||||
run: function* checkDontShare() {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
|
||||
yield promiseMessage(permissionError, () => {
|
||||
activateSecondaryAction(kActionDeny);
|
||||
});
|
||||
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: "getUserMedia audio+video+screen: stop sharing",
|
||||
run: function* checkStopSharing() {
|
||||
if (AppConstants.platform == "macosx") {
|
||||
todo(false, "Bug 1323481 - On Mac on treeherder, but not locally, requesting microphone + screen never makes the permission prompt appear, and so causes the test to timeout");
|
||||
return;
|
||||
}
|
||||
|
||||
function* share(audio, video, screen) {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(audio, video || !!screen,
|
||||
null, screen && "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
checkDeviceSelectors(audio, video, screen);
|
||||
if (screen) {
|
||||
document.getElementById("webRTC-selectWindow-menulist")
|
||||
.getItemAtIndex(2).doCommand();
|
||||
}
|
||||
yield promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
}
|
||||
|
||||
function* check(expected = {}) {
|
||||
let shared = Object.keys(expected).join(" and ");
|
||||
if (shared) {
|
||||
Assert.deepEqual((yield getMediaCaptureState()), expected,
|
||||
"expected " + shared + " to be shared");
|
||||
yield checkSharingUI(expected);
|
||||
} else {
|
||||
yield checkNotSharing();
|
||||
}
|
||||
}
|
||||
|
||||
info("Share screen and microphone");
|
||||
let indicator = promiseIndicatorWindow();
|
||||
yield share(true, false, true);
|
||||
yield indicator;
|
||||
yield check({audio: true, screen: "Screen"});
|
||||
|
||||
info("Share camera");
|
||||
yield share(false, true);
|
||||
yield check({video: true, audio: true, screen: "Screen"});
|
||||
|
||||
info("Stop the screen share, mic+cam should continue");
|
||||
yield stopSharing("screen", true);
|
||||
yield check({video: true, audio: true});
|
||||
|
||||
info("Stop the camera, everything should stop.");
|
||||
yield stopSharing("camera", false, true);
|
||||
|
||||
info("Now, share only the screen...");
|
||||
indicator = promiseIndicatorWindow();
|
||||
yield share(false, false, true);
|
||||
yield indicator;
|
||||
yield check({screen: "Screen"});
|
||||
|
||||
info("... and add camera and microphone in a second request.");
|
||||
yield share(true, true);
|
||||
yield check({video: true, audio: true, screen: "Screen"});
|
||||
|
||||
info("Stop the camera, this should stop everything.");
|
||||
yield stopSharing("camera", false, true);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: "getUserMedia screen: reloading the page removes all gUM UI",
|
||||
run: function* checkReloading() {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
document.getElementById("webRTC-selectWindow-menulist")
|
||||
.getItemAtIndex(2).doCommand();
|
||||
|
||||
let indicator = promiseIndicatorWindow();
|
||||
yield promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {screen: "Screen"},
|
||||
"expected screen to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({screen: "Screen"});
|
||||
|
||||
yield reloadAndAssertClosedStreams();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: "test showControlCenter from screen icon",
|
||||
run: function* checkShowControlCenter() {
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
document.getElementById("webRTC-selectWindow-menulist")
|
||||
.getItemAtIndex(2).doCommand();
|
||||
|
||||
let indicator = promiseIndicatorWindow();
|
||||
yield promiseMessage("ok", () => {
|
||||
PopupNotifications.panel.firstChild.button.click();
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {screen: "Screen"},
|
||||
"expected screen to be shared");
|
||||
yield indicator;
|
||||
yield checkSharingUI({screen: "Screen"});
|
||||
|
||||
ok(gIdentityHandler._identityPopup.hidden, "control center should be hidden");
|
||||
if ("nsISystemStatusBar" in Ci) {
|
||||
let activeStreams = webrtcUI.getActiveStreams(false, false, true);
|
||||
webrtcUI.showSharingDoorhanger(activeStreams[0]);
|
||||
}
|
||||
else {
|
||||
let win =
|
||||
Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator");
|
||||
let elt = win.document.getElementById("screenShareButton");
|
||||
EventUtils.synthesizeMouseAtCenter(elt, {}, win);
|
||||
yield promiseWaitForCondition(() => !gIdentityHandler._identityPopup.hidden);
|
||||
}
|
||||
ok(!gIdentityHandler._identityPopup.hidden, "control center should be open");
|
||||
|
||||
gIdentityHandler._identityPopup.hidden = true;
|
||||
yield expectNoObserverCalled();
|
||||
|
||||
yield closeStream();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
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,
|
||||
"starting without screen persistent permissions");
|
||||
|
||||
let promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
checkDeviceSelectors(false, false, true);
|
||||
document.getElementById("webRTC-selectWindow-menulist")
|
||||
.getItemAtIndex(2).doCommand();
|
||||
|
||||
// Ensure that checking the 'Remember this decision' checkbox disables
|
||||
// 'Allow'.
|
||||
let notification = PopupNotifications.panel.firstChild;
|
||||
ok(notification.hasAttribute("warninghidden"), "warning message is hidden");
|
||||
let checkbox = notification.checkbox;
|
||||
ok(!!checkbox, "checkbox is present");
|
||||
ok(!checkbox.checked, "checkbox is not checked");
|
||||
checkbox.click();
|
||||
ok(checkbox.checked, "checkbox now checked");
|
||||
ok(notification.button.disabled, "Allow button is disabled");
|
||||
ok(!notification.hasAttribute("warninghidden"), "warning message is shown");
|
||||
|
||||
// Click "Don't Allow" to save a persistent block permission.
|
||||
yield promiseMessage(permissionError, () => {
|
||||
activateSecondaryAction(kActionDeny);
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield checkNotSharing();
|
||||
|
||||
is(Perms.testExactPermission(uri, "screen"), Perms.DENY_ACTION,
|
||||
"screen sharing is persistently blocked");
|
||||
|
||||
// Request screensharing again, expect an immediate failure.
|
||||
promise = promiseMessage(permissionError);
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
|
||||
// Now set the permission to allow and expect a prompt.
|
||||
Perms.add(uri, "screen", Perms.ALLOW_ACTION);
|
||||
|
||||
// Request devices and expect a prompt despite the saved 'Allow' permission.
|
||||
promise = promisePopupNotificationShown("webRTC-shareDevices");
|
||||
yield promiseRequestDevice(false, true, null, "screen");
|
||||
yield promise;
|
||||
yield expectObserverCalled("getUserMedia:request");
|
||||
|
||||
// The 'remember' checkbox shouldn't be checked anymore.
|
||||
notification = PopupNotifications.panel.firstChild;
|
||||
ok(notification.hasAttribute("warninghidden"), "warning message is hidden");
|
||||
checkbox = notification.checkbox;
|
||||
ok(!!checkbox, "checkbox is present");
|
||||
ok(!checkbox.checked, "checkbox is not checked");
|
||||
|
||||
// Deny the request to cleanup...
|
||||
yield promiseMessage(permissionError, () => {
|
||||
activateSecondaryAction(kActionDeny);
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:deny");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
Perms.remove(uri, "screen");
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let tab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = tab;
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
|
||||
|
||||
browser.addEventListener("load", function onload() {
|
||||
browser.removeEventListener("load", onload, true);
|
||||
|
||||
is(PopupNotifications._currentNotifications.length, 0,
|
||||
"should start the test without any prior popup notification");
|
||||
ok(gIdentityHandler._identityPopup.hidden,
|
||||
"should start the test with the control center hidden");
|
||||
|
||||
Task.spawn(function* () {
|
||||
yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
|
||||
|
||||
for (let testCase of gTests) {
|
||||
info(testCase.desc);
|
||||
yield testCase.run();
|
||||
|
||||
// Cleanup before the next test
|
||||
yield expectNoObserverCalled();
|
||||
}
|
||||
}).then(finish, ex => {
|
||||
Cu.reportError(ex);
|
||||
ok(false, "Unexpected Exception: " + ex);
|
||||
finish();
|
||||
});
|
||||
}, true);
|
||||
let rootDir = getRootDirectory(gTestPath);
|
||||
rootDir = rootDir.replace("chrome://mochitests/content/",
|
||||
"https://example.com/");
|
||||
content.location = rootDir + "get_user_media.html";
|
||||
}
|
@ -23,8 +23,8 @@ var gTests = [
|
||||
});
|
||||
yield expectObserverCalled("getUserMedia:response:allow");
|
||||
yield expectObserverCalled("recording-device-events");
|
||||
is((yield getMediaCaptureState()), "CameraAndMicrophone",
|
||||
"expected camera and microphone to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
|
||||
"expected camera and microphone to be shared");
|
||||
|
||||
yield indicator;
|
||||
yield checkSharingUI({video: true, audio: true});
|
||||
|
@ -17,6 +17,20 @@ const kObservedTopics = [
|
||||
|
||||
var gObservedTopics = {};
|
||||
function observer(aSubject, aTopic, aData) {
|
||||
// With e10s disabled, our content script receives notifications for the
|
||||
// preview displayed in our screen sharing permission prompt; ignore them.
|
||||
const kBrowserURL = "chrome://browser/content/browser.xul";
|
||||
const nsIPropertyBag = Components.interfaces.nsIPropertyBag;
|
||||
if (aTopic == "recording-device-events" &&
|
||||
aSubject.QueryInterface(nsIPropertyBag).getProperty("requestURL") == kBrowserURL) {
|
||||
return;
|
||||
}
|
||||
if (aTopic == "recording-window-ended") {
|
||||
let win = Services.wm.getOuterWindowWithId(aData).top;
|
||||
if (win.document.documentURI == kBrowserURL)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(aTopic in gObservedTopics))
|
||||
gObservedTopics[aTopic] = 1;
|
||||
else
|
||||
@ -44,19 +58,28 @@ function _getMediaCaptureState() {
|
||||
let hasAudio = {};
|
||||
let hasScreenShare = {};
|
||||
let hasWindowShare = {};
|
||||
let hasAppShare = {};
|
||||
let hasBrowserShare = {};
|
||||
MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio,
|
||||
hasScreenShare, hasWindowShare);
|
||||
if (hasVideo.value && hasAudio.value)
|
||||
return "CameraAndMicrophone";
|
||||
hasScreenShare, hasWindowShare,
|
||||
hasAppShare, hasBrowserShare);
|
||||
let result = {};
|
||||
|
||||
if (hasVideo.value)
|
||||
return "Camera";
|
||||
result.video = true;
|
||||
if (hasAudio.value)
|
||||
return "Microphone";
|
||||
result.audio = true;
|
||||
|
||||
if (hasScreenShare.value)
|
||||
return "Screen";
|
||||
if (hasWindowShare.value)
|
||||
return "Window";
|
||||
return "none";
|
||||
result.screen = "Screen";
|
||||
else if (hasWindowShare.value)
|
||||
result.screen = "Window";
|
||||
else if (hasAppShare.value)
|
||||
result.screen = "Application";
|
||||
else if (hasBrowserShare.value)
|
||||
result.screen = "Browser";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
addMessageListener("Test:GetMediaCaptureState", data => {
|
||||
|
@ -102,7 +102,7 @@ function* assertWebRTCIndicatorStatus(expected) {
|
||||
if (expected.audio)
|
||||
expectAudio = true;
|
||||
if (expected.screen)
|
||||
expectScreen = true;
|
||||
expectScreen = expected.screen;
|
||||
}
|
||||
is(ui.showCameraIndicator, expectVideo, "camera global indicator as expected");
|
||||
is(ui.showMicrophoneIndicator, expectAudio, "microphone global indicator as expected");
|
||||
@ -217,15 +217,25 @@ function expectObserverCalled(aTopic) {
|
||||
});
|
||||
}
|
||||
|
||||
function expectNoObserverCalled() {
|
||||
function expectNoObserverCalled(aIgnoreDeviceEvents = false) {
|
||||
return new Promise(resolve => {
|
||||
let mm = _mm();
|
||||
mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
|
||||
function listener({data}) {
|
||||
mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
|
||||
for (let topic in data) {
|
||||
if (data[topic])
|
||||
if (!data[topic])
|
||||
continue;
|
||||
|
||||
// If we are stopping tracks that were created from 2 different
|
||||
// getUserMedia calls, the "recording-device-events" notification is
|
||||
// fired twice on Windows and Mac, and intermittently twice on Linux.
|
||||
if (topic == "recording-device-events" && aIgnoreDeviceEvents) {
|
||||
todo(false, "Got " + data[topic] + " unexpected " + topic +
|
||||
" notifications, see bug 1320994");
|
||||
} else {
|
||||
is(data[topic], 0, topic + " notification unexpected");
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
@ -233,18 +243,26 @@ function expectNoObserverCalled() {
|
||||
});
|
||||
}
|
||||
|
||||
function promiseMessage(aMessage, aAction) {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
function promiseMessageReceived() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let mm = _mm();
|
||||
mm.addMessageListener("Test:MessageReceived", function listener({data}) {
|
||||
mm.removeMessageListener("Test:MessageReceived", listener);
|
||||
resolve(data);
|
||||
});
|
||||
mm.sendAsyncMessage("Test:WaitForMessage");
|
||||
});
|
||||
}
|
||||
|
||||
function promiseMessage(aMessage, aAction) {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
promiseMessageReceived(aAction).then(data => {
|
||||
is(data, aMessage, "received " + aMessage);
|
||||
if (data == aMessage)
|
||||
resolve();
|
||||
else
|
||||
reject();
|
||||
mm.removeMessageListener("Test:MessageReceived", listener);
|
||||
});
|
||||
mm.sendAsyncMessage("Test:WaitForMessage");
|
||||
});
|
||||
|
||||
if (aAction)
|
||||
@ -328,7 +346,8 @@ function getMediaCaptureState() {
|
||||
});
|
||||
}
|
||||
|
||||
function* stopSharing(aType = "camera") {
|
||||
function* stopSharing(aType = "camera", aShouldKeepSharing = false,
|
||||
aExpectDoubleRecordingEvent = false) {
|
||||
let promiseRecordingEvent = promiseObserverCalled("recording-device-events");
|
||||
gIdentityHandler._identityBox.click();
|
||||
let permissions = document.getElementById("identity-popup-permission-list");
|
||||
@ -339,9 +358,16 @@ function* stopSharing(aType = "camera") {
|
||||
gIdentityHandler._identityPopup.hidden = true;
|
||||
yield promiseRecordingEvent;
|
||||
yield expectObserverCalled("getUserMedia:revoke");
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield expectNoObserverCalled();
|
||||
yield* checkNotSharing();
|
||||
|
||||
// If we are stopping screen sharing and expect to still have another stream,
|
||||
// "recording-window-ended" won't be fired.
|
||||
if (!aShouldKeepSharing)
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
|
||||
yield expectNoObserverCalled(aExpectDoubleRecordingEvent);
|
||||
|
||||
if (!aShouldKeepSharing)
|
||||
yield* checkNotSharing();
|
||||
}
|
||||
|
||||
function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType) {
|
||||
@ -379,7 +405,19 @@ function* closeStream(aAlreadyClosed, aFrameId) {
|
||||
yield* assertWebRTCIndicatorStatus(null);
|
||||
}
|
||||
|
||||
function checkDeviceSelectors(aAudio, aVideo) {
|
||||
function* reloadAndAssertClosedStreams() {
|
||||
info("reloading the web page");
|
||||
let promise = promiseObserverCalled("recording-device-events");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null,
|
||||
"() => content.location.reload()");
|
||||
yield promise;
|
||||
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
yield expectNoObserverCalled();
|
||||
yield checkNotSharing();
|
||||
}
|
||||
|
||||
function checkDeviceSelectors(aAudio, aVideo, aScreen) {
|
||||
let micSelector = document.getElementById("webRTC-selectMicrophone");
|
||||
if (aAudio)
|
||||
ok(!micSelector.hidden, "microphone selector visible");
|
||||
@ -391,6 +429,12 @@ function checkDeviceSelectors(aAudio, aVideo) {
|
||||
ok(!cameraSelector.hidden, "camera selector visible");
|
||||
else
|
||||
ok(cameraSelector.hidden, "camera selector hidden");
|
||||
|
||||
let screenSelector = document.getElementById("webRTC-selectWindowOrScreen");
|
||||
if (aScreen)
|
||||
ok(!screenSelector.hidden, "screen selector visible");
|
||||
else
|
||||
ok(screenSelector.hidden, "screen selector hidden");
|
||||
}
|
||||
|
||||
function* checkSharingUI(aExpected, aWin = window) {
|
||||
@ -399,7 +443,9 @@ function* checkSharingUI(aExpected, aWin = window) {
|
||||
let identityBox = doc.getElementById("identity-box");
|
||||
ok(identityBox.hasAttribute("sharing"), "sharing attribute is set");
|
||||
let sharing = identityBox.getAttribute("sharing");
|
||||
if (aExpected.video)
|
||||
if (aExpected.screen)
|
||||
is(sharing, "screen", "showing screen icon on the control center icon");
|
||||
else if (aExpected.video)
|
||||
is(sharing, "camera", "showing camera icon on the control center icon");
|
||||
else if (aExpected.audio)
|
||||
is(sharing, "microphone", "showing mic icon on the control center icon");
|
||||
@ -439,7 +485,8 @@ function* checkSharingUI(aExpected, aWin = window) {
|
||||
}
|
||||
|
||||
function* checkNotSharing() {
|
||||
is((yield getMediaCaptureState()), "none", "expected nothing to be shared");
|
||||
Assert.deepEqual((yield getMediaCaptureState()), {},
|
||||
"expected nothing to be shared");
|
||||
|
||||
ok(!document.getElementById("identity-box").hasAttribute("sharing"),
|
||||
"no sharing indicator on the control center icon");
|
||||
|
@ -194,9 +194,14 @@ var gSearchPane = {
|
||||
}
|
||||
},
|
||||
|
||||
onInputBlur: function() {
|
||||
onInputBlur: function(aEvent) {
|
||||
let tree = document.getElementById("engineList");
|
||||
tree.stopEditing(false);
|
||||
if (!tree.hasAttribute("editing"))
|
||||
return;
|
||||
|
||||
// Accept input unless discarded.
|
||||
let accept = aEvent.charCode != KeyEvent.DOM_VK_ESCAPE;
|
||||
tree.stopEditing(accept);
|
||||
},
|
||||
|
||||
onTreeSelect: function() {
|
||||
|
@ -186,6 +186,9 @@ var SessionSaverInternal = {
|
||||
let state = SessionStore.getCurrentState(forceUpdateAllWindows);
|
||||
PrivacyFilter.filterPrivateWindowsAndTabs(state);
|
||||
|
||||
// Make sure we only write worth saving tabs to disk.
|
||||
SessionStore.keepOnlyWorthSavingTabs(state);
|
||||
|
||||
// Make sure that we keep the previous session if we started with a single
|
||||
// private window and no non-private windows have been opened, yet.
|
||||
if (state.deferredInitialState) {
|
||||
|
@ -375,6 +375,34 @@ this.SessionStore = {
|
||||
}
|
||||
return number <= FORMAT_VERSION;
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters out not worth-saving tabs from a given browser state object.
|
||||
*
|
||||
* @param aState (object)
|
||||
* The browser state for which we remove worth-saving tabs.
|
||||
* The given object will be modified.
|
||||
*/
|
||||
keepOnlyWorthSavingTabs: function (aState) {
|
||||
for (let i = aState.windows.length - 1; i >= 0; i--) {
|
||||
let win = aState.windows[i];
|
||||
for (let j = win.tabs.length - 1; j >= 0; j--) {
|
||||
let tab = win.tabs[j];
|
||||
if (!SessionStoreInternal._shouldSaveTabState(tab)) {
|
||||
win.tabs.splice(j, 1);
|
||||
if (win.selected > j) {
|
||||
win.selected--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!win.tabs.length) {
|
||||
aState.windows.splice(i, 1);
|
||||
if (aState.selectedWindow > i) {
|
||||
aState.selectedWindow--;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Freeze the SessionStore object. We don't want anyone to modify it.
|
||||
|
@ -35,8 +35,8 @@ add_task(function* new_window() {
|
||||
newWin = null;
|
||||
|
||||
let state = JSON.parse((yield promiseRecoveryFileContents()));
|
||||
is(state.windows.length, 2,
|
||||
"observe1: 2 windows in data written to disk");
|
||||
is(state.windows.length, 1,
|
||||
"observe1: 1 window in data written to disk");
|
||||
is(state._closedWindows.length, 0,
|
||||
"observe1: no closed windows in data written to disk");
|
||||
|
||||
@ -57,6 +57,8 @@ add_task(function* new_tab() {
|
||||
let newTab;
|
||||
try {
|
||||
newTab = gBrowser.addTab("about:mozilla");
|
||||
yield promiseBrowserLoaded(newTab.linkedBrowser);
|
||||
yield TabStateFlusher.flush(newTab.linkedBrowser);
|
||||
|
||||
let state = JSON.parse((yield promiseRecoveryFileContents()));
|
||||
is(state.windows.length, 1,
|
||||
@ -68,7 +70,9 @@ add_task(function* new_tab() {
|
||||
is(ss.getClosedWindowCount(), 1,
|
||||
"observe2: 1 closed window according to API");
|
||||
} finally {
|
||||
gBrowser.removeTab(newTab);
|
||||
if (newTab) {
|
||||
gBrowser.removeTab(newTab);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -77,6 +81,7 @@ add_task(function* done() {
|
||||
// The API still represents the closed window as closed, so we can clear it
|
||||
// with the API, but just to make sure...
|
||||
// is(ss.getClosedWindowCount(), 1, "1 closed window according to API");
|
||||
yield promiseAllButPrimaryWindowClosed();
|
||||
forgetClosedWindows();
|
||||
Services.prefs.clearUserPref("browser.sessionstore.interval");
|
||||
});
|
||||
|
@ -8,16 +8,16 @@ requestLongerTimeout(2);
|
||||
|
||||
add_task(function* test_1() {
|
||||
let win = yield promiseNewWindowLoaded();
|
||||
win.gBrowser.addTab("http://www.example.com/1");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#1");
|
||||
|
||||
win = yield promiseNewWindowLoaded({private: true});
|
||||
win.gBrowser.addTab("http://www.example.com/2");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#2");
|
||||
|
||||
win = yield promiseNewWindowLoaded();
|
||||
win.gBrowser.addTab("http://www.example.com/3");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#3");
|
||||
|
||||
win = yield promiseNewWindowLoaded({private: true});
|
||||
win.gBrowser.addTab("http://www.example.com/4");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#4");
|
||||
|
||||
let curState = JSON.parse(ss.getBrowserState());
|
||||
is(curState.windows.length, 5, "Browser has opened 5 windows");
|
||||
@ -27,9 +27,9 @@ add_task(function* test_1() {
|
||||
|
||||
let state = JSON.parse(yield promiseRecoveryFileContents());
|
||||
|
||||
is(state.windows.length, 3,
|
||||
"sessionstore state: 3 windows in data being written to disk");
|
||||
is(state.selectedWindow, 3,
|
||||
is(state.windows.length, 2,
|
||||
"sessionstore state: 2 windows in data being written to disk");
|
||||
is(state.selectedWindow, 2,
|
||||
"Selected window is updated to match one of the saved windows");
|
||||
ok(state.windows.every(win => !win.isPrivate),
|
||||
"Saved windows are not private");
|
||||
@ -44,10 +44,10 @@ add_task(function* test_1() {
|
||||
// Test opening default mochitest window + 2 private windows
|
||||
add_task(function* test_2() {
|
||||
let win = yield promiseNewWindowLoaded({private: true});
|
||||
win.gBrowser.addTab("http://www.example.com/1");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#1");
|
||||
|
||||
win = yield promiseNewWindowLoaded({private: true});
|
||||
win.gBrowser.addTab("http://www.example.com/2");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#2");
|
||||
|
||||
let curState = JSON.parse(ss.getBrowserState());
|
||||
is(curState.windows.length, 3, "Browser has opened 3 windows");
|
||||
@ -57,10 +57,10 @@ add_task(function* test_2() {
|
||||
|
||||
let state = JSON.parse(yield promiseRecoveryFileContents());
|
||||
|
||||
is(state.windows.length, 1,
|
||||
"sessionstore state: 1 windows in data being written to disk");
|
||||
is(state.selectedWindow, 1,
|
||||
"Selected window is updated to match one of the saved windows");
|
||||
is(state.windows.length, 0,
|
||||
"sessionstore state: no window in data being written to disk");
|
||||
is(state.selectedWindow, 0,
|
||||
"Selected window updated to 0 given there are no saved windows");
|
||||
is(state._closedWindows.length, 0,
|
||||
"sessionstore state: no closed windows in data being written to disk");
|
||||
|
||||
@ -72,13 +72,13 @@ add_task(function* test_2() {
|
||||
// Test opening default-normal-private-normal windows and closing a normal window
|
||||
add_task(function* test_3() {
|
||||
let normalWindow = yield promiseNewWindowLoaded();
|
||||
yield promiseTabLoad(normalWindow, "http://www.example.com/");
|
||||
yield promiseTabLoad(normalWindow, "http://www.example.com/#1");
|
||||
|
||||
let win = yield promiseNewWindowLoaded({private: true});
|
||||
yield promiseTabLoad(win, "http://www.example.com/");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#2");
|
||||
|
||||
win = yield promiseNewWindowLoaded();
|
||||
yield promiseTabLoad(win, "http://www.example.com/");
|
||||
yield promiseTabLoad(win, "http://www.example.com/#3");
|
||||
|
||||
let curState = JSON.parse(ss.getBrowserState());
|
||||
is(curState.windows.length, 4, "Browser has opened 4 windows");
|
||||
@ -96,9 +96,9 @@ add_task(function* test_3() {
|
||||
|
||||
let state = JSON.parse(yield promiseRecoveryFileContents());
|
||||
|
||||
is(state.windows.length, 2,
|
||||
"sessionstore state: 2 windows in data being written to disk");
|
||||
is(state.selectedWindow, 2,
|
||||
is(state.windows.length, 1,
|
||||
"sessionstore state: 1 window in data being written to disk");
|
||||
is(state.selectedWindow, 1,
|
||||
"Selected window is updated to match one of the saved windows");
|
||||
ok(state.windows.every(win => !win.isPrivate),
|
||||
"Saved windows are not private");
|
||||
@ -113,8 +113,7 @@ add_task(function* test_3() {
|
||||
});
|
||||
|
||||
function* promiseTabLoad(win, url) {
|
||||
let browser = win.gBrowser.selectedBrowser;
|
||||
browser.loadURI(url);
|
||||
yield promiseBrowserLoaded(browser);
|
||||
yield TabStateFlusher.flush(browser);
|
||||
let tab = win.gBrowser.addTab(url);
|
||||
yield promiseBrowserLoaded(tab.linkedBrowser);
|
||||
yield TabStateFlusher.flush(tab.linkedBrowser);
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ add_task(function* () {
|
||||
|
||||
ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
|
||||
|
||||
yield forceSaveState();
|
||||
|
||||
win = SessionStore.undoCloseWindow(0);
|
||||
yield TestUtils.topicObserved("sessionstore-single-window-restored",
|
||||
subject => subject == win);
|
||||
@ -50,6 +52,8 @@ add_task(function* () {
|
||||
|
||||
ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
|
||||
|
||||
yield forceSaveState();
|
||||
|
||||
win = SessionStore.undoCloseWindow(0);
|
||||
yield TestUtils.topicObserved("sessionstore-single-window-restored",
|
||||
subject => subject == win);
|
||||
|
@ -124,6 +124,8 @@ add_task(function test_scroll_background_tabs() {
|
||||
// Close the window
|
||||
yield BrowserTestUtils.closeWindow(newWin);
|
||||
|
||||
yield forceSaveState();
|
||||
|
||||
// Now restore the window
|
||||
newWin = ss.undoCloseWindow(0);
|
||||
|
||||
|
@ -35,6 +35,8 @@ add_task(function test_scroll_background_about_reader_tabs() {
|
||||
// Close the window
|
||||
yield BrowserTestUtils.closeWindow(newWin);
|
||||
|
||||
yield forceSaveState();
|
||||
|
||||
// Now restore the window
|
||||
newWin = ss.undoCloseWindow(0);
|
||||
|
||||
|
@ -117,10 +117,6 @@ browser.jar:
|
||||
skin/classic/browser/sync-horizontalbar@2x.png
|
||||
skin/classic/browser/sync-mobileIcon.svg (../shared/sync-mobileIcon.svg)
|
||||
skin/classic/browser/sync-notification-24.png
|
||||
skin/classic/browser/syncProgress-menuPanel.png
|
||||
skin/classic/browser/syncProgress-menuPanel@2x.png
|
||||
skin/classic/browser/syncProgress-toolbar.png
|
||||
skin/classic/browser/syncProgress-toolbar-inverted.png
|
||||
skin/classic/browser/syncSetup.css
|
||||
skin/classic/browser/syncCommon.css
|
||||
skin/classic/browser/syncQuota.css
|
||||
|
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -168,12 +168,6 @@ browser.jar:
|
||||
skin/classic/browser/syncQuota.css
|
||||
skin/classic/browser/syncProgress-horizontalbar.png
|
||||
skin/classic/browser/syncProgress-horizontalbar@2x.png
|
||||
skin/classic/browser/syncProgress-menuPanel.png
|
||||
skin/classic/browser/syncProgress-menuPanel@2x.png
|
||||
skin/classic/browser/syncProgress-toolbar.png
|
||||
skin/classic/browser/syncProgress-toolbar@2x.png
|
||||
skin/classic/browser/syncProgress-toolbar-inverted.png
|
||||
skin/classic/browser/syncProgress-toolbar-inverted@2x.png
|
||||
skin/classic/browser/Toolbar-background-noise.png (Toolbar-background-noise.png)
|
||||
skin/classic/browser/lion/toolbarbutton-dropmarker.png (toolbarbutton-dropmarker-lion.png)
|
||||
skin/classic/browser/toolbarbutton-dropmarker@2x.png (toolbarbutton-dropmarker-lion@2x.png)
|
||||
|
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 33 KiB |
@ -178,14 +178,7 @@ browser.jar:
|
||||
skin/classic/browser/syncProgress-horizontalbar@2x.png
|
||||
skin/classic/browser/syncProgress-horizontalbar-XPVista7.png
|
||||
skin/classic/browser/syncProgress-horizontalbar-XPVista7@2x.png
|
||||
skin/classic/browser/syncProgress-menuPanel.png
|
||||
skin/classic/browser/syncProgress-menuPanel@2x.png
|
||||
skin/classic/browser/syncProgress-toolbar.png
|
||||
skin/classic/browser/syncProgress-toolbar@2x.png
|
||||
skin/classic/browser/syncProgress-toolbar-inverted.png
|
||||
skin/classic/browser/syncProgress-toolbar-inverted@2x.png
|
||||
skin/classic/browser/syncProgress-toolbar-XPVista7.png
|
||||
skin/classic/browser/syncProgress-toolbar-XPVista7@2x.png
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
|
||||
#endif
|
||||
@ -231,8 +224,6 @@ browser.jar:
|
||||
% override chrome://browser/skin/sync-horizontalbar@2x.png chrome://browser/skin/sync-horizontalbar-XPVista7@2x.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/syncProgress-horizontalbar.png chrome://browser/skin/syncProgress-horizontalbar-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/syncProgress-horizontalbar@2x.png chrome://browser/skin/syncProgress-horizontalbar-XPVista7@2x.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/syncProgress-toolbar.png chrome://browser/skin/syncProgress-toolbar-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/syncProgress-toolbar@2x.png chrome://browser/skin/syncProgress-toolbar-XPVista7@2x.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/toolbarbutton-dropdown-arrow.png chrome://browser/skin/toolbarbutton-dropdown-arrow-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/urlbar-history-dropmarker.png chrome://browser/skin/urlbar-history-dropmarker-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/urlbar-history-dropmarker@2x.png chrome://browser/skin/urlbar-history-dropmarker-XPVista7@2x.png os=WINNT osversion<=6.1
|
||||
|
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 10 KiB |
@ -164,13 +164,8 @@ NS_INTERFACE_MAP_END
|
||||
/* static */ bool
|
||||
CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject)
|
||||
{
|
||||
JS::Rooted<JSObject*> obj(aCx, aObject);
|
||||
if (Preferences::GetBool("dom.webcomponents.customelements.enabled") ||
|
||||
Preferences::GetBool("dom.webcomponents.enabled")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return Preferences::GetBool("dom.webcomponents.customelements.enabled") ||
|
||||
Preferences::GetBool("dom.webcomponents.enabled");
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<CustomElementRegistry>
|
||||
@ -183,8 +178,7 @@ CustomElementRegistry::Create(nsPIDOMWindowInner* aWindow)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!Preferences::GetBool("dom.webcomponents.customelements.enabled") &&
|
||||
!Preferences::GetBool("dom.webcomponents.enabled")) {
|
||||
if (!IsCustomElementEnabled()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,8 @@ public:
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementRegistry)
|
||||
|
||||
public:
|
||||
static bool IsCustomElementEnabled(JSContext* aCx, JSObject* aObject);
|
||||
static bool IsCustomElementEnabled(JSContext* aCx = nullptr,
|
||||
JSObject* aObject = nullptr);
|
||||
static already_AddRefed<CustomElementRegistry> Create(nsPIDOMWindowInner* aWindow);
|
||||
static void ProcessTopElementQueue();
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/EventStates.h"
|
||||
#include "mozilla/ServoRestyleManager.h"
|
||||
#include "mozilla/dom/Attr.h"
|
||||
#include "nsDOMAttributeMap.h"
|
||||
#include "nsIAtom.h"
|
||||
@ -1112,6 +1113,12 @@ FragmentOrElement::SetXBLInsertionParent(nsIContent* aContent)
|
||||
slots->mXBLInsertionParent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// We just changed the flattened tree, so any Servo style data is now invalid.
|
||||
// We rely on nsXBLService::LoadBindings to re-traverse the subtree afterwards.
|
||||
if (IsStyledByServo() && IsElement() && AsElement()->HasServoData()) {
|
||||
ServoRestyleManager::ClearServoDataFromSubtree(AsElement());
|
||||
}
|
||||
}
|
||||
|
||||
CustomElementData*
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "nsITimeoutHandler.h"
|
||||
#include "mozilla/dom/TabGroup.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
static int32_t gRunningTimeoutDepth = 0;
|
||||
@ -227,7 +228,8 @@ TimeoutManager::SetTimeout(nsITimeoutHandler* aHandler,
|
||||
}
|
||||
}
|
||||
|
||||
InsertTimeoutIntoList(timeout);
|
||||
mTimeouts.Insert(timeout, mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
|
||||
: Timeouts::SortBy::TimeWhen);
|
||||
|
||||
timeout->mTimeoutId = GetTimeoutId(aReason);
|
||||
*aReturn = timeout->mTimeoutId;
|
||||
@ -239,30 +241,30 @@ void
|
||||
TimeoutManager::ClearTimeout(int32_t aTimerId, Timeout::Reason aReason)
|
||||
{
|
||||
uint32_t timerId = (uint32_t)aTimerId;
|
||||
Timeout* timeout;
|
||||
|
||||
for (timeout = mTimeouts.GetFirst(); timeout; timeout = timeout->getNext()) {
|
||||
if (timeout->mTimeoutId == timerId && timeout->mReason == aReason) {
|
||||
if (timeout->mRunning) {
|
||||
/* We're running from inside the timeout. Mark this
|
||||
timeout for deferred deletion by the code in
|
||||
ForEachTimeoutAbortable([&](Timeout* aTimeout) {
|
||||
if (aTimeout->mTimeoutId == timerId && aTimeout->mReason == aReason) {
|
||||
if (aTimeout->mRunning) {
|
||||
/* We're running from inside the aTimeout. Mark this
|
||||
aTimeout for deferred deletion by the code in
|
||||
RunTimeout() */
|
||||
timeout->mIsInterval = false;
|
||||
aTimeout->mIsInterval = false;
|
||||
}
|
||||
else {
|
||||
/* Delete the timeout from the pending timeout list */
|
||||
timeout->remove();
|
||||
/* Delete the aTimeout from the pending aTimeout list */
|
||||
aTimeout->remove();
|
||||
|
||||
if (timeout->mTimer) {
|
||||
timeout->mTimer->Cancel();
|
||||
timeout->mTimer = nullptr;
|
||||
timeout->Release();
|
||||
if (aTimeout->mTimer) {
|
||||
aTimeout->mTimer->Cancel();
|
||||
aTimeout->mTimer = nullptr;
|
||||
aTimeout->Release();
|
||||
}
|
||||
timeout->Release();
|
||||
aTimeout->Release();
|
||||
}
|
||||
break;
|
||||
return true; // abort!
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@ -420,7 +422,8 @@ TimeoutManager::RunTimeout(Timeout* aTimeout)
|
||||
if (needsReinsertion) {
|
||||
// Insert interval timeout onto list sorted in deadline order.
|
||||
// AddRefs timeout.
|
||||
InsertTimeoutIntoList(timeout);
|
||||
mTimeouts.Insert(timeout, mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
|
||||
: Timeouts::SortBy::TimeWhen);
|
||||
}
|
||||
|
||||
// Release the timeout struct since it's possibly out of the list
|
||||
@ -629,6 +632,21 @@ TimeoutManager::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Timeouts::SortBy sortBy = mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
|
||||
: Timeouts::SortBy::TimeWhen;
|
||||
|
||||
return mTimeouts.ResetTimersForThrottleReduction(aPreviousThrottleDelayMS,
|
||||
DOMMinTimeoutValue(),
|
||||
sortBy,
|
||||
mWindow.GetThrottledEventQueue());
|
||||
}
|
||||
|
||||
nsresult
|
||||
TimeoutManager::Timeouts::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
|
||||
int32_t aMinTimeoutValueMS,
|
||||
SortBy aSortBy,
|
||||
ThrottledEventQueue* aQueue)
|
||||
{
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
// If insertion point is non-null, we're in the middle of firing timers and
|
||||
@ -638,8 +656,8 @@ TimeoutManager::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS
|
||||
// point or anything before it, so should start at the timer after insertion
|
||||
// point, if there is one.
|
||||
// Otherwise, start at the beginning of the list.
|
||||
for (Timeout* timeout = mTimeouts.InsertionPoint() ?
|
||||
mTimeouts.InsertionPoint()->getNext() : mTimeouts.GetFirst();
|
||||
for (Timeout* timeout = InsertionPoint() ?
|
||||
InsertionPoint()->getNext() : GetFirst();
|
||||
timeout; ) {
|
||||
// It's important that this check be <= so that we guarantee that
|
||||
// taking std::max with |now| won't make a quantity equal to
|
||||
@ -662,7 +680,7 @@ TimeoutManager::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS
|
||||
// background window
|
||||
TimeDuration interval =
|
||||
TimeDuration::FromMilliseconds(std::max(timeout->mInterval,
|
||||
uint32_t(DOMMinTimeoutValue())));
|
||||
uint32_t(aMinTimeoutValueMS)));
|
||||
uint32_t oldIntervalMillisecs = 0;
|
||||
timeout->mTimer->GetDelay(&oldIntervalMillisecs);
|
||||
TimeDuration oldInterval = TimeDuration::FromMilliseconds(oldIntervalMillisecs);
|
||||
@ -690,15 +708,14 @@ TimeoutManager::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS
|
||||
NS_ASSERTION(!nextTimeout ||
|
||||
timeout->mWhen < nextTimeout->mWhen, "How did that happen?");
|
||||
timeout->remove();
|
||||
// InsertTimeoutIntoList will addref |timeout| and reset
|
||||
// mFiringDepth. Make sure to undo that after calling it.
|
||||
// Insert() will addref |timeout| and reset mFiringDepth. Make sure to
|
||||
// undo that after calling it.
|
||||
uint32_t firingDepth = timeout->mFiringDepth;
|
||||
InsertTimeoutIntoList(timeout);
|
||||
Insert(timeout, aSortBy);
|
||||
timeout->mFiringDepth = firingDepth;
|
||||
timeout->Release();
|
||||
|
||||
nsresult rv = timeout->InitTimer(mWindow.GetThrottledEventQueue(),
|
||||
delay.ToMilliseconds());
|
||||
nsresult rv = timeout->InitTimer(aQueue, delay.ToMilliseconds());
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Error resetting non background timer for DOM timeout!");
|
||||
@ -717,36 +734,37 @@ TimeoutManager::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS
|
||||
void
|
||||
TimeoutManager::ClearAllTimeouts()
|
||||
{
|
||||
Timeout* timeout;
|
||||
Timeout* nextTimeout;
|
||||
bool seenRunningTimeout = false;
|
||||
|
||||
for (timeout = mTimeouts.GetFirst(); timeout; timeout = nextTimeout) {
|
||||
ForEachTimeout([&](Timeout* aTimeout) {
|
||||
/* If RunTimeout() is higher up on the stack for this
|
||||
window, e.g. as a result of document.write from a timeout,
|
||||
then we need to reset the list insertion point for
|
||||
newly-created timeouts in case the user adds a timeout,
|
||||
before we pop the stack back to RunTimeout. */
|
||||
if (mRunningTimeout == timeout) {
|
||||
mTimeouts.SetInsertionPoint(nullptr);
|
||||
if (mRunningTimeout == aTimeout) {
|
||||
seenRunningTimeout = true;
|
||||
}
|
||||
|
||||
nextTimeout = timeout->getNext();
|
||||
|
||||
if (timeout->mTimer) {
|
||||
timeout->mTimer->Cancel();
|
||||
timeout->mTimer = nullptr;
|
||||
if (aTimeout->mTimer) {
|
||||
aTimeout->mTimer->Cancel();
|
||||
aTimeout->mTimer = nullptr;
|
||||
|
||||
// Drop the count since the timer isn't going to hold on
|
||||
// anymore.
|
||||
timeout->Release();
|
||||
aTimeout->Release();
|
||||
}
|
||||
|
||||
// Set timeout->mCleared to true to indicate that the timeout was
|
||||
// cleared and taken out of the list of timeouts
|
||||
timeout->mCleared = true;
|
||||
aTimeout->mCleared = true;
|
||||
|
||||
// Drop the count since we're removing it from the list.
|
||||
timeout->Release();
|
||||
aTimeout->Release();
|
||||
});
|
||||
|
||||
if (seenRunningTimeout) {
|
||||
mTimeouts.SetInsertionPoint(nullptr);
|
||||
}
|
||||
|
||||
// Clear out our list
|
||||
@ -754,16 +772,16 @@ TimeoutManager::ClearAllTimeouts()
|
||||
}
|
||||
|
||||
void
|
||||
TimeoutManager::InsertTimeoutIntoList(Timeout* aTimeout)
|
||||
TimeoutManager::Timeouts::Insert(Timeout* aTimeout, SortBy aSortBy)
|
||||
{
|
||||
// Start at mLastTimeout and go backwards. Don't go further than insertion
|
||||
// point, though. This optimizes for the common case of insertion at the end.
|
||||
Timeout* prevSibling;
|
||||
for (prevSibling = mTimeouts.GetLast();
|
||||
prevSibling && prevSibling != mTimeouts.InsertionPoint() &&
|
||||
for (prevSibling = GetLast();
|
||||
prevSibling && prevSibling != InsertionPoint() &&
|
||||
// This condition needs to match the one in SetTimeoutOrInterval that
|
||||
// determines whether to set mWhen or mTimeRemaining.
|
||||
(mWindow.IsFrozen() ?
|
||||
(aSortBy == SortBy::TimeRemaining ?
|
||||
prevSibling->mTimeRemaining > aTimeout->mTimeRemaining :
|
||||
prevSibling->mWhen > aTimeout->mWhen);
|
||||
prevSibling = prevSibling->getPrevious()) {
|
||||
@ -774,7 +792,7 @@ TimeoutManager::InsertTimeoutIntoList(Timeout* aTimeout)
|
||||
if (prevSibling) {
|
||||
prevSibling->setNext(aTimeout);
|
||||
} else {
|
||||
mTimeouts.InsertFront(aTimeout);
|
||||
InsertFront(aTimeout);
|
||||
}
|
||||
|
||||
aTimeout->mFiringDepth = 0;
|
||||
@ -808,33 +826,31 @@ TimeoutManager::EndRunningTimeout(Timeout* aTimeout)
|
||||
void
|
||||
TimeoutManager::UnmarkGrayTimers()
|
||||
{
|
||||
for (Timeout* timeout = mTimeouts.GetFirst();
|
||||
timeout;
|
||||
timeout = timeout->getNext()) {
|
||||
if (timeout->mScriptHandler) {
|
||||
timeout->mScriptHandler->MarkForCC();
|
||||
ForEachTimeout([](Timeout* aTimeout) {
|
||||
if (aTimeout->mScriptHandler) {
|
||||
aTimeout->mScriptHandler->MarkForCC();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimeoutManager::Suspend()
|
||||
{
|
||||
for (Timeout* t = mTimeouts.GetFirst(); t; t = t->getNext()) {
|
||||
ForEachTimeout([](Timeout* aTimeout) {
|
||||
// Leave the timers with the current time remaining. This will
|
||||
// cause the timers to potentially fire when the window is
|
||||
// Resume()'d. Time effectively passes while suspended.
|
||||
|
||||
// Drop the XPCOM timer; we'll reschedule when restoring the state.
|
||||
if (t->mTimer) {
|
||||
t->mTimer->Cancel();
|
||||
t->mTimer = nullptr;
|
||||
if (aTimeout->mTimer) {
|
||||
aTimeout->mTimer->Cancel();
|
||||
aTimeout->mTimer = nullptr;
|
||||
|
||||
// Drop the reference that the timer's closure had on this timeout, we'll
|
||||
// add it back in Resume().
|
||||
t->Release();
|
||||
aTimeout->Release();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@ -843,67 +859,67 @@ TimeoutManager::Resume()
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
DebugOnly<bool> _seenDummyTimeout = false;
|
||||
|
||||
for (Timeout* t = mTimeouts.GetFirst(); t; t = t->getNext()) {
|
||||
ForEachTimeout([&](Timeout* aTimeout) {
|
||||
// There's a chance we're being called with RunTimeout on the stack in which
|
||||
// case we have a dummy timeout in the list that *must not* be resumed. It
|
||||
// can be identified by a null mWindow.
|
||||
if (!t->mWindow) {
|
||||
if (!aTimeout->mWindow) {
|
||||
NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
|
||||
_seenDummyTimeout = true;
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!t->mTimer);
|
||||
MOZ_ASSERT(!aTimeout->mTimer);
|
||||
|
||||
// The timeout mWhen is set to the absolute time when the timer should
|
||||
// fire. Recalculate the delay from now until that deadline. If the
|
||||
// the deadline has already passed or falls within our minimum delay
|
||||
// deadline, then clamp the resulting value to the minimum delay. The
|
||||
// mWhen will remain at its absolute time, but we won't fire the OS
|
||||
// mWhen will remain at its absolute time, but we won'aTimeout fire the OS
|
||||
// timer until our calculated delay has passed.
|
||||
int32_t remaining = 0;
|
||||
if (t->mWhen > now) {
|
||||
remaining = static_cast<int32_t>((t->mWhen - now).ToMilliseconds());
|
||||
if (aTimeout->mWhen > now) {
|
||||
remaining = static_cast<int32_t>((aTimeout->mWhen - now).ToMilliseconds());
|
||||
}
|
||||
uint32_t delay = std::max(remaining, DOMMinTimeoutValue());
|
||||
|
||||
t->mTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
if (!t->mTimer) {
|
||||
t->remove();
|
||||
continue;
|
||||
aTimeout->mTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
if (!aTimeout->mTimer) {
|
||||
aTimeout->remove();
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = t->InitTimer(mWindow.GetThrottledEventQueue(), delay);
|
||||
nsresult rv = aTimeout->InitTimer(mWindow.GetThrottledEventQueue(), delay);
|
||||
if (NS_FAILED(rv)) {
|
||||
t->mTimer = nullptr;
|
||||
t->remove();
|
||||
continue;
|
||||
aTimeout->mTimer = nullptr;
|
||||
aTimeout->remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a reference for the new timer's closure.
|
||||
t->AddRef();
|
||||
}
|
||||
aTimeout->AddRef();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimeoutManager::Freeze()
|
||||
{
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
for (Timeout *t = mTimeouts.GetFirst(); t; t = t->getNext()) {
|
||||
ForEachTimeout([&](Timeout* aTimeout) {
|
||||
// Save the current remaining time for this timeout. We will
|
||||
// re-apply it when the window is Thaw()'d. This effectively
|
||||
// shifts timers to the right as if time does not pass while
|
||||
// the window is frozen.
|
||||
if (t->mWhen > now) {
|
||||
t->mTimeRemaining = t->mWhen - now;
|
||||
if (aTimeout->mWhen > now) {
|
||||
aTimeout->mTimeRemaining = aTimeout->mWhen - now;
|
||||
} else {
|
||||
t->mTimeRemaining = TimeDuration(0);
|
||||
aTimeout->mTimeRemaining = TimeDuration(0);
|
||||
}
|
||||
|
||||
// Since we are suspended there should be no OS timer set for
|
||||
// this timeout entry.
|
||||
MOZ_ASSERT(!t->mTimer);
|
||||
}
|
||||
MOZ_ASSERT(!aTimeout->mTimer);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@ -912,19 +928,19 @@ TimeoutManager::Thaw()
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
DebugOnly<bool> _seenDummyTimeout = false;
|
||||
|
||||
for (Timeout *t = mTimeouts.GetFirst(); t; t = t->getNext()) {
|
||||
ForEachTimeout([&](Timeout* aTimeout) {
|
||||
// There's a chance we're being called with RunTimeout on the stack in which
|
||||
// case we have a dummy timeout in the list that *must not* be resumed. It
|
||||
// can be identified by a null mWindow.
|
||||
if (!t->mWindow) {
|
||||
if (!aTimeout->mWindow) {
|
||||
NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
|
||||
_seenDummyTimeout = true;
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set mWhen back to the time when the timer is supposed to fire.
|
||||
t->mWhen = now + t->mTimeRemaining;
|
||||
aTimeout->mWhen = now + aTimeout->mTimeRemaining;
|
||||
|
||||
MOZ_ASSERT(!t->mTimer);
|
||||
}
|
||||
MOZ_ASSERT(!aTimeout->mTimer);
|
||||
});
|
||||
}
|
||||
|
@ -13,6 +13,9 @@ class nsITimeoutHandler;
|
||||
class nsGlobalWindow;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ThrottledEventQueue;
|
||||
|
||||
namespace dom {
|
||||
|
||||
// This class manages the timeouts in a Window's setTimeout/setInterval pool.
|
||||
@ -44,9 +47,6 @@ public:
|
||||
bool aRunningPendingTimeouts);
|
||||
|
||||
void ClearAllTimeouts();
|
||||
// Insert aTimeout into the list, before all timeouts that would
|
||||
// fire after it, but no earlier than mTimeoutInsertionPoint, if any.
|
||||
void InsertTimeoutIntoList(mozilla::dom::Timeout* aTimeout);
|
||||
uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
|
||||
|
||||
// Apply back pressure to the window if the TabGroup ThrottledEventQueue
|
||||
@ -87,11 +87,15 @@ public:
|
||||
template <class Callable>
|
||||
void ForEachTimeout(Callable c)
|
||||
{
|
||||
for (Timeout* timeout = mTimeouts.GetFirst();
|
||||
timeout;
|
||||
timeout = timeout->getNext()) {
|
||||
c(timeout);
|
||||
}
|
||||
mTimeouts.ForEach(c);
|
||||
}
|
||||
|
||||
// Run some code for each Timeout in our list, but let the callback cancel
|
||||
// the iteration by returning true.
|
||||
template <class Callable>
|
||||
void ForEachTimeoutAbortable(Callable c)
|
||||
{
|
||||
mTimeouts.ForEachAbortable(c);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -105,6 +109,19 @@ private:
|
||||
{
|
||||
}
|
||||
|
||||
// Insert aTimeout into the list, before all timeouts that would
|
||||
// fire after it, but no earlier than mTimeoutInsertionPoint, if any.
|
||||
enum class SortBy
|
||||
{
|
||||
TimeRemaining,
|
||||
TimeWhen
|
||||
};
|
||||
void Insert(mozilla::dom::Timeout* aTimeout, SortBy aSortBy);
|
||||
nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
|
||||
int32_t aMinTimeoutValueMS,
|
||||
SortBy aSortBy,
|
||||
mozilla::ThrottledEventQueue* aQueue);
|
||||
|
||||
const Timeout* GetFirst() const { return mTimeoutList.getFirst(); }
|
||||
Timeout* GetFirst() { return mTimeoutList.getFirst(); }
|
||||
const Timeout* GetLast() const { return mTimeoutList.getLast(); }
|
||||
@ -122,6 +139,28 @@ private:
|
||||
return mTimeoutInsertionPoint;
|
||||
}
|
||||
|
||||
template <class Callable>
|
||||
void ForEach(Callable c)
|
||||
{
|
||||
for (Timeout* timeout = GetFirst();
|
||||
timeout;
|
||||
timeout = timeout->getNext()) {
|
||||
c(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Callable>
|
||||
void ForEachAbortable(Callable c)
|
||||
{
|
||||
for (Timeout* timeout = GetFirst();
|
||||
timeout;
|
||||
timeout = timeout->getNext()) {
|
||||
if (c(timeout)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// mTimeoutList is generally sorted by mWhen, unless mTimeoutInsertionPoint is
|
||||
// non-null. In that case, the dummy timeout pointed to by
|
||||
|
@ -12737,7 +12737,7 @@ nsDocument::CheckCustomElementName(const ElementCreationOptions& aOptions,
|
||||
// only check aOptions if 'is' is passed and the webcomponents preference
|
||||
// is enabled
|
||||
if (!aOptions.mIs.WasPassed() ||
|
||||
!Preferences::GetBool("dom.webcomponents.enabled")) {
|
||||
!CustomElementRegistry::IsCustomElementEnabled()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -2221,190 +2221,6 @@ Intersect(uint32_t srcSize, int32_t dstStartInSrc, uint32_t dstSize,
|
||||
*out_intSize = std::max<int32_t>(0, intEndInSrc - *out_intStartInSrc);
|
||||
}
|
||||
|
||||
static bool
|
||||
ZeroTexImageWithClear(WebGLContext* webgl, GLContext* gl, TexImageTarget target,
|
||||
GLuint tex, uint32_t level, const webgl::FormatUsageInfo* usage,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
MOZ_ASSERT(gl->IsCurrent());
|
||||
|
||||
ScopedFramebuffer scopedFB(gl);
|
||||
ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
|
||||
|
||||
const auto format = usage->format;
|
||||
|
||||
GLenum attachPoint = 0;
|
||||
GLbitfield clearBits = 0;
|
||||
|
||||
if (format->IsColorFormat()) {
|
||||
attachPoint = LOCAL_GL_COLOR_ATTACHMENT0;
|
||||
clearBits = LOCAL_GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
|
||||
if (format->d) {
|
||||
attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
|
||||
clearBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
|
||||
if (format->s) {
|
||||
attachPoint = (format->d ? LOCAL_GL_DEPTH_STENCIL_ATTACHMENT
|
||||
: LOCAL_GL_STENCIL_ATTACHMENT);
|
||||
clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
|
||||
}
|
||||
|
||||
MOZ_RELEASE_ASSERT(attachPoint && clearBits, "GFX: No bits cleared.");
|
||||
|
||||
{
|
||||
gl::GLContext::LocalErrorScope errorScope(*gl);
|
||||
gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint, target.get(), tex,
|
||||
level);
|
||||
if (errorScope.GetError()) {
|
||||
MOZ_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
|
||||
if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
|
||||
return false;
|
||||
|
||||
{
|
||||
gl::GLContext::LocalErrorScope errorScope(*gl);
|
||||
|
||||
const bool fakeNoAlpha = false;
|
||||
webgl->ForceClearFramebufferWithDefaultValues(clearBits, fakeNoAlpha);
|
||||
if (errorScope.GetError()) {
|
||||
MOZ_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ZeroTextureData(WebGLContext* webgl, const char* funcName, GLuint tex,
|
||||
TexImageTarget target, uint32_t level,
|
||||
const webgl::FormatUsageInfo* usage, uint32_t xOffset, uint32_t yOffset,
|
||||
uint32_t zOffset, uint32_t width, uint32_t height, uint32_t depth)
|
||||
{
|
||||
// This has two usecases:
|
||||
// 1. Lazy zeroing of uninitialized textures:
|
||||
// a. Before draw, when FakeBlack isn't viable. (TexStorage + Draw*)
|
||||
// b. Before partial upload. (TexStorage + TexSubImage)
|
||||
// 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image)
|
||||
|
||||
// We have no sympathy for any of these cases.
|
||||
|
||||
// "Doctor, it hurts when I do this!" "Well don't do that!"
|
||||
webgl->GenerateWarning("%s: This operation requires zeroing texture data. This is"
|
||||
" slow.",
|
||||
funcName);
|
||||
|
||||
gl::GLContext* gl = webgl->GL();
|
||||
gl->MakeCurrent();
|
||||
|
||||
GLenum scopeBindTarget;
|
||||
switch (target.get()) {
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
|
||||
scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
|
||||
break;
|
||||
default:
|
||||
scopeBindTarget = target.get();
|
||||
break;
|
||||
}
|
||||
ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
|
||||
auto compression = usage->format->compression;
|
||||
if (compression) {
|
||||
MOZ_RELEASE_ASSERT(!xOffset && !yOffset && !zOffset, "GFX: Can't zero compressed texture with offsets.");
|
||||
|
||||
auto sizedFormat = usage->format->sizedFormat;
|
||||
MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
|
||||
|
||||
const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) {
|
||||
return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
|
||||
};
|
||||
|
||||
const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
|
||||
const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
|
||||
|
||||
CheckedUint32 checkedByteCount = compression->bytesPerBlock;
|
||||
checkedByteCount *= widthBlocks;
|
||||
checkedByteCount *= heightBlocks;
|
||||
checkedByteCount *= depth;
|
||||
|
||||
if (!checkedByteCount.isValid())
|
||||
return false;
|
||||
|
||||
const size_t byteCount = checkedByteCount.value();
|
||||
|
||||
UniqueBuffer zeros = calloc(1, byteCount);
|
||||
if (!zeros)
|
||||
return false;
|
||||
|
||||
ScopedUnpackReset scopedReset(webgl);
|
||||
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it
|
||||
// well.
|
||||
|
||||
GLenum error = DoCompressedTexSubImage(gl, target.get(), level, xOffset, yOffset,
|
||||
zOffset, width, height, depth, sizedFormat,
|
||||
byteCount, zeros.get());
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto driverUnpackInfo = usage->idealUnpack;
|
||||
MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
|
||||
|
||||
if (usage->IsRenderable() && depth == 1 &&
|
||||
!xOffset && !yOffset && !zOffset)
|
||||
{
|
||||
// While we would like to skip the extra complexity of trying to zero with an FB
|
||||
// clear, ANGLE_depth_texture requires this.
|
||||
do {
|
||||
if (ZeroTexImageWithClear(webgl, gl, target, tex, level, usage, width,
|
||||
height))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} while (false);
|
||||
}
|
||||
|
||||
const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
|
||||
|
||||
const auto bytesPerPixel = webgl::BytesPerPixel(packing);
|
||||
|
||||
CheckedUint32 checkedByteCount = bytesPerPixel;
|
||||
checkedByteCount *= width;
|
||||
checkedByteCount *= height;
|
||||
checkedByteCount *= depth;
|
||||
|
||||
if (!checkedByteCount.isValid())
|
||||
return false;
|
||||
|
||||
const size_t byteCount = checkedByteCount.value();
|
||||
|
||||
UniqueBuffer zeros = calloc(1, byteCount);
|
||||
if (!zeros)
|
||||
return false;
|
||||
|
||||
ScopedUnpackReset scopedReset(webgl);
|
||||
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well.
|
||||
const auto error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width,
|
||||
height, depth, packing, zeros.get());
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CheckedUint32
|
||||
|
@ -2132,12 +2132,6 @@ Intersect(uint32_t srcSize, int32_t dstStartInSrc, uint32_t dstSize,
|
||||
uint32_t* const out_intStartInSrc, uint32_t* const out_intStartInDst,
|
||||
uint32_t* const out_intSize);
|
||||
|
||||
bool
|
||||
ZeroTextureData(WebGLContext* webgl, const char* funcName, GLuint tex,
|
||||
TexImageTarget target, uint32_t level,
|
||||
const webgl::FormatUsageInfo* usage, uint32_t xOffset, uint32_t yOffset,
|
||||
uint32_t zOffset, uint32_t width, uint32_t height, uint32_t depth);
|
||||
|
||||
////
|
||||
|
||||
void
|
||||
|
@ -583,6 +583,160 @@ WebGLTexture::EnsureImageDataInitialized(const char* funcName, TexImageTarget ta
|
||||
return InitializeImageData(funcName, target, level);
|
||||
}
|
||||
|
||||
static void
|
||||
ZeroANGLEDepthTexture(WebGLContext* webgl, GLuint tex,
|
||||
const webgl::FormatUsageInfo* usage, uint32_t width,
|
||||
uint32_t height)
|
||||
{
|
||||
const auto& format = usage->format;
|
||||
GLenum attachPoint = 0;
|
||||
GLbitfield clearBits = 0;
|
||||
|
||||
if (format->d) {
|
||||
attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
|
||||
clearBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
|
||||
if (format->s) {
|
||||
attachPoint = (format->d ? LOCAL_GL_DEPTH_STENCIL_ATTACHMENT
|
||||
: LOCAL_GL_STENCIL_ATTACHMENT);
|
||||
clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
|
||||
}
|
||||
|
||||
MOZ_RELEASE_ASSERT(attachPoint && clearBits, "GFX: No bits cleared.");
|
||||
|
||||
////
|
||||
const auto& gl = webgl->gl;
|
||||
MOZ_ASSERT(gl->IsCurrent());
|
||||
|
||||
gl::ScopedFramebuffer scopedFB(gl);
|
||||
const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
|
||||
|
||||
gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint, LOCAL_GL_TEXTURE_2D,
|
||||
tex, 0);
|
||||
|
||||
const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
|
||||
MOZ_RELEASE_ASSERT(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
|
||||
|
||||
////
|
||||
|
||||
const bool fakeNoAlpha = false;
|
||||
webgl->ForceClearFramebufferWithDefaultValues(clearBits, fakeNoAlpha);
|
||||
}
|
||||
|
||||
static bool
|
||||
ZeroTextureData(WebGLContext* webgl, const char* funcName, GLuint tex,
|
||||
TexImageTarget target, uint32_t level,
|
||||
const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height,
|
||||
uint32_t depth)
|
||||
{
|
||||
// This has two usecases:
|
||||
// 1. Lazy zeroing of uninitialized textures:
|
||||
// a. Before draw, when FakeBlack isn't viable. (TexStorage + Draw*)
|
||||
// b. Before partial upload. (TexStorage + TexSubImage)
|
||||
// 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image)
|
||||
|
||||
// We have no sympathy for any of these cases.
|
||||
|
||||
// "Doctor, it hurts when I do this!" "Well don't do that!"
|
||||
webgl->GenerateWarning("%s: This operation requires zeroing texture data. This is"
|
||||
" slow.",
|
||||
funcName);
|
||||
|
||||
gl::GLContext* gl = webgl->GL();
|
||||
gl->MakeCurrent();
|
||||
|
||||
GLenum scopeBindTarget;
|
||||
switch (target.get()) {
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
|
||||
case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
|
||||
scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
|
||||
break;
|
||||
default:
|
||||
scopeBindTarget = target.get();
|
||||
break;
|
||||
}
|
||||
const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
|
||||
auto compression = usage->format->compression;
|
||||
if (compression) {
|
||||
auto sizedFormat = usage->format->sizedFormat;
|
||||
MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
|
||||
|
||||
const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) {
|
||||
return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
|
||||
};
|
||||
|
||||
const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
|
||||
const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
|
||||
|
||||
CheckedUint32 checkedByteCount = compression->bytesPerBlock;
|
||||
checkedByteCount *= widthBlocks;
|
||||
checkedByteCount *= heightBlocks;
|
||||
checkedByteCount *= depth;
|
||||
|
||||
if (!checkedByteCount.isValid())
|
||||
return false;
|
||||
|
||||
const size_t byteCount = checkedByteCount.value();
|
||||
|
||||
UniqueBuffer zeros = calloc(1, byteCount);
|
||||
if (!zeros)
|
||||
return false;
|
||||
|
||||
ScopedUnpackReset scopedReset(webgl);
|
||||
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it
|
||||
// well.
|
||||
|
||||
const auto error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, 0,
|
||||
width, height, depth, sizedFormat,
|
||||
byteCount, zeros.get());
|
||||
return !error;
|
||||
}
|
||||
|
||||
const auto driverUnpackInfo = usage->idealUnpack;
|
||||
MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
|
||||
|
||||
if (webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) &&
|
||||
gl->IsANGLE() &&
|
||||
usage->format->d)
|
||||
{
|
||||
// ANGLE_depth_texture does not allow uploads, so we have to clear.
|
||||
// (Restriction because of D3D9)
|
||||
MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D);
|
||||
MOZ_ASSERT(level == 0);
|
||||
ZeroANGLEDepthTexture(webgl, tex, usage, width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
|
||||
|
||||
const auto bytesPerPixel = webgl::BytesPerPixel(packing);
|
||||
|
||||
CheckedUint32 checkedByteCount = bytesPerPixel;
|
||||
checkedByteCount *= width;
|
||||
checkedByteCount *= height;
|
||||
checkedByteCount *= depth;
|
||||
|
||||
if (!checkedByteCount.isValid())
|
||||
return false;
|
||||
|
||||
const size_t byteCount = checkedByteCount.value();
|
||||
|
||||
UniqueBuffer zeros = calloc(1, byteCount);
|
||||
if (!zeros)
|
||||
return false;
|
||||
|
||||
ScopedUnpackReset scopedReset(webgl);
|
||||
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well.
|
||||
const auto error = DoTexSubImage(gl, target, level, 0, 0, 0, width, height, depth,
|
||||
packing, zeros.get());
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLTexture::InitializeImageData(const char* funcName, TexImageTarget target,
|
||||
uint32_t level)
|
||||
@ -596,8 +750,8 @@ WebGLTexture::InitializeImageData(const char* funcName, TexImageTarget target,
|
||||
const auto& height = imageInfo.mHeight;
|
||||
const auto& depth = imageInfo.mDepth;
|
||||
|
||||
if (!ZeroTextureData(mContext, funcName, mGLName, target, level, usage, 0, 0, 0,
|
||||
width, height, depth))
|
||||
if (!ZeroTextureData(mContext, funcName, mGLName, target, level, usage, width, height,
|
||||
depth))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -1930,6 +1930,90 @@ WebGLTexture::ValidateCopyTexImageForFeedback(const char* funcName, uint32_t lev
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
DoCopyTexOrSubImage(WebGLContext* webgl, const char* funcName, bool isSubImage,
|
||||
const WebGLTexture* tex, TexImageTarget target, GLint level,
|
||||
GLint xWithinSrc, GLint yWithinSrc,
|
||||
uint32_t srcTotalWidth, uint32_t srcTotalHeight,
|
||||
const webgl::FormatUsageInfo* srcUsage,
|
||||
GLint xOffset, GLint yOffset, GLint zOffset,
|
||||
uint32_t dstWidth, uint32_t dstHeight,
|
||||
const webgl::FormatUsageInfo* dstUsage)
|
||||
{
|
||||
gl::GLContext* gl = webgl->gl;
|
||||
gl->MakeCurrent();
|
||||
|
||||
////
|
||||
|
||||
uint32_t readX, readY;
|
||||
uint32_t writeX, writeY;
|
||||
uint32_t rwWidth, rwHeight;
|
||||
Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX, &rwWidth);
|
||||
Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY, &rwHeight);
|
||||
|
||||
////
|
||||
|
||||
GLenum error = 0;
|
||||
do {
|
||||
const auto& idealUnpack = dstUsage->idealUnpack;
|
||||
if (!isSubImage) {
|
||||
UniqueBuffer buffer;
|
||||
|
||||
if (rwWidth != dstWidth || rwHeight != dstHeight) {
|
||||
const auto& pi = idealUnpack->ToPacking();
|
||||
CheckedUint32 byteCount = BytesPerPixel(pi);
|
||||
byteCount *= dstWidth;
|
||||
byteCount *= dstHeight;
|
||||
|
||||
if (byteCount.isValid()) {
|
||||
buffer = calloc(1, byteCount.value());
|
||||
}
|
||||
|
||||
if (!buffer.get()) {
|
||||
webgl->ErrorOutOfMemory("%s: Ran out of memory allocating zeros.",
|
||||
funcName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const ScopedUnpackReset unpackReset(webgl);
|
||||
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
|
||||
error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight, 1,
|
||||
buffer.get());
|
||||
if (error)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rwWidth || !rwHeight) {
|
||||
// There aren't any pixels to copy, so we're 'done'.
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& srcFormat = srcUsage->format;
|
||||
ScopedCopyTexImageSource maybeSwizzle(webgl, funcName, srcTotalWidth,
|
||||
srcTotalHeight, srcFormat, dstUsage);
|
||||
|
||||
const uint8_t zOffset = 0;
|
||||
error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
|
||||
readY, rwWidth, rwHeight);
|
||||
if (error)
|
||||
break;
|
||||
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
if (error == LOCAL_GL_OUT_OF_MEMORY) {
|
||||
webgl->ErrorOutOfMemory("%s: Ran out of memory during texture copy.", funcName);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors.");
|
||||
webgl->GenerateWarning("%s: Unexpected error during texture copy. Context lost.",
|
||||
funcName);
|
||||
webgl->ForceLoseContext();
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is no CopyTexImage3D.
|
||||
void
|
||||
WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internalFormat,
|
||||
@ -1960,11 +2044,13 @@ WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internal
|
||||
// Get source info
|
||||
|
||||
const webgl::FormatUsageInfo* srcUsage;
|
||||
uint32_t srcWidth;
|
||||
uint32_t srcHeight;
|
||||
if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcWidth, &srcHeight))
|
||||
uint32_t srcTotalWidth;
|
||||
uint32_t srcTotalHeight;
|
||||
if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth,
|
||||
&srcTotalHeight))
|
||||
{
|
||||
return;
|
||||
auto srcFormat = srcUsage->format;
|
||||
}
|
||||
|
||||
if (!ValidateCopyTexImageForFeedback(funcName, level))
|
||||
return;
|
||||
@ -1972,13 +2058,13 @@ WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internal
|
||||
////////////////////////////////////
|
||||
// Check that source and dest info are compatible
|
||||
|
||||
const auto& srcFormat = srcUsage->format;
|
||||
const auto dstUsage = ValidateCopyDestUsage(funcName, mContext, srcFormat,
|
||||
internalFormat);
|
||||
if (!dstUsage)
|
||||
return;
|
||||
|
||||
const auto dstFormat = dstUsage->format;
|
||||
|
||||
const auto& dstFormat = dstUsage->format;
|
||||
if (!ValidateTargetForFormat(funcName, mContext, target, dstFormat))
|
||||
return;
|
||||
|
||||
@ -1994,63 +2080,11 @@ WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internal
|
||||
////////////////////////////////////
|
||||
// Do the thing!
|
||||
|
||||
gl::GLContext* gl = mContext->gl;
|
||||
gl->MakeCurrent();
|
||||
|
||||
ScopedCopyTexImageSource maybeSwizzle(mContext, funcName, srcWidth, srcHeight,
|
||||
srcFormat, dstUsage);
|
||||
|
||||
uint32_t readX, readY;
|
||||
uint32_t writeX, writeY;
|
||||
uint32_t rwWidth, rwHeight;
|
||||
Intersect(srcWidth, x, width, &readX, &writeX, &rwWidth);
|
||||
Intersect(srcHeight, y, height, &readY, &writeY, &rwHeight);
|
||||
|
||||
const auto& idealUnpack = dstUsage->idealUnpack;
|
||||
const auto& driverInternalFormat = idealUnpack->internalFormat;
|
||||
|
||||
GLenum error = DoCopyTexImage2D(gl, target, level, driverInternalFormat, x, y, width,
|
||||
height);
|
||||
do {
|
||||
if (rwWidth == uint32_t(width) && rwHeight == uint32_t(height))
|
||||
break;
|
||||
|
||||
if (error)
|
||||
break;
|
||||
|
||||
// 1. Zero the texture data.
|
||||
// 2. CopyTexSubImage the subrect.
|
||||
|
||||
const uint8_t zOffset = 0;
|
||||
if (!ZeroTextureData(mContext, funcName, mGLName, target, level, dstUsage, 0, 0,
|
||||
zOffset, width, height, depth))
|
||||
{
|
||||
mContext->ErrorOutOfMemory("%s: Failed to zero texture data.", funcName);
|
||||
MOZ_ASSERT(false, "Failed to zero texture data.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rwWidth || !rwHeight) {
|
||||
// There aren't any, so we're 'done'.
|
||||
mContext->DummyReadFramebufferOperation(funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
|
||||
readY, rwWidth, rwHeight);
|
||||
} while (false);
|
||||
|
||||
if (error == LOCAL_GL_OUT_OF_MEMORY) {
|
||||
mContext->ErrorOutOfMemory("%s: Ran out of memory during texture copy.",
|
||||
funcName);
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors.");
|
||||
mContext->GenerateWarning("%s: Unexpected error during texture copy. Context"
|
||||
" lost.",
|
||||
funcName);
|
||||
mContext->ForceLoseContext();
|
||||
const bool isSubImage = false;
|
||||
if (!DoCopyTexOrSubImage(mContext, funcName, isSubImage, this, target, level, x, y,
|
||||
srcTotalWidth, srcTotalHeight, srcUsage, 0, 0, 0, width,
|
||||
height, dstUsage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2087,8 +2121,8 @@ WebGLTexture::CopyTexSubImage(const char* funcName, TexImageTarget target, GLint
|
||||
|
||||
auto dstUsage = imageInfo->mFormat;
|
||||
MOZ_ASSERT(dstUsage);
|
||||
auto dstFormat = dstUsage->format;
|
||||
|
||||
auto dstFormat = dstUsage->format;
|
||||
if (!mContext->IsWebGL2() && dstFormat->d) {
|
||||
mContext->ErrorInvalidOperation("%s: Function may not be called on a texture of"
|
||||
" format %s.",
|
||||
@ -2100,11 +2134,13 @@ WebGLTexture::CopyTexSubImage(const char* funcName, TexImageTarget target, GLint
|
||||
// Get source info
|
||||
|
||||
const webgl::FormatUsageInfo* srcUsage;
|
||||
uint32_t srcWidth;
|
||||
uint32_t srcHeight;
|
||||
if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcWidth, &srcHeight))
|
||||
uint32_t srcTotalWidth;
|
||||
uint32_t srcTotalHeight;
|
||||
if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth,
|
||||
&srcTotalHeight))
|
||||
{
|
||||
return;
|
||||
auto srcFormat = srcUsage->format;
|
||||
}
|
||||
|
||||
if (!ValidateCopyTexImageForFeedback(funcName, level, zOffset))
|
||||
return;
|
||||
@ -2112,29 +2148,13 @@ WebGLTexture::CopyTexSubImage(const char* funcName, TexImageTarget target, GLint
|
||||
////////////////////////////////////
|
||||
// Check that source and dest info are compatible
|
||||
|
||||
auto srcFormat = srcUsage->format;
|
||||
if (!ValidateCopyTexImageFormats(mContext, funcName, srcFormat, dstFormat))
|
||||
return;
|
||||
|
||||
////////////////////////////////////
|
||||
// Do the thing!
|
||||
|
||||
mContext->gl->MakeCurrent();
|
||||
|
||||
ScopedCopyTexImageSource maybeSwizzle(mContext, funcName, srcWidth, srcHeight,
|
||||
srcFormat, dstUsage);
|
||||
|
||||
uint32_t readX, readY;
|
||||
uint32_t writeX, writeY;
|
||||
uint32_t rwWidth, rwHeight;
|
||||
Intersect(srcWidth, x, width, &readX, &writeX, &rwWidth);
|
||||
Intersect(srcHeight, y, height, &readY, &writeY, &rwHeight);
|
||||
|
||||
if (!rwWidth || !rwHeight) {
|
||||
// There aren't any, so we're 'done'.
|
||||
mContext->DummyReadFramebufferOperation(funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
bool uploadWillInitialize;
|
||||
if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset,
|
||||
yOffset, zOffset, width, height, depth,
|
||||
@ -2143,21 +2163,11 @@ WebGLTexture::CopyTexSubImage(const char* funcName, TexImageTarget target, GLint
|
||||
return;
|
||||
}
|
||||
|
||||
GLenum error = DoCopyTexSubImage(mContext->gl, target, level, xOffset + writeX,
|
||||
yOffset + writeY, zOffset, readX, readY, rwWidth,
|
||||
rwHeight);
|
||||
|
||||
if (error == LOCAL_GL_OUT_OF_MEMORY) {
|
||||
mContext->ErrorOutOfMemory("%s: Ran out of memory during texture copy.",
|
||||
funcName);
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors.");
|
||||
mContext->GenerateWarning("%s: Unexpected error during texture copy. Context"
|
||||
" lost.",
|
||||
funcName);
|
||||
mContext->ForceLoseContext();
|
||||
const bool isSubImage = true;
|
||||
if (!DoCopyTexOrSubImage(mContext, funcName, isSubImage, this, target, level, x, y,
|
||||
srcTotalWidth, srcTotalHeight, srcUsage, xOffset, yOffset,
|
||||
zOffset, width, height, dstUsage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -251,25 +251,27 @@ NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&&
|
||||
NS_ASSERTION(nodeInfo->NamespaceEquals(kNameSpaceID_XHTML),
|
||||
"Trying to HTML elements that don't have the XHTML namespace");
|
||||
|
||||
int32_t tag = parserService->HTMLCaseSensitiveAtomTagToId(name);
|
||||
|
||||
// Per the Custom Element specification, unknown tags that are valid custom
|
||||
// element names should be HTMLElement instead of HTMLUnknownElement.
|
||||
int32_t tag = parserService->HTMLCaseSensitiveAtomTagToId(name);
|
||||
if ((tag == eHTMLTag_userdefined &&
|
||||
nsContentUtils::IsCustomElementName(name)) ||
|
||||
aIs) {
|
||||
bool isCustomElementName = (tag == eHTMLTag_userdefined &&
|
||||
nsContentUtils::IsCustomElementName(name));
|
||||
if (isCustomElementName) {
|
||||
NS_IF_ADDREF(*aResult = NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
|
||||
if (!*aResult) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsContentUtils::SetupCustomElement(*aResult, aIs);
|
||||
|
||||
return NS_OK;
|
||||
} else {
|
||||
*aResult = CreateHTMLElement(tag, nodeInfo.forget(), aFromParser).take();
|
||||
}
|
||||
|
||||
*aResult = CreateHTMLElement(tag,
|
||||
nodeInfo.forget(), aFromParser).take();
|
||||
return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
|
||||
if (!*aResult) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (isCustomElementName || aIs) {
|
||||
nsContentUtils::SetupCustomElement(*aResult, aIs);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<nsGenericHTMLElement>
|
||||
|
@ -101,6 +101,7 @@ function startTest() {
|
||||
is(extendedButton.tagName, "BUTTON", "Created element should have local name of BUTTON");
|
||||
is(extendedButton.__proto__, extendedProto, "Created element should have the prototype of the extended type.");
|
||||
is(extendedButton.getAttribute("is"), "x-extended-button", "The |is| attribute of the created element should be the extended type.");
|
||||
is(extendedButton.type, "submit", "Created element should be a button with type of \"submit\"");
|
||||
|
||||
// document.createElementNS with different namespace than definition.
|
||||
try {
|
||||
|
@ -58,7 +58,6 @@
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "mozilla/ServoStyleSet.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
@ -420,14 +419,6 @@ nsXBLBinding::GenerateAnonymousContent()
|
||||
if (mContent)
|
||||
mContent->UnsetAttr(namespaceID, name, false);
|
||||
}
|
||||
|
||||
// Now that we've finished shuffling the tree around, go ahead and restyle it
|
||||
// since frame construction is about to happen.
|
||||
nsIPresShell* presShell = mBoundElement->OwnerDoc()->GetShell();
|
||||
ServoStyleSet* servoSet = presShell->StyleSet()->GetAsServo();
|
||||
if (servoSet) {
|
||||
servoSet->StyleNewChildren(mBoundElement->AsElement());
|
||||
}
|
||||
}
|
||||
|
||||
nsIURI*
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ServoStyleSet.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
|
||||
@ -406,6 +407,24 @@ nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
|
||||
return false;
|
||||
}
|
||||
|
||||
// RAII class to invoke StyleNewChildren for Elements in Servo-backed documents
|
||||
// on destruction.
|
||||
class MOZ_STACK_CLASS AutoStyleNewChildren
|
||||
{
|
||||
public:
|
||||
explicit AutoStyleNewChildren(Element* aElement) : mElement(aElement) { MOZ_ASSERT(mElement); }
|
||||
~AutoStyleNewChildren()
|
||||
{
|
||||
nsIPresShell* presShell = mElement->OwnerDoc()->GetShell();
|
||||
ServoStyleSet* servoSet = presShell ? presShell->StyleSet()->GetAsServo() : nullptr;
|
||||
if (servoSet) {
|
||||
servoSet->StyleNewChildren(mElement);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Element* mElement;
|
||||
};
|
||||
|
||||
// This function loads a particular XBL file and installs all of the bindings
|
||||
// onto the element.
|
||||
@ -436,6 +455,15 @@ nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// There are various places in this function where we shuffle content around
|
||||
// the subtree and rebind things to and from insertion points. Once all that's
|
||||
// done, we want to invoke StyleNewChildren to style any unstyled children
|
||||
// that we may have after bindings have been removed and applied. This includes
|
||||
// anonymous content created in this function, explicit children for which we
|
||||
// defer styling until after XBL bindings are applied, and elements whose existing
|
||||
// style was invalidated by a call to SetXBLInsertionParent.
|
||||
AutoStyleNewChildren styleNewChildren(aContent->AsElement());
|
||||
|
||||
nsXBLBinding *binding = aContent->GetXBLBinding();
|
||||
if (binding) {
|
||||
if (binding->MarkedForDeath()) {
|
||||
|
@ -83,7 +83,7 @@
|
||||
|
||||
#define MAXSUGGESTION 15
|
||||
#define MAXSHARPS 5
|
||||
#define MAXWORDLEN 176
|
||||
#define MAXWORDLEN 100
|
||||
|
||||
#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
|
||||
# define H_DEPRECATED __attribute__((__deprecated__))
|
||||
|
24
extensions/spellcheck/hunspell/src/patches/1322666
Normal file
@ -0,0 +1,24 @@
|
||||
Bug 1322666 - Change MAXWORDLEN to 100
|
||||
|
||||
diff --git a/extensions/spellcheck/hunspell/src/hunspell.hxx b/extensions/spellcheck/hunspell/src/hunspell.hxx
|
||||
--- a/extensions/spellcheck/hunspell/src/hunspell.hxx
|
||||
+++ b/extensions/spellcheck/hunspell/src/hunspell.hxx
|
||||
@@ -78,17 +78,17 @@
|
||||
#include "atypes.hxx"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define SPELL_XML "<?xml?>"
|
||||
|
||||
#define MAXSUGGESTION 15
|
||||
#define MAXSHARPS 5
|
||||
-#define MAXWORDLEN 176
|
||||
+#define MAXWORDLEN 100
|
||||
|
||||
#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
|
||||
# define H_DEPRECATED __attribute__((__deprecated__))
|
||||
#elif defined(_MSC_VER) && (_MSC_VER >= 1300)
|
||||
# define H_DEPRECATED __declspec(deprecated)
|
||||
#else
|
||||
# define H_DEPRECATED
|
||||
#endif
|
@ -4464,13 +4464,6 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav)
|
||||
{
|
||||
TDZCheckCache tdzCache(this);
|
||||
return emitDestructuringLHS(target, flav);
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted)
|
||||
{
|
||||
@ -4741,7 +4734,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
||||
//
|
||||
// let x, y;
|
||||
// let a, b, c, d;
|
||||
// let tmp, done, iter, result; // stack values
|
||||
// let iter, result, done, value; // stack values
|
||||
//
|
||||
// iter = x[Symbol.iterator]();
|
||||
//
|
||||
@ -4749,115 +4742,113 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
||||
// result = iter.next();
|
||||
// done = result.done;
|
||||
//
|
||||
// if (done) {
|
||||
// a = undefined;
|
||||
// if (done)
|
||||
// value = undefined;
|
||||
// else
|
||||
// value = result.value;
|
||||
//
|
||||
// result = undefined;
|
||||
// done = true;
|
||||
// } else {
|
||||
// a = result.value;
|
||||
//
|
||||
// // Do next element's .next() and .done access here
|
||||
// result = iter.next();
|
||||
// done = result.done;
|
||||
// }
|
||||
// a = value;
|
||||
//
|
||||
// // ==== emitted by loop for b ====
|
||||
// if (done) {
|
||||
// b = undefined;
|
||||
//
|
||||
// result = undefined;
|
||||
// done = true;
|
||||
// value = undefined;
|
||||
// } else {
|
||||
// b = result.value;
|
||||
//
|
||||
// result = iter.next();
|
||||
// done = result.done;
|
||||
// if (done)
|
||||
// value = undefined;
|
||||
// else
|
||||
// value = result.value;
|
||||
// }
|
||||
//
|
||||
// b = value;
|
||||
//
|
||||
// // ==== emitted by loop for elision ====
|
||||
// if (done) {
|
||||
// result = undefined
|
||||
// done = true
|
||||
// value = undefined;
|
||||
// } else {
|
||||
// result.value;
|
||||
//
|
||||
// result = iter.next();
|
||||
// done = result.done;
|
||||
// if (done)
|
||||
// value = undefined;
|
||||
// else
|
||||
// value = result.value;
|
||||
// }
|
||||
//
|
||||
// // ==== emitted by loop for c ====
|
||||
// if (done) {
|
||||
// c = y;
|
||||
// value = undefined;
|
||||
// } else {
|
||||
// tmp = result.value;
|
||||
// if (tmp === undefined)
|
||||
// tmp = y;
|
||||
// c = tmp;
|
||||
//
|
||||
// // Don't do next element's .next() and .done access if
|
||||
// // this is the last non-spread element.
|
||||
// result = iter.next();
|
||||
// done = result.done;
|
||||
// if (done)
|
||||
// value = undefined;
|
||||
// else
|
||||
// value = result.value;
|
||||
// }
|
||||
//
|
||||
// if (value === undefined)
|
||||
// value = y;
|
||||
//
|
||||
// c = value;
|
||||
//
|
||||
// // ==== emitted by loop for d ====
|
||||
// if (done) {
|
||||
// // Assing empty array when completed
|
||||
// d = [];
|
||||
// } else {
|
||||
// d = [...iter];
|
||||
// }
|
||||
// if (done)
|
||||
// value = [];
|
||||
// else
|
||||
// value = [...iter];
|
||||
//
|
||||
// d = value;
|
||||
|
||||
/*
|
||||
* Use an iterator to destructure the RHS, instead of index lookup. We
|
||||
* must leave the *original* value on the stack.
|
||||
*/
|
||||
// Use an iterator to destructure the RHS, instead of index lookup. We
|
||||
// must leave the *original* value on the stack.
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ OBJ
|
||||
return false;
|
||||
if (!emitIterator()) // ... OBJ? ITER
|
||||
if (!emitIterator()) // ... OBJ ITER
|
||||
return false;
|
||||
bool needToPopIterator = true;
|
||||
|
||||
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
|
||||
bool isHead = member == pattern->pn_head;
|
||||
bool hasNext = !!member->pn_next;
|
||||
|
||||
if (member->isKind(PNK_SPREAD)) {
|
||||
IfThenElseEmitter ifThenElse(this);
|
||||
if (!isHead) {
|
||||
// If spread is not the first element of the pattern,
|
||||
// iterator can already be completed.
|
||||
if (!ifThenElse.emitIfElse()) // ... OBJ? ITER
|
||||
// ... OBJ ITER DONE
|
||||
if (!ifThenElse.emitIfElse()) // ... OBJ ITER
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_POP)) // ... OBJ?
|
||||
if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ARRAY
|
||||
return false;
|
||||
if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY
|
||||
return false;
|
||||
if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ?
|
||||
return false;
|
||||
|
||||
if (!ifThenElse.emitElse()) // ... OBJ? ITER
|
||||
if (!ifThenElse.emitElse()) // ... OBJ ITER
|
||||
return false;
|
||||
}
|
||||
|
||||
// If iterator is not completed, create a new array with the rest
|
||||
// of the iterator.
|
||||
if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ITER ARRAY
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER
|
||||
return false;
|
||||
if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX
|
||||
if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ITER ARRAY
|
||||
return false;
|
||||
if (!emitSpread()) // ... OBJ? ARRAY INDEX
|
||||
if (!emitNumberOp(0)) // ... OBJ ITER ITER ARRAY INDEX
|
||||
return false;
|
||||
if (!emit1(JSOP_POP)) // ... OBJ? ARRAY
|
||||
if (!emitSpread()) // ... OBJ ITER ARRAY INDEX
|
||||
return false;
|
||||
if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ?
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER ARRAY
|
||||
return false;
|
||||
|
||||
if (!isHead) {
|
||||
if (!ifThenElse.emitEnd())
|
||||
return false;
|
||||
MOZ_ASSERT(ifThenElse.popped() == 1);
|
||||
MOZ_ASSERT(ifThenElse.pushed() == 1);
|
||||
}
|
||||
needToPopIterator = false;
|
||||
MOZ_ASSERT(!member->pn_next);
|
||||
|
||||
if (!emitDestructuringLHS(member, flav)) // ... OBJ ITER
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(!hasNext);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -4869,110 +4860,100 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
||||
}
|
||||
|
||||
bool isElision = subpattern->isKind(PNK_ELISION);
|
||||
bool hasNextNonSpread = member->pn_next && !member->pn_next->isKind(PNK_SPREAD);
|
||||
bool hasNextSpread = member->pn_next && member->pn_next->isKind(PNK_SPREAD);
|
||||
|
||||
MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD));
|
||||
|
||||
auto emitNext = [pattern](ExclusiveContext* cx, BytecodeEmitter* bce) {
|
||||
if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER ITER
|
||||
return false;
|
||||
if (!bce->emitIteratorNext(pattern)) // ... OBJ? ITER RESULT
|
||||
return false;
|
||||
if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT
|
||||
return false;
|
||||
if (!bce->emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE?
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (isHead) {
|
||||
if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE?
|
||||
return false;
|
||||
}
|
||||
|
||||
IfThenElseEmitter ifThenElse(this);
|
||||
if (!ifThenElse.emitIfElse()) // ... OBJ? ITER RESULT
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_POP)) // ... OBJ? ITER
|
||||
return false;
|
||||
if (pndefault) {
|
||||
// Emit only pndefault tree here, as undefined check in emitDefault
|
||||
// should always be true.
|
||||
if (!emitInitializerInBranch(pndefault, subpattern)) // ... OBJ? ITER VALUE
|
||||
return false;
|
||||
} else {
|
||||
if (!isElision) {
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER UNDEFINED
|
||||
IfThenElseEmitter ifAlreadyDone(this);
|
||||
if (!isHead) {
|
||||
// If this element is not the first element of the pattern,
|
||||
// iterator can already be completed.
|
||||
// ... OBJ ITER DONE
|
||||
if (hasNext) {
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE DONE
|
||||
return false;
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING))
|
||||
}
|
||||
if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF
|
||||
return false;
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF
|
||||
return false;
|
||||
|
||||
if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE
|
||||
return false;
|
||||
|
||||
if (hasNext) {
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!isElision) {
|
||||
if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER
|
||||
return false;
|
||||
} else if (pndefault) {
|
||||
if (!emit1(JSOP_POP)) // ... OBJ? ITER
|
||||
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER ITER
|
||||
return false;
|
||||
if (!emitIteratorNext(pattern)) // ... OBJ ITER RESULT
|
||||
return false;
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT RESULT
|
||||
return false;
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER RESULT DONE
|
||||
return false;
|
||||
|
||||
if (hasNext) {
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT DONE DONE
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup next element's result when the iterator is done.
|
||||
if (hasNextNonSpread) {
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER RESULT
|
||||
return false;
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING))
|
||||
return false;
|
||||
if (!emit1(JSOP_TRUE)) // ... OBJ? ITER RESULT DONE?
|
||||
return false;
|
||||
} else if (hasNextSpread) {
|
||||
if (!emit1(JSOP_TRUE)) // ... OBJ? ITER DONE?
|
||||
IfThenElseEmitter ifDone(this);
|
||||
if (!ifDone.emitIfElse()) // ... OBJ ITER RESULT ?DONE
|
||||
return false;
|
||||
|
||||
if (hasNext) {
|
||||
if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ifThenElse.emitElse()) // ... OBJ? ITER RESULT
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE
|
||||
return false;
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF
|
||||
return false;
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF
|
||||
return false;
|
||||
|
||||
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE
|
||||
if (!ifDone.emitElse()) // ... OBJ ITER RESULT ?DONE
|
||||
return false;
|
||||
|
||||
if (hasNext) {
|
||||
if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT
|
||||
return false;
|
||||
}
|
||||
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE VALUE
|
||||
return false;
|
||||
|
||||
if (!ifDone.emitEnd())
|
||||
return false;
|
||||
MOZ_ASSERT(ifDone.pushed() == 0);
|
||||
|
||||
if (!isHead) {
|
||||
if (!ifAlreadyDone.emitEnd())
|
||||
return false;
|
||||
MOZ_ASSERT(ifAlreadyDone.pushed() == 1);
|
||||
}
|
||||
|
||||
if (pndefault) {
|
||||
if (!emitDefault(pndefault, subpattern)) // ... OBJ? ITER VALUE
|
||||
if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE VALUE
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isElision) {
|
||||
if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER
|
||||
if (!emitDestructuringLHS(subpattern, flav)) // ... OBJ ITER ?DONE
|
||||
return false;
|
||||
} else {
|
||||
if (!emit1(JSOP_POP)) // ... OBJ? ITER
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup next element's result when the iterator is not done.
|
||||
if (hasNextNonSpread) {
|
||||
if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE?
|
||||
return false;
|
||||
} else if (hasNextSpread) {
|
||||
if (!emit1(JSOP_FALSE)) // ... OBJ? ITER DONE?
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ifThenElse.emitEnd())
|
||||
return false;
|
||||
if (hasNextNonSpread)
|
||||
MOZ_ASSERT(ifThenElse.pushed() == 1);
|
||||
else if (hasNextSpread)
|
||||
MOZ_ASSERT(ifThenElse.pushed() == 0);
|
||||
else
|
||||
MOZ_ASSERT(ifThenElse.popped() == 1);
|
||||
}
|
||||
|
||||
if (needToPopIterator) {
|
||||
if (!emit1(JSOP_POP)) // ... OBJ?
|
||||
return false;
|
||||
}
|
||||
if (!emit1(JSOP_POP)) // ... OBJ
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -6120,7 +6101,7 @@ BytecodeEmitter::emitSpread(bool allowSelfHosted)
|
||||
return false;
|
||||
if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT
|
||||
return false;
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE?
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE
|
||||
return false;
|
||||
|
||||
if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER ARR I RESULT
|
||||
@ -6309,7 +6290,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte
|
||||
return false;
|
||||
if (!emit1(JSOP_DUP)) // ITER RESULT RESULT
|
||||
return false;
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE?
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE
|
||||
return false;
|
||||
|
||||
if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget))
|
||||
@ -6805,7 +6786,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn)
|
||||
return false;
|
||||
if (!emit1(JSOP_DUP)) // ITER RESULT RESULT
|
||||
return false;
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE?
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE
|
||||
return false;
|
||||
|
||||
JumpList beq;
|
||||
@ -6977,14 +6958,13 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
|
||||
RootedFunction fun(cx, funbox->function());
|
||||
RootedAtom name(cx, fun->explicitName());
|
||||
MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
|
||||
MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto);
|
||||
|
||||
/*
|
||||
* Set the |wasEmitted| flag in the funbox once the function has been
|
||||
* emitted. Function definitions that need hoisting to the top of the
|
||||
* function will be seen by emitFunction in two places.
|
||||
*/
|
||||
if (funbox->wasEmitted && pn->functionIsHoisted()) {
|
||||
if (funbox->wasEmitted) {
|
||||
// Annex B block-scoped functions are hoisted like any other
|
||||
// block-scoped function to the top of their scope. When their
|
||||
// definitions are seen for the second time, we need to emit the
|
||||
@ -7113,7 +7093,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
|
||||
}
|
||||
|
||||
if (needsProto) {
|
||||
MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA);
|
||||
MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA);
|
||||
pn->setOp(JSOP_FUNWITHPROTO);
|
||||
}
|
||||
|
||||
@ -10049,15 +10029,6 @@ CGConstList::finish(ConstArray* array)
|
||||
array->vector[i] = list[i];
|
||||
}
|
||||
|
||||
bool
|
||||
CGObjectList::isAdded(ObjectBox* objbox)
|
||||
{
|
||||
// An objbox added to CGObjectList as non-first element has non-null
|
||||
// emitLink member. The first element has null emitLink.
|
||||
// Check for firstbox to cover the first element.
|
||||
return objbox->emitLink || objbox == firstbox;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the index of the given object for code generator.
|
||||
*
|
||||
@ -10069,15 +10040,9 @@ CGObjectList::isAdded(ObjectBox* objbox)
|
||||
unsigned
|
||||
CGObjectList::add(ObjectBox* objbox)
|
||||
{
|
||||
if (isAdded(objbox))
|
||||
return indexOf(objbox->object);
|
||||
|
||||
MOZ_ASSERT(!objbox->emitLink);
|
||||
objbox->emitLink = lastbox;
|
||||
lastbox = objbox;
|
||||
|
||||
// See the comment in CGObjectList::isAdded.
|
||||
if (!firstbox)
|
||||
firstbox = objbox;
|
||||
return length++;
|
||||
}
|
||||
|
||||
@ -10104,12 +10069,7 @@ CGObjectList::finish(ObjectArray* array)
|
||||
MOZ_ASSERT(!*cursor);
|
||||
MOZ_ASSERT(objbox->object->isTenured());
|
||||
*cursor = objbox->object;
|
||||
|
||||
ObjectBox* tmp = objbox->emitLink;
|
||||
// Clear emitLink for CGObjectList::isAdded.
|
||||
objbox->emitLink = nullptr;
|
||||
objbox = tmp;
|
||||
} while (objbox != nullptr);
|
||||
} while ((objbox = objbox->emitLink) != nullptr);
|
||||
MOZ_ASSERT(cursor == array->vector);
|
||||
}
|
||||
|
||||
|
@ -43,12 +43,10 @@ class CGConstList {
|
||||
|
||||
struct CGObjectList {
|
||||
uint32_t length; /* number of emitted so far objects */
|
||||
ObjectBox* firstbox; /* first emitted object */
|
||||
ObjectBox* lastbox; /* last emitted object */
|
||||
|
||||
CGObjectList() : length(0), firstbox(nullptr), lastbox(nullptr) {}
|
||||
CGObjectList() : length(0), lastbox(nullptr) {}
|
||||
|
||||
bool isAdded(ObjectBox* objbox);
|
||||
unsigned add(ObjectBox* objbox);
|
||||
unsigned indexOf(JSObject* obj);
|
||||
void finish(ObjectArray* array);
|
||||
@ -648,7 +646,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
||||
// the stack and emits code to destructure a single lhs expression (either a
|
||||
// name or a compound []/{} expression).
|
||||
MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav);
|
||||
MOZ_MUST_USE bool emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav);
|
||||
|
||||
// emitDestructuringOps assumes the to-be-destructured value has been
|
||||
// pushed on the stack and emits code to destructure each part of a [] or
|
||||
|
@ -649,14 +649,12 @@ class ParseNode
|
||||
MOZ_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION);
|
||||
MOZ_ASSERT(isOp(JSOP_LAMBDA) || // lambda, genexpr
|
||||
isOp(JSOP_LAMBDA_ARROW) || // arrow function
|
||||
isOp(JSOP_FUNWITHPROTO) || // already emitted lambda with needsProto
|
||||
isOp(JSOP_DEFFUN) || // non-body-level function statement
|
||||
isOp(JSOP_NOP) || // body-level function stmt in global code
|
||||
isOp(JSOP_GETLOCAL) || // body-level function stmt in function code
|
||||
isOp(JSOP_GETARG) || // body-level function redeclaring formal
|
||||
isOp(JSOP_INITLEXICAL)); // block-level function stmt
|
||||
return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) &&
|
||||
!isOp(JSOP_FUNWITHPROTO) && !isOp(JSOP_DEFFUN);
|
||||
return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1917,43 +1917,63 @@ CacheIRWriter::copyStubData(uint8_t* dest) const
|
||||
{
|
||||
uintptr_t* destWords = reinterpret_cast<uintptr_t*>(dest);
|
||||
|
||||
for (size_t i = 0; i < stubFields_.length(); i++) {
|
||||
StubField::Type type = stubFields_[i].type();
|
||||
switch (type) {
|
||||
for (const StubField& field : stubFields_) {
|
||||
switch (field.type()) {
|
||||
case StubField::Type::RawWord:
|
||||
*destWords = stubFields_[i].asWord();
|
||||
*destWords = field.asWord();
|
||||
break;
|
||||
case StubField::Type::Shape:
|
||||
InitGCPtr<Shape*>(destWords, stubFields_[i].asWord());
|
||||
InitGCPtr<Shape*>(destWords, field.asWord());
|
||||
break;
|
||||
case StubField::Type::JSObject:
|
||||
InitGCPtr<JSObject*>(destWords, stubFields_[i].asWord());
|
||||
InitGCPtr<JSObject*>(destWords, field.asWord());
|
||||
break;
|
||||
case StubField::Type::ObjectGroup:
|
||||
InitGCPtr<ObjectGroup*>(destWords, stubFields_[i].asWord());
|
||||
InitGCPtr<ObjectGroup*>(destWords, field.asWord());
|
||||
break;
|
||||
case StubField::Type::Symbol:
|
||||
InitGCPtr<JS::Symbol*>(destWords, stubFields_[i].asWord());
|
||||
InitGCPtr<JS::Symbol*>(destWords, field.asWord());
|
||||
break;
|
||||
case StubField::Type::String:
|
||||
InitGCPtr<JSString*>(destWords, stubFields_[i].asWord());
|
||||
InitGCPtr<JSString*>(destWords, field.asWord());
|
||||
break;
|
||||
case StubField::Type::Id:
|
||||
InitGCPtr<jsid>(destWords, stubFields_[i].asWord());
|
||||
InitGCPtr<jsid>(destWords, field.asWord());
|
||||
break;
|
||||
case StubField::Type::RawInt64:
|
||||
*reinterpret_cast<uint64_t*>(destWords) = stubFields_[i].asInt64();
|
||||
*reinterpret_cast<uint64_t*>(destWords) = field.asInt64();
|
||||
break;
|
||||
case StubField::Type::Value:
|
||||
InitGCPtr<JS::Value>(destWords, stubFields_[i].asInt64());
|
||||
InitGCPtr<JS::Value>(destWords, field.asInt64());
|
||||
break;
|
||||
case StubField::Type::Limit:
|
||||
MOZ_CRASH("Invalid type");
|
||||
}
|
||||
destWords += StubField::sizeInBytes(type) / sizeof(uintptr_t);
|
||||
destWords += StubField::sizeInBytes(field.type()) / sizeof(uintptr_t);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIRWriter::stubDataEquals(const uint8_t* stubData) const
|
||||
{
|
||||
const uintptr_t* stubDataWords = reinterpret_cast<const uintptr_t*>(stubData);
|
||||
|
||||
for (const StubField& field : stubFields_) {
|
||||
if (field.sizeIsWord()) {
|
||||
if (field.asWord() != *stubDataWords)
|
||||
return false;
|
||||
stubDataWords++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.asInt64() != *reinterpret_cast<const uint64_t*>(stubDataWords))
|
||||
return false;
|
||||
stubDataWords += sizeof(uint64_t) / sizeof(uintptr_t);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HashNumber
|
||||
CacheIRStubKey::hash(const CacheIRStubKey::Lookup& l)
|
||||
{
|
||||
@ -2065,28 +2085,46 @@ jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We got our shared stub code and stub info. Time to allocate and attach a
|
||||
// new stub.
|
||||
|
||||
MOZ_ASSERT(code);
|
||||
MOZ_ASSERT(stubInfo);
|
||||
MOZ_ASSERT(stub->isMonitoredFallback());
|
||||
MOZ_ASSERT(stubInfo->stubDataSize() == writer.stubDataSize());
|
||||
|
||||
// Ensure we don't attach duplicate stubs. This can happen if a stub failed
|
||||
// for some reason and the IR generator doesn't check for exactly the same
|
||||
// conditions.
|
||||
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
|
||||
if (!iter->isCacheIR_Monitored())
|
||||
continue;
|
||||
|
||||
ICCacheIR_Monitored* otherStub = iter->toCacheIR_Monitored();
|
||||
if (otherStub->stubInfo() != stubInfo)
|
||||
continue;
|
||||
if (!writer.stubDataEquals(otherStub->stubDataStart()))
|
||||
continue;
|
||||
|
||||
// We found a stub that's exactly the same as the stub we're about to
|
||||
// attach. Just return nullptr, the caller should do nothing in this
|
||||
// case.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Time to allocate and attach a new stub.
|
||||
|
||||
size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
|
||||
|
||||
ICStubSpace* stubSpace = ICStubCompiler::StubSpaceForStub(stubInfo->makesGCCalls(),
|
||||
outerScript, engine);
|
||||
void* newStub = stubSpace->alloc(bytesNeeded);
|
||||
if (!newStub)
|
||||
void* newStubMem = stubSpace->alloc(bytesNeeded);
|
||||
if (!newStubMem)
|
||||
return nullptr;
|
||||
|
||||
ICStub* monitorStub = stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub();
|
||||
new(newStub) ICCacheIR_Monitored(code, monitorStub, stubInfo);
|
||||
auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo);
|
||||
|
||||
writer.copyStubData((uint8_t*)newStub + stubInfo->stubDataOffset());
|
||||
stub->addNewStub((ICStub*)newStub);
|
||||
return (ICStub*)newStub;
|
||||
writer.copyStubData(newStub->stubDataStart());
|
||||
stub->addNewStub(newStub);
|
||||
return newStub;
|
||||
}
|
||||
|
||||
void
|
||||
@ -2196,6 +2234,11 @@ CacheIRStubInfo::copyStubData(ICStub* src, ICStub* dest) const
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
ICCacheIR_Monitored::stubDataStart()
|
||||
{
|
||||
return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
|
||||
}
|
||||
|
||||
/* static */ ICCacheIR_Monitored*
|
||||
ICCacheIR_Monitored::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
|
||||
|
@ -897,7 +897,7 @@ GetPropIRGenerator::tryAttachDenseElement(HandleObject obj, ObjOperandId objId,
|
||||
if (!obj->isNative())
|
||||
return false;
|
||||
|
||||
if (uint32_t(idVal_.toInt32()) >= obj->as<NativeObject>().getDenseInitializedLength())
|
||||
if (!obj->as<NativeObject>().containsDenseElement(uint32_t(idVal_.toInt32())))
|
||||
return false;
|
||||
|
||||
writer.guardShape(objId, obj->as<NativeObject>().lastProperty());
|
||||
|
@ -229,9 +229,6 @@ class StubField
|
||||
};
|
||||
Type type_;
|
||||
|
||||
bool sizeIsWord() const { return sizeIsWord(type_); }
|
||||
bool sizeIsInt64() const { return sizeIsInt64(type_); }
|
||||
|
||||
public:
|
||||
StubField(uint64_t data, Type type)
|
||||
: dataInt64_(data), type_(type)
|
||||
@ -241,6 +238,9 @@ class StubField
|
||||
|
||||
Type type() const { return type_; }
|
||||
|
||||
bool sizeIsWord() const { return sizeIsWord(type_); }
|
||||
bool sizeIsInt64() const { return sizeIsInt64(type_); }
|
||||
|
||||
uintptr_t asWord() const { MOZ_ASSERT(sizeIsWord()); return dataWord_; }
|
||||
uint64_t asInt64() const { MOZ_ASSERT(sizeIsInt64()); return dataInt64_; }
|
||||
} JS_HAZ_GC_POINTER;
|
||||
@ -357,6 +357,7 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||
return stubDataSize_;
|
||||
}
|
||||
void copyStubData(uint8_t* dest) const;
|
||||
bool stubDataEquals(const uint8_t* stubData) const;
|
||||
|
||||
bool operandIsDead(uint32_t operandId, uint32_t currentInstruction) const {
|
||||
if (operandId >= operandLastUsed_.length())
|
||||
|
@ -2273,6 +2273,8 @@ IonCompile(JSContext* cx, JSScript* script,
|
||||
return reason;
|
||||
}
|
||||
|
||||
AssertBasicGraphCoherency(builder->graph());
|
||||
|
||||
// If possible, compile the script off thread.
|
||||
if (options.offThreadCompilationAvailable()) {
|
||||
JitSpew(JitSpew_IonSyncLogs, "Can't log script %s:%" PRIuSIZE
|
||||
|
@ -192,9 +192,11 @@ MIRGraph::removeSuccessorBlocks(MBasicBlock* start)
|
||||
removeBlock(block);
|
||||
} else {
|
||||
MOZ_ASSERT(block != osrBlock());
|
||||
for (size_t j = 0; j < block->numPredecessors(); j++) {
|
||||
if (!block->getPredecessor(j)->isMarked())
|
||||
for (size_t j = 0; j < block->numPredecessors(); ) {
|
||||
if (!block->getPredecessor(j)->isMarked()) {
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
block->removePredecessor(block->getPredecessor(j));
|
||||
}
|
||||
// This shouldn't have any instructions yet.
|
||||
|
@ -887,6 +887,8 @@ class ICCacheIR_Monitored : public ICMonitoredStub
|
||||
const CacheIRStubInfo* stubInfo() const {
|
||||
return stubInfo_;
|
||||
}
|
||||
|
||||
uint8_t* stubDataStart();
|
||||
};
|
||||
|
||||
// Updated stubs are IC stubs that use a TypeUpdate IC to track
|
||||
|
25
js/src/tests/ecma_6/Destructuring/array-default-class.js
Normal file
@ -0,0 +1,25 @@
|
||||
var BUGNUMBER = 1322314;
|
||||
var summary = "Function in computed property in class expression in array destructuring default";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
function* g([
|
||||
a = class E {
|
||||
[ (function() { return "foo"; })() ]() {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
]) {
|
||||
yield a;
|
||||
}
|
||||
|
||||
let C = [...g([])][0];
|
||||
let x = new C();
|
||||
assertEq(x.foo(), 10);
|
||||
|
||||
C = [...g([undefined])][0];
|
||||
x = new C();
|
||||
assertEq(x.foo(), 10);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
@ -4,7 +4,7 @@
|
||||
<div style="width: 1000px; height: 1000px"></div>
|
||||
<script type="application/javascript;version=1.8">
|
||||
function grabEventAndGo(event) {
|
||||
gen.send(event);
|
||||
gen.next(event);
|
||||
}
|
||||
|
||||
function waitAsync() {
|
||||
@ -17,7 +17,7 @@ function postPos() {
|
||||
"*");
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
function* runTest() {
|
||||
var inner = document.getElementById("inner");
|
||||
window.onload = grabEventAndGo;
|
||||
// Wait for onLoad event.
|
||||
@ -55,9 +55,6 @@ function runTest() {
|
||||
postPos();
|
||||
|
||||
parent.postMessage("done", "*");
|
||||
// Let parent process sent messages.
|
||||
// "End" generator.
|
||||
yield;
|
||||
}
|
||||
|
||||
var gen = runTest();
|
||||
|
@ -18,10 +18,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=583889
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function grabEventAndGo(event) {
|
||||
gen.send(event);
|
||||
gen.next(event);
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
function* runTest() {
|
||||
window.onload = grabEventAndGo;
|
||||
// Wait for onLoad event.
|
||||
yield;
|
||||
@ -43,8 +43,6 @@ function runTest() {
|
||||
|
||||
// finish(), yet let the test actually end first, to be safe.
|
||||
SimpleTest.executeSoon(SimpleTest.finish);
|
||||
// "End" generator.
|
||||
yield;
|
||||
}
|
||||
|
||||
var gen = runTest();
|
||||
|
@ -16,14 +16,13 @@ function olo()
|
||||
|
||||
function neext()
|
||||
{
|
||||
try {
|
||||
iter.next();
|
||||
} catch (e if e instanceof StopIteration) {
|
||||
let {done} = iter.next();
|
||||
if (done) {
|
||||
clearInterval(interv);
|
||||
}
|
||||
}
|
||||
|
||||
function foo()
|
||||
function* foo()
|
||||
{
|
||||
var docElem = document.documentElement;
|
||||
var head = document.getElementById("head");
|
||||
|
@ -83,7 +83,7 @@ function waitInterrupt(result, gen) {
|
||||
|
||||
function testPhoom(isCapturing, x, y, expectEvent) {
|
||||
|
||||
var eventGen = (function() {
|
||||
var eventGen = (function*() {
|
||||
var innerdoc = document.getElementById('testframe').contentDocument;
|
||||
|
||||
for (var doc of [document, innerdoc]) {
|
||||
@ -163,8 +163,6 @@ function testPhoom(isCapturing, x, y, expectEvent) {
|
||||
}
|
||||
|
||||
setTimeout(nextTest, 0);
|
||||
|
||||
yield;
|
||||
})();
|
||||
|
||||
setTimeout(function() { eventGen.next(); }, 0);
|
||||
|
@ -29,7 +29,7 @@
|
||||
// should be fired when a page is restored from the bfcache as though it had
|
||||
// reloaded.
|
||||
//
|
||||
function testIterator() {
|
||||
function* testIterator() {
|
||||
// Make sure bfcache is on.
|
||||
enableBFCache(true);
|
||||
|
||||
@ -69,10 +69,7 @@
|
||||
} );
|
||||
yield;
|
||||
|
||||
// Tell the framework the test is finished. Include the final 'yield'
|
||||
// statement to prevent a StopIteration exception from being thrown.
|
||||
finish();
|
||||
yield;
|
||||
}
|
||||
|
||||
]]></script>
|
||||
|
@ -554,6 +554,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
// This is not the root element or we are calculating something other
|
||||
// than font size, so rem is relative to the root element's font size.
|
||||
// Find the root style context by walking up the style context tree.
|
||||
// NOTE: We should not call ResolveStyleFor() against the root element
|
||||
// to obtain the root style here because it may lead to reentrant call
|
||||
// of nsStyleSet::GetContext().
|
||||
nsStyleContext* rootStyle = aStyleContext;
|
||||
while (rootStyle->GetParent()) {
|
||||
rootStyle = rootStyle->GetParent();
|
||||
|
@ -506,7 +506,7 @@ strlcat(dst, src, siz)
|
||||
|
||||
#endif /* LINUX or WIN32 */
|
||||
|
||||
#if defined(USE_OWN_INET_NTOP) || defined(WIN32)
|
||||
#if defined(USE_OWN_INET_NTOP) || (defined(WIN32) && WINVER < 0x0600)
|
||||
#include <errno.h>
|
||||
#ifdef WIN32
|
||||
#include <Ws2ipdef.h>
|
||||
@ -774,19 +774,5 @@ int gettimeofday(struct timeval *tv, void *tz)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if _MSC_VER < 1900
|
||||
int snprintf(char *buffer, size_t n, const char *format, ...)
|
||||
{
|
||||
va_list argp;
|
||||
int ret;
|
||||
va_start(argp, format);
|
||||
ret = _vscprintf(format, argp);
|
||||
vsnprintf_s(buffer, n, _TRUNCATE, format, argp);
|
||||
va_end(argp);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -64,8 +64,7 @@ int nr_write_pid_file(char *pid_filename);
|
||||
int nr_reg_uint4_fetch_and_check(NR_registry key, UINT4 min, UINT4 max, int log_fac, int die, UINT4 *val);
|
||||
int nr_reg_uint8_fetch_and_check(NR_registry key, UINT8 min, UINT8 max, int log_fac, int die, UINT8 *val);
|
||||
|
||||
#ifdef WIN32
|
||||
int snprintf(char *buffer, size_t n, const char *format, ...);
|
||||
#if defined(WIN32) && WINVER < 0x0600
|
||||
const char *inet_ntop(int af, const void *src, char *dst, size_t size);
|
||||
int inet_pton(int af, const char *src, void *dst);
|
||||
#endif
|
||||
|
@ -6672,7 +6672,7 @@ var Distribution = {
|
||||
// aFile is an nsIFile
|
||||
// aCallback takes the parsed JSON object as a parameter
|
||||
readJSON: function dc_readJSON(aFile, aCallback) {
|
||||
Task.spawn(function() {
|
||||
Task.spawn(function*() {
|
||||
let bytes = yield OS.File.read(aFile.path);
|
||||
let raw = new TextDecoder().decode(bytes) || "";
|
||||
|
||||
|
@ -257,7 +257,7 @@ function removeSnippet(messageId, snippetId) {
|
||||
function writeStat(snippetId, timestamp) {
|
||||
let data = gEncoder.encode(snippetId + "," + timestamp + ";");
|
||||
|
||||
Task.spawn(function() {
|
||||
Task.spawn(function*() {
|
||||
try {
|
||||
let file = yield OS.File.open(gStatsPath, { append: true, write: true });
|
||||
try {
|
||||
|
@ -213,7 +213,7 @@ var gDatabaseEnsured = false;
|
||||
* Creates the database schema.
|
||||
*/
|
||||
function createDatabase(db) {
|
||||
return Task.spawn(function create_database_task() {
|
||||
return Task.spawn(function* create_database_task() {
|
||||
yield db.execute(SQL.createItemsTable);
|
||||
});
|
||||
}
|
||||
@ -222,7 +222,7 @@ function createDatabase(db) {
|
||||
* Migrates the database schema to a new version.
|
||||
*/
|
||||
function upgradeDatabase(db, oldVersion, newVersion) {
|
||||
return Task.spawn(function upgrade_database_task() {
|
||||
return Task.spawn(function* upgrade_database_task() {
|
||||
switch (oldVersion) {
|
||||
case 1:
|
||||
// Migration from v1 to latest:
|
||||
@ -251,10 +251,10 @@ function upgradeDatabase(db, oldVersion, newVersion) {
|
||||
* @resolves Handle on an opened SQLite database.
|
||||
*/
|
||||
function getDatabaseConnection() {
|
||||
return Task.spawn(function get_database_connection_task() {
|
||||
return Task.spawn(function* get_database_connection_task() {
|
||||
let db = yield Sqlite.openConnection({ path: DB_PATH });
|
||||
if (gDatabaseEnsured) {
|
||||
throw new Task.Result(db);
|
||||
return db;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -277,7 +277,7 @@ function getDatabaseConnection() {
|
||||
}
|
||||
|
||||
gDatabaseEnsured = true;
|
||||
throw new Task.Result(db);
|
||||
return db;
|
||||
});
|
||||
}
|
||||
|
||||
@ -350,10 +350,10 @@ HomeStorage.prototype = {
|
||||
": you cannot save more than " + MAX_SAVE_COUNT + " items at once";
|
||||
}
|
||||
|
||||
return Task.spawn(function save_task() {
|
||||
return Task.spawn(function* save_task() {
|
||||
let db = yield getDatabaseConnection();
|
||||
try {
|
||||
yield db.executeTransaction(function save_transaction() {
|
||||
yield db.executeTransaction(function* save_transaction() {
|
||||
if (options && options.replace) {
|
||||
yield db.executeCached(SQL.deleteFromDataset, { dataset_id: this.datasetId });
|
||||
}
|
||||
@ -392,7 +392,7 @@ HomeStorage.prototype = {
|
||||
* @resolves When the operation has completed.
|
||||
*/
|
||||
deleteAll: function() {
|
||||
return Task.spawn(function delete_all_task() {
|
||||
return Task.spawn(function* delete_all_task() {
|
||||
let db = yield getDatabaseConnection();
|
||||
try {
|
||||
let params = { dataset_id: this.datasetId };
|
||||
|
@ -92,7 +92,7 @@ add_task(function* test_formdata() {
|
||||
// Creates a tab, loads a page with some form fields,
|
||||
// modifies their values and closes the tab.
|
||||
function createAndRemoveTab() {
|
||||
return Task.spawn(function () {
|
||||
return Task.spawn(function* () {
|
||||
// Create a new tab.
|
||||
let tab = gBrowserApp.addTab(URL);
|
||||
let browser = tab.browser;
|
||||
@ -147,7 +147,7 @@ add_task(function* test_formdata2() {
|
||||
// Creates a tab, loads a page with some form fields,
|
||||
// modifies their values and closes the tab.
|
||||
function createAndRemoveTab() {
|
||||
return Task.spawn(function () {
|
||||
return Task.spawn(function* () {
|
||||
// Create a new tab.
|
||||
let tab = gBrowserApp.addTab(URL);
|
||||
let browser = tab.browser;
|
||||
@ -207,7 +207,7 @@ add_task(function* test_formdata_navigation() {
|
||||
// Creates a tab, loads a page with some form fields, modifies their values,
|
||||
// navigates to a different page and back again and closes the tab.
|
||||
function createNavigateAndRemoveTab() {
|
||||
return Task.spawn(function () {
|
||||
return Task.spawn(function* () {
|
||||
// Create a new tab.
|
||||
let tab = gBrowserApp.addTab(URL);
|
||||
let browser = tab.browser;
|
||||
|
@ -65,7 +65,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981
|
||||
|
||||
// Creates a tab, sets a scroll position and closes the tab.
|
||||
function createAndRemoveTab() {
|
||||
return Task.spawn(function () {
|
||||
return Task.spawn(function* () {
|
||||
// Create a new tab.
|
||||
tabScroll = BrowserApp.addTab(URL);
|
||||
let browser = tabScroll.browser;
|
||||
@ -120,7 +120,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981
|
||||
|
||||
// Creates a tab, sets a scroll position and closes the tab.
|
||||
function createAndRemoveReaderTab() {
|
||||
return Task.spawn(function () {
|
||||
return Task.spawn(function* () {
|
||||
// Create a new tab.
|
||||
tabScroll = BrowserApp.addTab(URL_reader);
|
||||
let browser = tabScroll.browser;
|
||||
@ -176,7 +176,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981
|
||||
|
||||
// Creates a tab, sets a scroll position and zoom level and closes the tab.
|
||||
function createAndRemoveTab() {
|
||||
return Task.spawn(function () {
|
||||
return Task.spawn(function* () {
|
||||
// Create a new tab.
|
||||
tabScroll = BrowserApp.addTab(URL);
|
||||
let browser = tabScroll.browser;
|
||||
@ -238,7 +238,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981
|
||||
|
||||
// Creates a tab, sets a scroll position and zoom level and closes the tab.
|
||||
function createAndRemoveTab() {
|
||||
return Task.spawn(function () {
|
||||
return Task.spawn(function* () {
|
||||
// Create a new tab.
|
||||
tabScroll = BrowserApp.addTab(URL_desktop);
|
||||
let browser = tabScroll.browser;
|
||||
|
@ -685,7 +685,7 @@ var _Task;
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* add_task(function test() {
|
||||
* add_task(function* test() {
|
||||
* let result = yield Promise.resolve(true);
|
||||
*
|
||||
* do_check_true(result);
|
||||
@ -694,7 +694,7 @@ var _Task;
|
||||
* do_check_eq(secondary, "expected value");
|
||||
* });
|
||||
*
|
||||
* add_task(function test_early_return() {
|
||||
* add_task(function* test_early_return() {
|
||||
* let result = yield somethingThatReturnsAPromise();
|
||||
*
|
||||
* if (!result) {
|
||||
|
@ -882,7 +882,7 @@ GeckoDriver.prototype.executeScript = function*(cmd, resp) {
|
||||
* @throws JavaScriptError
|
||||
* If an Error was thrown whilst evaluating the script.
|
||||
*/
|
||||
GeckoDriver.prototype.executeAsyncScript = function (cmd, resp) {
|
||||
GeckoDriver.prototype.executeAsyncScript = function* (cmd, resp) {
|
||||
let {script, args, scriptTimeout} = cmd.parameters;
|
||||
scriptTimeout = scriptTimeout || this.scriptTimeout;
|
||||
|
||||
@ -932,7 +932,7 @@ GeckoDriver.prototype.execute_ = function (script, args, timeout, opts = {}) {
|
||||
*
|
||||
* Scripts are expected to call the {@code finish} global when done.
|
||||
*/
|
||||
GeckoDriver.prototype.executeJSScript = function (cmd, resp) {
|
||||
GeckoDriver.prototype.executeJSScript = function* (cmd, resp) {
|
||||
let {script, args, scriptTimeout} = cmd.parameters;
|
||||
scriptTimeout = scriptTimeout || this.scriptTimeout;
|
||||
|
||||
|
@ -371,7 +371,7 @@ function serverDebug(metadata, response)
|
||||
* Creates a generator that iterates over the contents of
|
||||
* an nsIFile directory.
|
||||
*/
|
||||
function dirIter(dir)
|
||||
function* dirIter(dir)
|
||||
{
|
||||
var en = dir.directoryEntries;
|
||||
while (en.hasMoreElements()) {
|
||||
|
@ -15,12 +15,12 @@ function executeWithTimeout() {
|
||||
);
|
||||
}
|
||||
|
||||
add_task(function asyncTest_no1() {
|
||||
add_task(function* asyncTest_no1() {
|
||||
yield executeWithTimeout();
|
||||
test1Complete = true;
|
||||
});
|
||||
|
||||
add_task(function asyncTest_no2() {
|
||||
add_task(function* asyncTest_no2() {
|
||||
yield executeWithTimeout();
|
||||
test2Complete = true;
|
||||
});
|
||||
|
@ -3,10 +3,20 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* This file defines specific rules for print preview when using simplify mode.
|
||||
* These rules already exist on aboutReaderControls.css, however, we decoupled it
|
||||
* from the original file so we don't need to load a bunch of extra queries that
|
||||
* will not take effect when using the simplify page checkbox. This file defines
|
||||
* styling for title and author on the header element. */
|
||||
* Some of these rules (styling for title and author on the header element)
|
||||
* already exist on aboutReaderControls.css, however, we decoupled it from the
|
||||
* original file so we don't need to load a bunch of extra queries that will not
|
||||
* take effect when using the simplify page checkbox. */
|
||||
|
||||
body {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#container {
|
||||
max-width: 100%;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
}
|
||||
|
||||
.header > h1 {
|
||||
font-size: 1.6em;
|
||||
|
@ -554,6 +554,13 @@ var Printing = {
|
||||
headStyleElement.setAttribute("type", "text/css");
|
||||
content.document.head.appendChild(headStyleElement);
|
||||
|
||||
// Create link element referencing simplifyMode.css and append it to head
|
||||
headStyleElement = content.document.createElement("link");
|
||||
headStyleElement.setAttribute("rel", "stylesheet");
|
||||
headStyleElement.setAttribute("href", "chrome://global/content/simplifyMode.css");
|
||||
headStyleElement.setAttribute("type", "text/css");
|
||||
content.document.head.appendChild(headStyleElement);
|
||||
|
||||
content.document.body.innerHTML = "";
|
||||
|
||||
// Create container div (main element) and append it to body
|
||||
@ -567,12 +574,6 @@ var Printing = {
|
||||
headerElement.setAttribute("class", "header");
|
||||
containerElement.appendChild(headerElement);
|
||||
|
||||
// Create style element for header div and import simplifyMode.css
|
||||
let controlHeaderStyle = content.document.createElement("style");
|
||||
controlHeaderStyle.setAttribute("scoped", "");
|
||||
controlHeaderStyle.textContent = "@import url(\"chrome://global/content/simplifyMode.css\");";
|
||||
headerElement.appendChild(controlHeaderStyle);
|
||||
|
||||
// Jam the article's title and byline into header div
|
||||
let titleElement = content.document.createElement("h1");
|
||||
titleElement.setAttribute("id", "reader-title");
|
||||
|
@ -6,19 +6,6 @@ body {
|
||||
padding: 64px 51px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
#container {
|
||||
max-width: 100% !important;
|
||||
font-size: 14px !important;
|
||||
font-family: Georgia, "Times New Roman", serif !important;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
body.loaded {
|
||||
transition: color 0.4s, background-color 0.4s;
|
||||
}
|
||||
|
@ -55,6 +55,7 @@
|
||||
figure,
|
||||
.wp-caption {
|
||||
margin: 0 0 10px 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,9 @@
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
// Defined in dwmapi in a header that needs a higher numbered _WINNT #define
|
||||
#ifndef DWM_SIT_DISPLAYFRAME
|
||||
#define DWM_SIT_DISPLAYFRAME 0x1
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace widget {
|
||||
|
@ -604,7 +604,7 @@ WinUtils::SystemScaleFactor()
|
||||
return systemScale;
|
||||
}
|
||||
|
||||
#ifndef WM_DPICHANGED
|
||||
#if WINVER < 0x603
|
||||
typedef enum {
|
||||
MDT_EFFECTIVE_DPI = 0,
|
||||
MDT_ANGULAR_DPI = 1,
|
||||
|
@ -64,8 +64,9 @@ nsClipboard::nsClipboard() : nsBaseClipboard()
|
||||
// to the OS clipboard.
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
do_GetService("@mozilla.org/observer-service;1");
|
||||
if (observerService)
|
||||
if (observerService) {
|
||||
observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@ -94,26 +95,34 @@ UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime)
|
||||
{
|
||||
UINT format;
|
||||
|
||||
if (strcmp(aMimeStr, kTextMime) == 0)
|
||||
if (strcmp(aMimeStr, kTextMime) == 0) {
|
||||
format = CF_TEXT;
|
||||
else if (strcmp(aMimeStr, kUnicodeMime) == 0)
|
||||
}
|
||||
else if (strcmp(aMimeStr, kUnicodeMime) == 0) {
|
||||
format = CF_UNICODETEXT;
|
||||
else if (strcmp(aMimeStr, kRTFMime) == 0)
|
||||
}
|
||||
else if (strcmp(aMimeStr, kRTFMime) == 0) {
|
||||
format = ::RegisterClipboardFormat(L"Rich Text Format");
|
||||
}
|
||||
else if (strcmp(aMimeStr, kJPEGImageMime) == 0 ||
|
||||
strcmp(aMimeStr, kJPGImageMime) == 0 ||
|
||||
strcmp(aMimeStr, kPNGImageMime) == 0)
|
||||
strcmp(aMimeStr, kPNGImageMime) == 0) {
|
||||
format = CF_DIBV5;
|
||||
}
|
||||
else if (strcmp(aMimeStr, kFileMime) == 0 ||
|
||||
strcmp(aMimeStr, kFilePromiseMime) == 0)
|
||||
strcmp(aMimeStr, kFilePromiseMime) == 0) {
|
||||
format = CF_HDROP;
|
||||
}
|
||||
else if (strcmp(aMimeStr, kNativeHTMLMime) == 0 ||
|
||||
aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0)
|
||||
aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0) {
|
||||
format = CF_HTML;
|
||||
else if (strcmp(aMimeStr, kCustomTypesMime) == 0)
|
||||
}
|
||||
else if (strcmp(aMimeStr, kCustomTypesMime) == 0) {
|
||||
format = CF_CUSTOMTYPES;
|
||||
else
|
||||
}
|
||||
else {
|
||||
format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
@ -129,8 +138,9 @@ nsresult nsClipboard::CreateNativeDataObject(nsITransferable * aTransferable, ID
|
||||
// the OLE IDataObject interface
|
||||
nsDataObj * dataObj = new nsDataObj(uri);
|
||||
|
||||
if (!dataObj)
|
||||
if (!dataObj) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
dataObj->AddRef();
|
||||
|
||||
@ -253,8 +263,9 @@ nsresult nsClipboard::SetupNativeDataObject(nsITransferable * aTransferable, IDa
|
||||
//-------------------------------------------------------------------------
|
||||
NS_IMETHODIMP nsClipboard::SetNativeClipboardData ( int32_t aWhichClipboard )
|
||||
{
|
||||
if ( aWhichClipboard != kGlobalClipboard )
|
||||
if (aWhichClipboard != kGlobalClipboard) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mIgnoreEmptyNotification = true;
|
||||
|
||||
@ -412,8 +423,9 @@ nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject * aDataObject, UINT
|
||||
*aData = nullptr;
|
||||
*aLen = 0;
|
||||
|
||||
if ( !aDataObject )
|
||||
if (!aDataObject) {
|
||||
return result;
|
||||
}
|
||||
|
||||
UINT format = aFormat;
|
||||
HRESULT hres = S_FALSE;
|
||||
@ -505,8 +517,9 @@ nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject * aDataObject, UINT
|
||||
*aLen = fileNameLen * sizeof(char16_t);
|
||||
result = NS_OK;
|
||||
}
|
||||
else
|
||||
else {
|
||||
result = NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
GlobalUnlock (stm.hGlobal) ;
|
||||
|
||||
@ -583,8 +596,9 @@ nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
|
||||
nsITransferable * aTransferable)
|
||||
{
|
||||
// make sure we have a good transferable
|
||||
if ( !aTransferable )
|
||||
if (!aTransferable) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nsresult res = NS_ERROR_FAILURE;
|
||||
|
||||
@ -592,8 +606,9 @@ nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
|
||||
// obtained through conversion)
|
||||
nsCOMPtr<nsIArray> flavorList;
|
||||
res = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) );
|
||||
if ( NS_FAILED(res) )
|
||||
if (NS_FAILED(res)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Walk through flavors and see which flavor is on the clipboard them on the native clipboard,
|
||||
uint32_t i;
|
||||
@ -612,26 +627,30 @@ nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
|
||||
uint32_t dataLen = 0;
|
||||
bool dataFound = false;
|
||||
if (nullptr != aDataObject) {
|
||||
if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format, flavorStr, &data, &dataLen)) )
|
||||
if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format, flavorStr, &data, &dataLen))) {
|
||||
dataFound = true;
|
||||
}
|
||||
}
|
||||
else if (nullptr != aWindow) {
|
||||
if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format, &data, &dataLen)) )
|
||||
if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format, &data, &dataLen))) {
|
||||
dataFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// This is our second chance to try to find some data, having not found it
|
||||
// when directly asking for the flavor. Let's try digging around in other
|
||||
// flavors to help satisfy our craving for data.
|
||||
if ( !dataFound ) {
|
||||
if ( strcmp(flavorStr, kUnicodeMime) == 0 )
|
||||
dataFound = FindUnicodeFromPlainText ( aDataObject, anIndex, &data, &dataLen );
|
||||
if (strcmp(flavorStr, kUnicodeMime) == 0) {
|
||||
dataFound = FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
|
||||
}
|
||||
else if ( strcmp(flavorStr, kURLMime) == 0 ) {
|
||||
// drags from other windows apps expose the native
|
||||
// CFSTR_INETURL{A,W} flavor
|
||||
dataFound = FindURLFromNativeURL ( aDataObject, anIndex, &data, &dataLen );
|
||||
if ( !dataFound )
|
||||
dataFound = FindURLFromLocalFile ( aDataObject, anIndex, &data, &dataLen );
|
||||
if (!dataFound) {
|
||||
dataFound = FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
|
||||
}
|
||||
}
|
||||
} // if we try one last ditch effort to find our data
|
||||
|
||||
@ -642,8 +661,9 @@ nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
|
||||
// we have a file path in |data|. Create an nsLocalFile object.
|
||||
nsDependentString filepath(reinterpret_cast<char16_t*>(data));
|
||||
nsCOMPtr<nsIFile> file;
|
||||
if ( NS_SUCCEEDED(NS_NewLocalFile(filepath, false, getter_AddRefs(file))) )
|
||||
if (NS_SUCCEEDED(NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
|
||||
genericDataWrapper = do_QueryInterface(file);
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
else if ( strcmp(flavorStr, kNativeHTMLMime) == 0 ) {
|
||||
@ -651,8 +671,9 @@ nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
|
||||
// the editor folks want CF_HTML exactly as it's on the clipboard, no conversions,
|
||||
// no fancy stuff. Pull it off the clipboard, stuff it into a wrapper and hand
|
||||
// it back to them.
|
||||
if ( FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen) )
|
||||
nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) );
|
||||
if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
|
||||
nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
|
||||
}
|
||||
else
|
||||
{
|
||||
free(data);
|
||||
@ -695,8 +716,9 @@ nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
|
||||
|
||||
if (strcmp(flavorStr, kRTFMime) == 0) {
|
||||
// RTF on Windows is known to sometimes deliver an extra null byte.
|
||||
if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0')
|
||||
if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
|
||||
dataLen--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -851,8 +873,9 @@ nsClipboard :: FindURLFromLocalFile ( IDataObject* inDataObject, UINT inIndex, v
|
||||
file->GetLeafName(title);
|
||||
// We rely on IsInternetShortcut check that file has a .url extension.
|
||||
title.SetLength(title.Length() - 4);
|
||||
if (title.IsEmpty())
|
||||
if (title.IsEmpty()) {
|
||||
title = urlString;
|
||||
}
|
||||
*outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + title);
|
||||
*outDataLen = NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
|
||||
|
||||
@ -910,10 +933,12 @@ nsClipboard :: FindURLFromNativeURL ( IDataObject* inDataObject, UINT inIndex, v
|
||||
bool unescaped = NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen, esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
|
||||
|
||||
nsString urlString;
|
||||
if (unescaped)
|
||||
if (unescaped) {
|
||||
NS_CopyNativeToUnicode(urlUnescapedA, urlString);
|
||||
else
|
||||
}
|
||||
else {
|
||||
NS_CopyNativeToUnicode(nsDependentCString(static_cast<char*>(tempOutData), tempDataLen), urlString);
|
||||
}
|
||||
|
||||
// the internal mozilla URL format, text/x-moz-url, contains
|
||||
// URL\ntitle. Since we don't actually have a title here,
|
||||
@ -936,13 +961,15 @@ nsClipboard :: ResolveShortcut ( nsIFile* aFile, nsACString& outURL )
|
||||
{
|
||||
nsCOMPtr<nsIFileProtocolHandler> fph;
|
||||
nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
|
||||
if (NS_FAILED(rv))
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
|
||||
if (NS_FAILED(rv))
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uri->GetSpec(outURL);
|
||||
} // ResolveShortcut
|
||||
@ -965,8 +992,9 @@ NS_IMETHODIMP
|
||||
nsClipboard::GetNativeClipboardData ( nsITransferable * aTransferable, int32_t aWhichClipboard )
|
||||
{
|
||||
// make sure we have a good transferable
|
||||
if ( !aTransferable || aWhichClipboard != kGlobalClipboard )
|
||||
if ( !aTransferable || aWhichClipboard != kGlobalClipboard ) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult res;
|
||||
|
||||
@ -1006,13 +1034,15 @@ NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(const char** aFlavorList,
|
||||
bool *_retval)
|
||||
{
|
||||
*_retval = false;
|
||||
if (aWhichClipboard != kGlobalClipboard || !aFlavorList)
|
||||
if (aWhichClipboard != kGlobalClipboard || !aFlavorList) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0;i < aLength; ++i) {
|
||||
for (uint32_t i = 0; i < aLength; ++i) {
|
||||
#ifdef DEBUG
|
||||
if (strcmp(aFlavorList[i], kTextMime) == 0)
|
||||
NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" );
|
||||
if (strcmp(aFlavorList[i], kTextMime) == 0) {
|
||||
NS_WARNING("DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD");
|
||||
}
|
||||
#endif
|
||||
|
||||
UINT format = GetFormat(aFlavorList[i]);
|
||||
@ -1026,8 +1056,9 @@ NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(const char** aFlavorList,
|
||||
if (strcmp(aFlavorList[i], kUnicodeMime) == 0) {
|
||||
// client asked for unicode and it wasn't present, check if we have CF_TEXT.
|
||||
// We'll handle the actual data substitution in the data object.
|
||||
if (IsClipboardFormatAvailable(GetFormat(kTextMime)))
|
||||
if (IsClipboardFormatAvailable(GetFormat(kTextMime))) {
|
||||
*_retval = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,8 @@
|
||||
#ifndef SM_SYSTEMDOCKED
|
||||
#define SM_CONVERTIBLESLATEMODE 0x00002003
|
||||
#define SM_SYSTEMDOCKED 0x00002004
|
||||
#endif
|
||||
#if WINVER < 0x0601
|
||||
typedef enum _AR_STATE
|
||||
{
|
||||
AR_ENABLED = 0x0,
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "mozilla/TouchEvents.h"
|
||||
|
||||
// Desktop builds target apis for 502. Win8 Metro builds target 602.
|
||||
#if WINVER < 0x0602
|
||||
#if WINVER < 0x0601
|
||||
|
||||
DECLARE_HANDLE(HGESTUREINFO);
|
||||
|
||||
|
@ -329,6 +329,118 @@ private:
|
||||
Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
|
||||
DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
|
||||
|
||||
#if defined(ACCESSIBILITY)
|
||||
/**
|
||||
* Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
|
||||
* injecting tiptsf.dll. The touchscreen process then posts registered messages
|
||||
* to our main thread. The tiptsf hook picks up those registered messages and
|
||||
* uses them as commands, some of which call into UIA, which then calls into
|
||||
* MSAA, which then sends WM_GETOBJECT to us.
|
||||
*
|
||||
* We can get ahead of this by installing our own thread-local WH_GETMESSAGE
|
||||
* hook. Since thread-local hooks are called ahead of global hooks, we will
|
||||
* see these registered messages before tiptsf does. At this point we can then
|
||||
* raise a flag that blocks a11y before invoking CallNextHookEx which will then
|
||||
* invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
|
||||
* flag by calling TIPMessageHandler::IsA11yBlocked().
|
||||
*/
|
||||
class TIPMessageHandler
|
||||
{
|
||||
public:
|
||||
~TIPMessageHandler()
|
||||
{
|
||||
if (mHook) {
|
||||
::UnhookWindowsHookEx(mHook);
|
||||
}
|
||||
}
|
||||
|
||||
static void Initialize()
|
||||
{
|
||||
MOZ_ASSERT(!sInstance);
|
||||
sInstance = new TIPMessageHandler();
|
||||
ClearOnShutdown(&sInstance);
|
||||
}
|
||||
|
||||
static bool IsA11yBlocked()
|
||||
{
|
||||
MOZ_ASSERT(sInstance);
|
||||
if (!sInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sInstance->mA11yBlockCount > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
TIPMessageHandler()
|
||||
: mHook(nullptr)
|
||||
, mA11yBlockCount(0)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Registered messages used by tiptsf
|
||||
mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
|
||||
mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
|
||||
mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
|
||||
mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
|
||||
mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
|
||||
mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
|
||||
mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
|
||||
|
||||
mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
|
||||
::GetCurrentThreadId());
|
||||
MOZ_ASSERT(mHook);
|
||||
}
|
||||
|
||||
class MOZ_RAII A11yInstantiationBlocker
|
||||
{
|
||||
public:
|
||||
A11yInstantiationBlocker()
|
||||
{
|
||||
MOZ_ASSERT(TIPMessageHandler::sInstance);
|
||||
++TIPMessageHandler::sInstance->mA11yBlockCount;
|
||||
}
|
||||
|
||||
~A11yInstantiationBlocker()
|
||||
{
|
||||
MOZ_ASSERT(TIPMessageHandler::sInstance &&
|
||||
TIPMessageHandler::sInstance->mA11yBlockCount > 0);
|
||||
--TIPMessageHandler::sInstance->mA11yBlockCount;
|
||||
}
|
||||
};
|
||||
|
||||
friend class A11yInstantiationBlocker;
|
||||
|
||||
static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam)
|
||||
{
|
||||
if (aCode < 0 || !sInstance) {
|
||||
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
|
||||
}
|
||||
|
||||
MSG* msg = reinterpret_cast<MSG*>(aLParam);
|
||||
UINT& msgCode = msg->message;
|
||||
|
||||
for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
|
||||
if (msgCode == sInstance->mMessages[i]) {
|
||||
A11yInstantiationBlocker block;
|
||||
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
|
||||
}
|
||||
}
|
||||
|
||||
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
|
||||
}
|
||||
|
||||
static StaticAutoPtr<TIPMessageHandler> sInstance;
|
||||
|
||||
HHOOK mHook;
|
||||
UINT mMessages[7];
|
||||
uint32_t mA11yBlockCount;
|
||||
};
|
||||
|
||||
StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
|
||||
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
/**************************************************************
|
||||
@ -456,6 +568,9 @@ nsWindow::nsWindow()
|
||||
// WinTaskbar.cpp for details.
|
||||
mozilla::widget::WinTaskbar::RegisterAppUserModelID();
|
||||
KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
|
||||
#if defined(ACCESSIBILITY)
|
||||
mozilla::TIPMessageHandler::Initialize();
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
IMEHandler::Initialize();
|
||||
if (SUCCEEDED(::OleInitialize(nullptr))) {
|
||||
sIsOleInitialized = TRUE;
|
||||
@ -3281,9 +3396,9 @@ nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
|
||||
if (aFullScreen && !sCurrentWindow) {
|
||||
sCurrentWindow = this;
|
||||
LPARAM pos = sCurrentWindow->lParamToClient(sMouseExitlParamScreen);
|
||||
sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
|
||||
sMouseExitwParam, pos, false,
|
||||
WidgetMouseEvent::eLeftButton,
|
||||
sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
|
||||
sMouseExitwParam, pos, false,
|
||||
WidgetMouseEvent::eLeftButton,
|
||||
MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
|
||||
}
|
||||
|
||||
@ -5599,7 +5714,7 @@ nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
|
||||
// Do explicit casting to make it working on 64bit systems (see bug 649236
|
||||
// for details).
|
||||
int32_t objId = static_cast<DWORD>(lParam);
|
||||
if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
|
||||
if (!TIPMessageHandler::IsA11yBlocked() && objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
|
||||
a11y::Accessible* rootAccessible = GetAccessible(); // Held by a11y cache
|
||||
if (rootAccessible) {
|
||||
IAccessible *msaaAccessible = nullptr;
|
||||
|