Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2017-05-05 15:25:16 +02:00
commit 170faef00b
308 changed files with 5191 additions and 2549 deletions

View File

@ -31,6 +31,16 @@ jobs:
- date
when: [] # never (hook only)
- name: nightly-desktop-win64
job:
type: decision-task
treeherder-symbol: Nd-Win64
triggered-by: nightly
target-tasks-method: nightly_win64
run-on-projects:
- date
when: [] # never (hook only)
- name: nightly-android
job:
type: decision-task

View File

@ -2,7 +2,8 @@
* 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/. */
// Note: this file is included in aboutDialog.xul if MOZ_UPDATER is defined.
// Note: this file is included in aboutDialog.xul and preferences/advanced.xul
// if MOZ_UPDATER is defined.
/* import-globals-from aboutDialog.js */

View File

@ -29,7 +29,7 @@ with Files("sync/**"):
BUG_COMPONENT = ("Firefox", "Sync")
with Files("test/alerts/**"):
BUG_COMPONENT = ("Toolkit", "Notification and Alerts")
BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
with Files("test/appUpdate/**"):
BUG_COMPONENT = ("Toolkit", "Application Update")

View File

@ -215,15 +215,15 @@ nsContextMenu.prototype = {
initNavigationItems: function CM_initNavigationItems() {
var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
this.onCanvas || this.onVideo || this.onAudio ||
this.onTextInput || this.onSocial);
this.onTextInput) && this.inTabBrowser;
this.showItem("context-navigation", shouldShow);
this.showItem("context-sep-navigation", shouldShow);
let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
let stopReloadItem = "";
if (shouldShow || this.onSocial) {
stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
if (shouldShow || !this.inTabBrowser) {
stopReloadItem = (stopped || !this.inTabBrowser) ? "reload" : "stop";
}
this.showItem("context-reload", stopReloadItem == "reload");
@ -290,9 +290,12 @@ nsContextMenu.prototype = {
this.onImage || this.onCanvas ||
this.onVideo || this.onAudio ||
this.onLink || this.onTextInput);
var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
var showInspect = this.inTabBrowser && gPrefService.getBoolPref("devtools.inspector.enabled");
this.showItem("context-viewsource", shouldShow);
this.showItem("context-viewinfo", shouldShow);
// The page info is broken for WebExtension popups, as the browser is
// destroyed when the popup is closed.
this.setItemAttr("context-viewinfo", "disabled", this.webExtBrowserType === "popup");
this.showItem("inspect-separator", showInspect);
this.showItem("context-inspect", showInspect);
@ -341,6 +344,9 @@ nsContextMenu.prototype = {
.disabled = !this.hasBGImage;
this.showItem("context-viewimageinfo", this.onImage);
// The image info popup is broken for WebExtension popups, since the browser
// is destroyed when the popup is closed.
this.setItemAttr("context-viewimageinfo", "disabled", this.webExtBrowserType === "popup");
this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
},
@ -350,11 +356,12 @@ nsContextMenu.prototype = {
this.showItem(bookmarkPage,
!(this.isContentSelected || this.onTextInput || this.onLink ||
this.onImage || this.onVideo || this.onAudio || this.onSocial ||
this.onCanvas));
this.onCanvas || this.inWebExtBrowser));
bookmarkPage.setAttribute("tooltiptext", bookmarkPage.getAttribute("buttontooltiptext"));
this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
!this.onSocial) || this.onPlainTextLink);
!this.onSocial && !this.onMozExtLink) ||
this.onPlainTextLink);
this.showItem("context-keywordfield",
this.onTextInput && this.onKeywordField);
this.showItem("frame", this.inFrame);
@ -398,13 +405,14 @@ nsContextMenu.prototype = {
let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
let pageShare = shareEnabled && !(this.isContentSelected ||
this.onTextInput || this.onLink || this.onImage ||
this.onVideo || this.onAudio || this.onCanvas);
this.onVideo || this.onAudio || this.onCanvas ||
this.inWebExtBrowser);
this.showItem("context-sharepage", pageShare);
this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink && !this.onMozExtLink);
this.showItem("context-shareimage", shareEnabled && this.onImage);
this.showItem("context-sharevideo", shareEnabled && this.onVideo);
this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:"));
this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:") || this.mediaURL.startsWith("moz-extension:"));
},
initSpellingItems() {
@ -677,6 +685,10 @@ nsContextMenu.prototype = {
this.onCTPPlugin = false;
this.canSpellCheck = false;
this.onPassword = false;
this.webExtBrowserType = "";
this.inWebExtBrowser = false;
this.inTabBrowser = true;
this.onMozExtLink = false;
if (this.isRemote) {
this.selectionInfo = gContextMenuContentData.selectionInfo;
@ -713,6 +725,10 @@ nsContextMenu.prototype = {
this.frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
}
this.onSocial = !!this.browser.getAttribute("origin");
this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
this.inWebExtBrowser = !!this.webExtBrowserType;
this.inTabBrowser = this.browser.ownerGlobal.gBrowser ?
!!this.browser.ownerGlobal.gBrowser.getTabForBrowser(this.browser) : false;
// Check if we are in a synthetic document (stand alone image, video, etc.).
this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
@ -869,6 +885,7 @@ nsContextMenu.prototype = {
this.linkTextStr = this.getLinkText();
this.linkProtocol = this.getLinkProtocol();
this.onMailtoLink = (this.linkProtocol == "mailto");
this.onMozExtLink = (this.linkProtocol == "moz-extension");
this.onSaveableLink = this.isLinkSaveable( this.link );
this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
try {
@ -1033,8 +1050,11 @@ nsContextMenu.prototype = {
}
if (!this.isRemote) {
params.frameOuterWindowID = WebNavigationFrames.getFrameId(this.target.ownerGlobal);
// Propagate the frameOuterWindowID value saved when
// the context menu has been opened.
params.frameOuterWindowID = this.frameOuterWindowID;
}
// If we want to change userContextId, we must be sure that we don't
// propagate the referrer.
if ("userContextId" in params &&
@ -1911,7 +1931,7 @@ nsContextMenu.prototype = {
_getTelemetryPageContextInfo() {
let rv = [];
for (let k of ["isContentSelected", "onLink", "onImage", "onCanvas", "onVideo", "onAudio",
"onTextInput", "onSocial"]) {
"onTextInput", "onSocial", "inWebExtBrowser", "inTabBrowser"]) {
if (this[k]) {
rv.push(k.replace(/^(?:is|on)(.)/, (match, firstLetter) => firstLetter.toLowerCase()));
}

View File

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
]
};

View File

@ -0,0 +1,6 @@
[DEFAULT]
support-files =
!/browser/base/content/test/general/contextmenu_common.js
subtst_contextmenu_webext.html
[browser_contextmenu_mozextension.js]

View File

@ -0,0 +1,82 @@
"use strict";
var { SocialService } = Cu.import("resource:///modules/SocialService.jsm", {});
let contextMenu;
let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
// A social share provider
let manifest = {
name: "provider 1",
origin: "https://example.com",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
};
add_task(function* test_setup() {
const example_base = "http://example.com/browser/browser/base/content/test/contextMenu/";
const url = example_base + "subtst_contextmenu_webext.html";
yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
const contextmenu_common = chrome_base + "contextmenu_common.js";
/* import-globals-from ../general/contextmenu_common.js */
Services.scriptloader.loadSubScript(contextmenu_common, this);
// Enable social sharing functions in the browser, so the context menu item is shown.
CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
yield new Promise((resolve) => SocialService.addProvider(manifest, resolve));
ok(SocialShare.shareButton && !SocialShare.shareButton.disabled, "Sharing is enabled");
});
add_task(function* test_link() {
// gets hidden for this case.
yield test_contextmenu("#link",
["context-openlinkintab", true,
...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
// We need a blank entry here because the containers submenu is
// dynamically generated with no ids.
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-savelink", true,
"context-copylink", true,
"context-searchselect", true]);
});
add_task(function* test_video() {
yield test_contextmenu("#video",
["context-media-play", null,
"context-media-mute", null,
"context-media-playbackrate", null,
["context-media-playbackrate-050x", null,
"context-media-playbackrate-100x", null,
"context-media-playbackrate-125x", null,
"context-media-playbackrate-150x", null,
"context-media-playbackrate-200x", null], null,
"context-media-loop", null,
"context-media-showcontrols", null,
"context-video-fullscreen", null,
"---", null,
"context-viewvideo", null,
"context-copyvideourl", null,
"---", null,
"context-savevideo", null,
"context-sharevideo", false,
"context-video-saveimage", null,
"context-sendvideo", null,
"context-castvideo", null,
[], null
]);
});
add_task(function* test_cleanup() {
lastElementSelector = null;
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
yield new Promise((resolve) => {
return SocialService.disableProvider(manifest.origin, resolve);
});
});

View File

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for browser context menu</title>
</head>
<body>
Browser context menu subtest.
<a href="moz-extension://foo-bar/tab.html" id="link">Link to an extension resource</a>
<video src="moz-extension://foo-bar/video.ogg" id="video"></video>
</body>
</html>

View File

@ -6,3 +6,4 @@ support-files =
skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
[browser_selectpopup_colors.js]
skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
[browser_selectpopup_searchfocus.js]

View File

@ -0,0 +1,42 @@
let SELECT =
"<html><body><select id='one'>";
for (let i = 0; i < 75; i++) {
SELECT +=
` <option>${i}${i}${i}${i}${i}</option>`;
}
SELECT +=
' <option selected="true">{"end": "true"}</option>' +
"</select></body></html>";
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({
"set": [
["dom.select_popup_in_parent.enabled", true],
["dom.forms.selectSearch", true]
]
});
});
add_task(function* test_focus_on_search_shouldnt_close_popup() {
const pageUrl = "data:text/html," + escape(SELECT);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
let menulist = document.getElementById("ContentSelectDropdown");
let selectPopup = menulist.menupopup;
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
yield popupShownPromise;
let searchInput = selectPopup.querySelector("textbox[type='search']");
searchInput.scrollIntoView();
let searchFocused = BrowserTestUtils.waitForEvent(searchInput, "focus");
yield EventUtils.synthesizeMouseAtCenter(searchInput, {}, window);
yield searchFocused;
is(selectPopup.state, "open", "select popup should still be open after clicking on the search field");
yield hideSelectPopup(selectPopup, "escape");
yield BrowserTestUtils.removeTab(tab);
});

View File

@ -235,8 +235,6 @@ var whitelist = new Set([
{file: "resource://gre/modules/Manifest.jsm"},
// Bug 1351089
{file: "resource://gre/modules/PresentationDeviceInfoManager.jsm"},
// Bug 1351091
{file: "resource://gre/modules/Profiler.jsm"},
// Bug 1351658
{file: "resource://gre/modules/PropertyListUtils.jsm", platforms: ["linux", "win"]},
// Bug 1351097

View File

@ -1,9 +1,11 @@
[DEFAULT]
support-files =
dummy_page.html
test_bug1358314.html
[browser_abandonment_telemetry.js]
[browser_allow_process_switches_despite_related_browser.js]
[browser_contextmenu_openlink_after_tabnavigated.js]
[browser_tabCloseProbes.js]
[browser_tabSpinnerProbe.js]
skip-if = !e10s # Tab spinner is e10s only.

View File

@ -0,0 +1,43 @@
"use strict";
const example_base = "http://example.com/browser/browser/base/content/test/tabs/";
add_task(function* test_contextmenu_openlink_after_tabnavigated() {
let url = example_base + "test_bug1358314.html";
const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
const contextMenu = document.getElementById("contentAreaContextMenu");
let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
yield BrowserTestUtils.synthesizeMouse("a", 0, 0, {
type: "contextmenu",
button: 2,
}, gBrowser.selectedBrowser);
yield awaitPopupShown;
info("Popup Shown");
info("Navigate the tab with the opened context menu");
gBrowser.selectedBrowser.loadURI("about:blank");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let awaitNewTabOpen = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/");
info("Click the 'open link in new tab' menu item");
let openLinkMenuItem = contextMenu.querySelector("#context-openlinkintab");
openLinkMenuItem.click();
info("Wait for the new tab to be opened");
const newTab = yield awaitNewTabOpen;
// Close the contextMenu popup if it has not been closed yet.
contextMenu.hidePopup();
yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
const newTabURL = yield ContentTask.spawn(newTab.linkedBrowser, null, function* () {
return content.location.href;
});
is(newTabURL, "http://example.com/", "Got the expected URL loaded in the new tab");
yield BrowserTestUtils.removeTab(newTab);
yield BrowserTestUtils.removeTab(tab);
});

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>Test page</p>
<a href="/">Link</a>
</body>
</html>

View File

@ -17,6 +17,7 @@ MOCHITEST_CHROME_MANIFESTS += [
BROWSER_CHROME_MANIFESTS += [
'content/test/alerts/browser.ini',
'content/test/captivePortal/browser.ini',
'content/test/contextMenu/browser.ini',
'content/test/forms/browser.ini',
'content/test/general/browser.ini',
'content/test/newtab/browser.ini',

View File

@ -233,6 +233,7 @@ class BasePopup {
browser.setAttribute("class", "webextension-popup-browser");
browser.setAttribute("webextension-view-type", "popup");
browser.setAttribute("tooltip", "aHTMLTooltip");
browser.setAttribute("contextmenu", "contentAreaContextMenu");
if (this.extension.remote) {
browser.setAttribute("remote", "true");
@ -286,6 +287,9 @@ class BasePopup {
setupBrowser(browser);
let mm = browser.messageManager;
// Sets the context information for context menus.
mm.loadFrameScript("chrome://browser/content/content.js", true);
mm.loadFrameScript(
"chrome://extensions/content/ext-browser-content.js", false);

View File

@ -321,7 +321,7 @@ this.browserAction = class extends ExtensionAPI {
if (pendingPopup) {
if (pendingPopup.window === window && pendingPopup.popupURL === popupURL) {
if (!this.blockParser) {
if (!blockParser) {
pendingPopup.unblockParser();
}

View File

@ -31,6 +31,7 @@ support-files =
[browser_ext_browserAction_area.js]
[browser_ext_browserAction_context.js]
[browser_ext_browserAction_contextMenu.js]
[browser_ext_browserAction_disabled.js]
[browser_ext_browserAction_pageAction_icon.js]
[browser_ext_browserAction_pageAction_icon_permissions.js]
@ -73,6 +74,7 @@ skip-if = debug || asan # Bug 1354681
[browser_ext_optionsPage_browser_style.js]
[browser_ext_optionsPage_privileges.js]
[browser_ext_pageAction_context.js]
[browser_ext_pageAction_contextMenu.js]
[browser_ext_pageAction_popup.js]
[browser_ext_pageAction_popup_resize.js]
[browser_ext_pageAction_simple.js]
@ -93,6 +95,7 @@ skip-if = debug || asan # Bug 1354681
[browser_ext_sessions_restore.js]
[browser_ext_sidebarAction.js]
[browser_ext_sidebarAction_context.js]
[browser_ext_sidebarAction_contextMenu.js]
[browser_ext_sidebarAction_tabs.js]
[browser_ext_sidebarAction_windows.js]
[browser_ext_simple.js]

View File

@ -0,0 +1,106 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
let extData = {
manifest: {
"permissions": ["contextMenus"],
"browser_action": {
"default_popup": "popup.html",
},
},
useAddonManager: "temporary",
files: {
"popup.html": `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/>
</head>
<body>
<span id="text">A Test Popup</span>
<img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
</body></html>
`,
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
},
};
let contextMenuItems = {
"context-navigation": "hidden",
"context-sep-navigation": "hidden",
"context-viewsource": "",
"context-viewinfo": "disabled",
"inspect-separator": "hidden",
"context-inspect": "hidden",
"context-bookmarkpage": "hidden",
"context-sharepage": "hidden",
};
add_task(function* browseraction_popup_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield clickBrowserAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension);
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* browseraction_popup_contextmenu_hidden_items() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield clickBrowserAction(extension);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
let item, state;
for (const itemID in contextMenuItems) {
item = contentAreaContextMenu.querySelector(`#${itemID}`);
state = contextMenuItems[itemID];
if (state !== "") {
ok(item[state], `${itemID} is ${state}`);
if (state !== "hidden") {
ok(!item.hidden, `Disabled ${itemID} is not hidden`);
}
} else {
ok(!item.hidden, `${itemID} is not hidden`);
ok(!item.disabled, `${itemID} is not disabled`);
}
}
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* browseraction_popup_image_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield clickBrowserAction(extension);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
ok(!item.hidden);
ok(item.disabled);
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});

View File

@ -0,0 +1,116 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
let extData = {
manifest: {
"permissions": ["contextMenus"],
"page_action": {
"default_popup": "popup.html",
},
},
useAddonManager: "temporary",
files: {
"popup.html": `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/>
</head>
<body>
<span id="text">A Test Popup</span>
<img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
</body></html>
`,
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
browser.tabs.query({active: true, currentWindow: true}, tabs => {
const tabId = tabs[0].id;
browser.pageAction.show(tabId).then(() => {
browser.test.sendMessage("action-shown");
});
});
},
};
let contextMenuItems = {
"context-navigation": "hidden",
"context-sep-navigation": "hidden",
"context-viewsource": "",
"context-viewinfo": "disabled",
"inspect-separator": "hidden",
"context-inspect": "hidden",
"context-bookmarkpage": "hidden",
"context-sharepage": "hidden",
};
add_task(function* pageaction_popup_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield extension.awaitMessage("action-shown");
yield clickPageAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension);
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* pageaction_popup_contextmenu_hidden_items() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield extension.awaitMessage("action-shown");
yield clickPageAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
let item, state;
for (const itemID in contextMenuItems) {
item = contentAreaContextMenu.querySelector(`#${itemID}`);
state = contextMenuItems[itemID];
if (state !== "") {
ok(item[state], `${itemID} is ${state}`);
if (state !== "hidden") {
ok(!item.hidden, `Disabled ${itemID} is not hidden`);
}
} else {
ok(!item.hidden, `${itemID} is not hidden`);
ok(!item.disabled, `${itemID} is not disabled`);
}
}
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* pageaction_popup_image_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield extension.awaitMessage("action-shown");
yield clickPageAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
ok(!item.hidden);
ok(item.disabled);
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});

View File

@ -4,7 +4,6 @@
let extData = {
manifest: {
"permissions": ["contextMenus"],
"sidebar_action": {
"default_panel": "sidebar.html",
},
@ -31,12 +30,6 @@ let extData = {
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
browser.test.onMessage.addListener(msg => {
if (msg === "set-panel") {
browser.sidebarAction.setPanel({panel: ""}).then(() => {
@ -103,20 +96,6 @@ add_task(function* sidebar_empty_panel() {
yield extension.unload();
});
add_task(function* sidebar_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar();
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* cleanup() {
// This is set on initial sidebar install.
Services.prefs.clearUserPref("extensions.sidebar-button.shown");

View File

@ -0,0 +1,119 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
let extData = {
manifest: {
"permissions": ["contextMenus"],
"sidebar_action": {
"default_panel": "sidebar.html",
},
},
useAddonManager: "temporary",
files: {
"sidebar.html": `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/>
<script src="sidebar.js"></script>
</head>
<body>
<span id="text">A Test Sidebar</span>
<img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
</body></html>
`,
"sidebar.js": function() {
window.onload = () => {
browser.test.sendMessage("sidebar");
};
},
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
},
};
let contextMenuItems = {
"context-navigation": "hidden",
"context-sep-navigation": "hidden",
"context-viewsource": "",
"context-viewinfo": "",
"inspect-separator": "hidden",
"context-inspect": "hidden",
"context-bookmarkpage": "hidden",
"context-sharepage": "hidden",
};
add_task(function* sidebar_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar();
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* sidebar_contextmenu_hidden_items() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar("#text");
let item, state;
for (const itemID in contextMenuItems) {
item = contentAreaContextMenu.querySelector(`#${itemID}`);
state = contextMenuItems[itemID];
if (state !== "") {
ok(item[state], `${itemID} is ${state}`);
if (state !== "hidden") {
ok(!item.hidden, `Disabled ${itemID} is not hidden`);
}
} else {
ok(!item.hidden, `${itemID} is not hidden`);
ok(!item.disabled, `${itemID} is not disabled`);
}
}
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* sidebar_image_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar("#testimg");
let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
ok(!item.hidden);
ok(!item.disabled);
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* cleanup() {
// This is set on initial sidebar install.
Services.prefs.clearUserPref("extensions.sidebar-button.shown");
});

View File

@ -8,7 +8,8 @@
* getBrowserActionPopup getPageActionPopup
* closeBrowserAction closePageAction
* promisePopupShown promisePopupHidden
* openContextMenu closeContextMenu openContextMenuInSidebar
* openContextMenu closeContextMenu
* openContextMenuInSidebar openContextMenuInPopup
* openExtensionContextMenu closeExtensionContextMenu
* openActionContextMenu openSubmenu closeActionContextMenu
* openTabContextMenu closeTabContextMenu
@ -232,6 +233,16 @@ function closeBrowserAction(extension, win = window) {
return Promise.resolve();
}
async function openContextMenuInPopup(extension, selector = "body") {
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
let browser = await awaitExtensionPanel(extension);
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "mousedown", button: 2}, browser);
await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, browser);
await popupShownPromise;
return contentAreaContextMenu;
}
async function openContextMenuInSidebar(selector = "body") {
let contentAreaContextMenu = SidebarUI.browser.contentDocument.getElementById("contentAreaContextMenu");
let browser = SidebarUI.browser.contentDocument.getElementById("webext-panels-browser");

View File

@ -1,6 +1,7 @@
[DEFAULT]
support-files =
../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
../../../../../toolkit/components/extensions/test/mochitest/file_sample.html
tags = webextensions
[test_ext_all_apis.html]

View File

@ -3,6 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from preferences.js */
/* import-globals-from ../../../base/content/aboutDialog-appUpdater.js */
// Load DownloadUtils module for convertByteUnits
Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
@ -20,7 +21,56 @@ var gAdvancedPane = {
this._inited = true;
let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
// Include the build ID if this is an "a#" (nightly) build
if (/a\d+$/.test(version)) {
let buildID = Services.appinfo.appBuildID;
let year = buildID.slice(0, 4);
let month = buildID.slice(4, 6);
let day = buildID.slice(6, 8);
version += ` (${year}-${month}-${day})`;
}
// Append "(32-bit)" or "(64-bit)" build architecture to the version number:
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
let archResource = Services.appinfo.is64Bit
? "aboutDialog.architecture.sixtyFourBit"
: "aboutDialog.architecture.thirtyTwoBit";
let arch = bundle.GetStringFromName(archResource);
version += ` (${arch})`;
document.getElementById("version").textContent = version;
// Show a release notes link if we have a URL.
let relNotesLink = document.getElementById("releasenotes");
let relNotesPrefType = Services.prefs.getPrefType("app.releaseNotesURL");
if (relNotesPrefType != Services.prefs.PREF_INVALID) {
let relNotesURL = Services.urlFormatter.formatURLPref("app.releaseNotesURL");
if (relNotesURL != "about:blank") {
relNotesLink.href = relNotesURL;
relNotesLink.hidden = false;
}
}
let distroId = Services.prefs.getCharPref("distribution.id", "");
if (distroId) {
let distroVersion = Services.prefs.getCharPref("distribution.version");
let distroIdField = document.getElementById("distributionId");
distroIdField.value = distroId + " - " + distroVersion;
distroIdField.hidden = false;
let distroAbout = Services.prefs.getStringPref("distribution.about", "");
if (distroAbout) {
let distroField = document.getElementById("distribution");
distroField.value = distroAbout;
distroField.hidden = false;
}
}
if (AppConstants.MOZ_UPDATER) {
gAppUpdater = new appUpdater();
let onUnload = () => {
window.removeEventListener("unload", onUnload);
Services.prefs.removeObserver("app.update.", this);

View File

@ -4,6 +4,10 @@
<!-- Advanced panel -->
#ifdef MOZ_UPDATER
<script type="application/javascript" src="chrome://browser/content/aboutDialog-appUpdater.js"/>
#endif
<script type="application/javascript"
src="chrome://browser/content/preferences/in-content/advanced.js"/>
@ -52,10 +56,118 @@
<!-- Update -->
<groupbox id="updateApp" data-category="paneAdvanced" hidden="true">
<caption><label>&updateApplication.label;</label></caption>
#ifdef MOZ_UPDATER
<description>&updateApplication.description;</description>
<hbox align="start">
<vbox flex="1">
<description>
&updateApplication.version.pre;<label id="version"/>&updateApplication.version.post;
<label id="releasenotes" class="learnMore text-link" hidden="true">&releaseNotes.link;</label>
</description>
<description id="distribution" class="text-blurb" hidden="true"/>
<description id="distributionId" class="text-blurb" hidden="true"/>
</vbox>
#ifdef MOZ_UPDATER
<spacer flex="1"/>
<vbox>
<button id="showUpdateHistory"
class="accessory-button"
label="&updateHistory2.label;"
accesskey="&updateHistory2.accesskey;"
preference="app.update.disable_button.showUpdateHistory"/>
</vbox>
#endif
</hbox>
#ifdef MOZ_UPDATER
<vbox id="updateBox">
<deck id="updateDeck" orient="vertical">
<hbox id="checkForUpdates" align="center">
<spacer flex="1"/>
<button id="checkForUpdatesButton"
label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
oncommand="gAppUpdater.checkForUpdates();"/>
</hbox>
<hbox id="downloadAndInstall" align="center">
<spacer flex="1"/>
<button id="downloadAndInstallButton"
oncommand="gAppUpdater.startDownload();"/>
<!-- label and accesskey will be filled by JS -->
</hbox>
<hbox id="apply" align="center">
<spacer flex="1"/>
<button id="updateButton"
label="&update.updateButton.label3;"
accesskey="&update.updateButton.accesskey;"
oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
</hbox>
<hbox id="checkingForUpdates" align="center">
<image class="update-throbber"/><label>&update.checkingForUpdates;</label>
<spacer flex="1"/>
<button label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
disabled="true"/>
</hbox>
<hbox id="downloading" align="center">
<image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
</hbox>
<hbox id="applying" align="center">
<image class="update-throbber"/><label>&update.applying;</label>
</hbox>
<hbox id="downloadFailed" align="center">
<label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
<spacer flex="1"/>
<button label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
oncommand="gAppUpdater.checkForUpdates();"/>
</hbox>
<hbox id="adminDisabled" align="center">
<label>&update.adminDisabled;</label>
<spacer flex="1"/>
<button label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
disabled="true"/>
</hbox>
<hbox id="noUpdatesFound" align="center">
<label>&update.noUpdatesFound;</label>
<spacer flex="1"/>
<button label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
oncommand="gAppUpdater.checkForUpdates();"/>
</hbox>
<hbox id="otherInstanceHandlingUpdates" align="center">
<label>&update.otherInstanceHandlingUpdates;</label>
<spacer flex="1"/>
<button label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
disabled="true"/>
</hbox>
<hbox id="manualUpdate" align="center">
<label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
<spacer flex="1"/>
<button label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
disabled="true"/>
</hbox>
<hbox id="unsupportedSystem" align="center">
<label>&update.unsupported.start;</label><label id="unsupportedLink" class="text-link">&update.unsupported.linkText;</label><label>&update.unsupported.end;</label>
<spacer flex="1"/>
<button label="&update.checkForUpdatesButton.label;"
accesskey="&update.checkForUpdatesButton.accesskey;"
disabled="true"/>
</hbox>
<hbox id="restarting" align="center">
<label>&update.restarting;</label>
<spacer flex="1"/>
<button label="&update.updateButton.label3;"
accesskey="&update.updateButton.accesskey;"
disabled="true"/>
</hbox>
</deck>
</vbox>
#endif
<separator/>
#ifdef MOZ_UPDATER
<description>&updateApplication.description;</description>
<radiogroup id="updateRadioGroup">
<radio id="autoDesktop"
value="auto"
@ -68,16 +180,6 @@
label="&updateManual2.label;"
accesskey="&updateManual2.accesskey;"/>
</radiogroup>
</vbox>
<spacer flex="1"/>
<vbox>
<button id="showUpdateHistory"
class="accessory-button"
label="&updateHistory2.label;"
accesskey="&updateHistory2.accesskey;"
preference="app.update.disable_button.showUpdateHistory"/>
</vbox>
</hbox>
#ifdef MOZ_MAINTENANCE_SERVICE
<checkbox id="useService"
label="&useService.label;"

View File

@ -37,6 +37,8 @@
"chrome://browser/locale/preferences/applications.dtd">
<!ENTITY % advancedDTD SYSTEM
"chrome://browser/locale/preferences/advanced.dtd">
<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
%aboutDialogDTD;
%brandDTD;
%globalPreferencesDTD;
%preferencesDTD;

View File

@ -110,14 +110,19 @@ add_task(function*() {
});
add_task(function*() {
mockUpdateManager.register();
yield openPreferencesViaOpenPreferencesAPI("advanced", { leaveOpen: true });
let doc = gBrowser.selectedBrowser.contentDocument;
let showBtn = doc.getElementById("showUpdateHistory");
let dialogOverlay = doc.getElementById("dialogOverlay");
// XXX: For unknown reasons, this mock cannot be loaded by
// XPCOMUtils.defineLazyServiceGetter() called in aboutDialog-appUpdater.js.
// It is registered here so that we could assert update history subdialog
// without stopping the preferences advanced pane from loading.
// See bug 1361929.
mockUpdateManager.register();
// Test the dialog window opens
is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
showBtn.doCommand();

View File

@ -32,7 +32,7 @@
class="showNormal"
label="&privatebrowsingpage.openPrivateWindow.label;"
accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"/>
<div class="showPrivate about-content-container">
<div class="showPrivate container">
<h1 class="title">
<span id="title">&privateBrowsing.title;</span>
<span id="titleTracking">&privateBrowsing.title.tracking;</span>

View File

@ -46,28 +46,9 @@ const prefObserver = {
}
};
const appStartupObserver = {
register() {
Services.obs.addObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
},
unregister() {
Services.obs.removeObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
},
observe() {
appStartupDone();
this.unregister();
}
}
const APP_STARTUP = 1;
function startup(data, reason) { // eslint-disable-line no-unused-vars
if (reason === APP_STARTUP) {
appStartupObserver.register();
} else {
appStartupDone();
}
prefObserver.register();
addonResourceURI = data.resourceURI;
// eslint-disable-next-line promise/catch-or-return

View File

@ -199,7 +199,7 @@ this.main = (function() {
}
browser.tabs.onUpdated.addListener(catcher.watchFunction((id, info, tab) => {
if (info.url && tab.active) {
if (info.url) {
if (urlEnabled(info.url)) {
enableButton(tab.id);
} else if (hasSeenOnboarding) {
@ -208,20 +208,6 @@ this.main = (function() {
}
}, true));
browser.tabs.onActivated.addListener(catcher.watchFunction(({tabId, windowId}) => {
catcher.watchPromise(browser.tabs.get(tabId).then((tab) => {
// onActivated may fire before the url is set
if (!tab.url) {
return;
}
if (urlEnabled(tab.url)) {
enableButton(tabId);
} else if (hasSeenOnboarding) {
disableButton(tabId);
}
}), true);
}));
communication.register("sendEvent", (sender, ...args) => {
catcher.watchPromise(sendEvent(...args));
// We don't wait for it to complete:

View File

@ -3,6 +3,10 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY aboutDialog.title "About &brandFullName;">
<!-- LOCALIZATION NOTE (update.*):
# These strings are also used in the update pane of preferences.
# See about:preferences#advanced.
-->
<!-- LOCALIZATION NOTE (update.checkForUpdatesButton.*, update.updateButton.*):
# Only one button is present at a time.
# The button when displayed is located directly under the Firefox version in

View File

@ -81,7 +81,19 @@
<!ENTITY updateTab.label "Update">
<!-- LOCALIZATION NOTE (updateApplication.label):
Strings from aboutDialog.dtd are displayed in this section of the preferences.
Please check for possible accesskey conflicts.
-->
<!ENTITY updateApplication.label "&brandShortName; Updates">
<!-- LOCALIZATION NOTE (updateApplication.version.*): updateApplication.version.pre
# is followed by a version number, keep the trailing space or replace it with a
# different character as needed. updateApplication.version.post is displayed
# after the version number, and is empty on purpose for English. You can use it
# if required by your language.
-->
<!ENTITY updateApplication.version.pre "Version ">
<!ENTITY updateApplication.version.post "">
<!ENTITY updateApplication.description "Allow &brandShortName; to">
<!ENTITY updateAuto2.label "Automatically install updates (recommended for improved security)">
<!ENTITY updateAuto2.accesskey "A">

View File

@ -568,6 +568,23 @@ description > html|a {
}
}
#updateDeck > hbox > label {
margin-inline-end: 5px ! important;
}
.update-throbber {
width: 16px;
min-height: 16px;
margin-inline-end: 3px;
list-style-image: url("chrome://global/skin/icons/loading.png");
}
@media (min-resolution: 1.1dppx) {
.update-throbber {
list-style-image: url("chrome://global/skin/icons/loading@2x.png");
}
}
.help-button {
position: fixed;
left: 0;

View File

@ -176,7 +176,6 @@
skin/classic/browser/places/bookmarks-notification-finish.png (../shared/places/bookmarks-notification-finish.png)
skin/classic/browser/places/bookmarks-notification-finish@2x.png (../shared/places/bookmarks-notification-finish@2x.png)
skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
skin/classic/browser/privatebrowsing/check.svg (../shared/privatebrowsing/check.svg)
skin/classic/browser/privatebrowsing/favicon.svg (../shared/privatebrowsing/favicon.svg)
skin/classic/browser/privatebrowsing/private-browsing.svg (../shared/privatebrowsing/private-browsing.svg)
skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)

View File

@ -5,52 +5,26 @@
@import url("chrome://global/skin/in-content/info-pages.css");
:root {
--color-grey-lightest: #fbfbfb;
--color-grey: #b1b1b1;
--color-blue: #0996f8;
--color-blue-dark: #0670cc;
--color-blue-darker: #005bab;
--icon-margin: 64px;
}
html.private {
--in-content-page-color: #beb8cc;
--in-content-text-color: #beb8cc;
--in-content-page-background: #1c023c;
}
body {
padding: 40px;
--in-content-page-color: white;
--in-content-text-color: white;
--in-content-page-background: #25003e;
}
a:link {
color: var(--color-blue);
text-decoration: none;
}
a:hover {
color: var(--color-blue-dark);
color: inherit;
text-decoration: underline;
}
a:hover:active {
color: var(--color-blue-darker);
}
a:visited {
color: var(--color-blue-darker);
}
.about-content-container {
max-width: 780px;
.container {
max-width: 48em;
}
.section-main {
margin-bottom: 48px;
margin-inline-start: var(--icon-margin);
padding-inline-start: 24px;
}
.section-main:last-child {
@ -67,8 +41,8 @@ p {
.list-row > ul > li {
float: left;
width: 220px;
line-height: 1.5em;
width: 16em;
line-height: 2em;
margin-inline-start: 1em;
margin-bottom: 0;
}
@ -79,17 +53,15 @@ p {
.title {
background-image: url("chrome://browser/skin/privatebrowsing/private-browsing.svg");
background-size: 64px;
background-position: left, center;
font-weight: lighter;
line-height: 1.5em;
min-height: 64px;
margin-inline-start: 0;
padding-inline-start: calc(var(--icon-margin) + 24px);
background-position: left center;
background-size: 2em;
line-height: 2em;
margin-inline-start: calc(-2em - 10px);
padding-inline-start: calc(2em + 10px);
}
.title:dir(rtl) {
background-position: right, center;
background-position: right center;
}
.about-subheader {
@ -97,12 +69,13 @@ p {
align-items: center;
font-size: 1.5em;
font-weight: lighter;
min-height: 32px;
background-image: url("chrome://browser/skin/privatebrowsing/tracking-protection.svg");
background-repeat: no-repeat;
background-size: 32px;
margin-inline-start: calc(var(--icon-margin) - 32px);
padding-inline-start: 56px;
background-position: left center;
background-size: 1.5em;
line-height: 1.5em;
margin-inline-start: calc(-1.5em - 10px);
padding-inline-start: calc(1.5em + 10px);
}
.about-subheader:dir(rtl) {
@ -114,23 +87,17 @@ p {
}
.about-info {
font-size: .875em;
font-size: .9em;
}
.tpTitle {
margin-inline-end: 12px;
}
.private strong {
color: var(--color-grey-lightest);
font-weight: normal;
}
a.button {
padding: 5px 40px;
background-color: #381e59;
border: 1px solid #43256a;
border-radius: 4px;
padding: 3px 20px;
background-color: #8000d7;
border: 1px solid #6000a1;
text-decoration: none;
display: inline-block;
}
@ -155,69 +122,42 @@ a.button {
.toggle + .toggle-btn {
box-sizing: border-box;
cursor: pointer;
min-width: 60px;
height: 24px;
border-radius: 12px;
min-width: 42px;
height: 26px;
border-radius: 13px;
background-color: var(--color-grey);
border: 1px var(--color-grey) solid;
padding: 2px;
}
.toggle + .toggle-btn::after,
.toggle + .toggle-btn::before {
position: relative;
display: block;
content: "";
width: 19px;
height: 100%;
padding: 1px;
}
.toggle + .toggle-btn::after {
position: relative;
display: block;
content: "";
width: 24px;
height: 100%;
left: 0;
box-shadow: 0 0 1px 1px hsla(0, 0%, 0%, .1),
0 1px 0 hsla(0, 0%, 0%, .2);
border-radius: 50%;
background: white;
transition: left .2s ease;
}
.toggle + .toggle-btn::before {
float: left;
left: 9px;
visibility: hidden;
background-size: 16px;
background-repeat: no-repeat;
background-color: transparent;
background-image: url("chrome://browser/skin/privatebrowsing/check.svg");
}
.toggle + .toggle-btn:dir(rtl)::after {
left: auto;
right: 0;
transition-property: right;
}
.toggle + .toggle-btn:dir(rtl)::before {
float: right;
left: auto;
right: 9px;
}
.toggle:checked + .toggle-btn {
background: #3fc455;
border: 1px solid #269939;
background: #16da00;
}
.toggle:checked + .toggle-btn::after {
left: 35px;
left: 16px;
}
.toggle:checked + .toggle-btn:dir(rtl)::after {
right: 35px;
}
.toggle:checked + .toggle-btn::before {
visibility: visible;
left: auto;
right: 16px;
}
.toggle:-moz-focusring + .toggle-btn {

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<path fill="#fff" d="M30.057,9.752L15.9,23.909h0l-4.044,4.045-4.045-4.045h0l-6.068-6.067,4.045-4.045,6.068,6.067L26.012,5.707Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 475 B

View File

@ -175,7 +175,8 @@ function waitForSources(dbg, ...sources) {
sources.map(url => {
function sourceExists(state) {
return getSources(state).some(s => {
return s.get("url").includes(url);
let u = s.get("url");
return u && u.includes(url);
});
}
@ -212,8 +213,9 @@ function assertPausedLocation(dbg, source, line) {
is(location.get("line"), line);
// Check the debug line
let cm = dbg.win.document.querySelector(".CodeMirror").CodeMirror;
ok(
dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
"Line is highlighted as paused"
);
}
@ -356,7 +358,10 @@ function findSource(dbg, url) {
}
const sources = dbg.selectors.getSources(dbg.getState());
const source = sources.find(s => s.get("url").includes(url));
const source = sources.find(s => {
let u = s.get("url");
return u && u.includes(url);
});
if (!source) {
throw new Error("Unable to find source: " + url);

View File

@ -23,6 +23,7 @@ support-files =
browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
browser_toolbox_options_enable_serviceworkers_testing.html
serviceworker.js
test_browser_toolbox_debugger.js
[browser_browser_toolbox.js]
[browser_browser_toolbox_debugger.js]

View File

@ -2,10 +2,19 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// This test asserts that the new debugger works from the browser toolbox process
// Its pass a big piece of Javascript string to the browser toolbox process via
// MOZ_TOOLBOX_TEST_SCRIPT env variable. It does that as test resources fetched from
// chrome://mochitests/ package isn't available from browser toolbox process.
// On debug test runner, it takes about 50s to run the test.
requestLongerTimeout(4);
const { setInterval, clearInterval } = require("sdk/timers");
const { fetch } = require("devtools/shared/DevToolsUtils");
const debuggerHeadURL = CHROME_URL_ROOT + "../../debugger/new/test/mochitest/head.js";
const testScriptURL = CHROME_URL_ROOT + "test_browser_toolbox_debugger.js";
add_task(function* runTest() {
yield new Promise(done => {
@ -41,73 +50,64 @@ add_task(function* runTest() {
// which lives in another process. So do not try to use any scope variable!
let env = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
let testScript = function () {
const { Task } = Components.utils.import("resource://gre/modules/Task.jsm", {});
dump("Opening the browser toolbox and debugger panel\n");
let window, document;
let testUrl = "http://mozilla.org/browser-toolbox-test.js";
Task.spawn(function* () {
dump("Waiting for debugger load\n");
let panel = yield toolbox.selectTool("jsdebugger");
let window = panel.panelWin;
let document = window.document;
yield window.once(window.EVENTS.SOURCE_SHOWN);
dump("Loaded, selecting the test script to debug\n");
let item = document.querySelector(`.dbg-source-item[tooltiptext="${testUrl}"]`);
let onSourceShown = window.once(window.EVENTS.SOURCE_SHOWN);
item.click();
yield onSourceShown;
dump("Selected, setting a breakpoint\n");
let { Sources, editor } = window.DebuggerView;
let onBreak = window.once(window.EVENTS.FETCHED_SCOPES);
editor.emit("gutterClick", 1);
yield onBreak;
dump("Paused, asserting breakpoint position\n");
let url = Sources.selectedItem.attachment.source.url;
if (url != testUrl) {
throw new Error("Breaking on unexpected script: " + url);
// First inject a very minimal head, with simplest assertion methods
// and very common globals
let testHead = (function () {
const info = msg => dump(msg + "\n");
const is = (a, b, description) => {
let msg = "'" + JSON.stringify(a) + "' is equal to '" + JSON.stringify(b) + "'";
if (description) {
msg += " - " + description;
}
let cursor = editor.getCursor();
if (cursor.line != 1) {
throw new Error("Breaking on unexpected line: " + cursor.line);
if (a !== b) {
msg = "FAILURE: " + msg;
dump(msg + "\n");
throw new Error(msg);
} else {
msg = "SUCCESS: " + msg;
dump(msg + "\n");
}
dump("Now, stepping over\n");
let stepOver = window.document.querySelector("#step-over");
let onFetchedScopes = window.once(window.EVENTS.FETCHED_SCOPES);
stepOver.click();
yield onFetchedScopes;
dump("Stepped, asserting step position\n");
url = Sources.selectedItem.attachment.source.url;
if (url != testUrl) {
throw new Error("Stepping on unexpected script: " + url);
}
cursor = editor.getCursor();
if (cursor.line != 2) {
throw new Error("Stepping on unexpected line: " + cursor.line);
}
dump("Resume script execution\n");
let resume = window.document.querySelector("#resume");
let onResume = toolbox.target.once("thread-resumed");
resume.click();
yield onResume;
dump("Close the browser toolbox\n");
toolbox.destroy();
}).catch(error => {
dump("Error while running code in the browser toolbox process:\n");
dump(error + "\n");
dump("stack:\n" + error.stack + "\n");
});
};
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
const ok = (a, description) => {
let msg = "'" + JSON.stringify(a) + "' is true";
if (description) {
msg += " - " + description;
}
if (!a) {
msg = "FAILURE: " + msg;
dump(msg + "\n");
throw new Error(msg);
} else {
msg = "SUCCESS: " + msg;
dump(msg + "\n");
}
};
const registerCleanupFunction = () => {};
const Cu = Components.utils;
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
}).toSource().replace(/^\(function \(\) \{|\}\)$/g, "");
// Stringify testHead's function and remove `(function {` prefix and `})` suffix
// to ensure inner symbols gets exposed to next pieces of code
// Then inject new debugger head file
let { content } = yield fetch(debuggerHeadURL);
let debuggerHead = content;
// We remove its import of shared-head, which isn't available in browser toolbox process
// And isn't needed thanks to testHead's symbols
debuggerHead = debuggerHead.replace(/Services.scriptloader.loadSubScript[^\)]*\);/, "");
// Finally, fetch the debugger test script that is going to be execute in the browser
// toolbox process
let testScript = (yield fetch(testScriptURL)).content;
let source =
"try {" + testHead + debuggerHead + testScript + "} catch (e) {" +
" dump('Exception: '+ e + ' at ' + e.fileName + ':' + " +
" e.lineNumber + '\\nStack: ' + e.stack + '\\n');" +
"}";
env.set("MOZ_TOOLBOX_TEST_SCRIPT", source);
registerCleanupFunction(() => {
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
});

View File

@ -0,0 +1,48 @@
/* global toolbox */
info(`START: ${new Error().lineNumber}`);
let testUrl = "http://mozilla.org/browser-toolbox-test.js";
Task.spawn(function* () {
Services.prefs.clearUserPref("devtools.debugger.tabs")
Services.prefs.clearUserPref("devtools.debugger.pending-selected-location")
info("Waiting for debugger load");
yield toolbox.selectTool("jsdebugger");
let dbg = createDebuggerContext(toolbox);
let window = dbg.win;
let document = window.document;
yield waitForSources(dbg, testUrl);
// yield waitForSourceCount(dbg, 6);
info("Loaded, selecting the test script to debug");
// First expand the domain
let domain = [...document.querySelectorAll(".tree-node")].find(node => {
return node.textContent == "mozilla.org";
});
let arrow = domain.querySelector(".arrow");
arrow.click();
let script = [...document.querySelectorAll(".tree-node")].find(node => {
return node.textContent.includes("browser-toolbox-test.js");
});
script = script.querySelector(".node");
script.click();
let onPaused = waitForPaused(dbg);
yield addBreakpoint(dbg, "browser-toolbox-test.js", 2);
yield onPaused;
assertPausedLocation(dbg, "browser-toolbox-test.js", 2);
yield stepIn(dbg);
assertPausedLocation(dbg, "browser-toolbox-test.js", 3);
yield resume(dbg);
info("Close the browser toolbox");
toolbox.destroy();
});

View File

@ -70,7 +70,7 @@ function setPrefDefaults() {
Services.prefs.setBoolPref("devtools.scratchpad.enabled", true);
// Bug 1225160 - Using source maps with browser debugging can lead to a crash
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
Services.prefs.setBoolPref("devtools.debugger.client-source-maps-enabled", true);
}
@ -157,16 +157,16 @@ function bindToolboxHandlers() {
}
function setupThreadListeners(panel) {
updateBadgeText(panel._controller.activeThread.state == "paused");
updateBadgeText(panel._selectors().getPause(panel._getState()));
let onPaused = updateBadgeText.bind(null, true);
let onResumed = updateBadgeText.bind(null, false);
panel.target.on("thread-paused", onPaused);
panel.target.on("thread-resumed", onResumed);
gToolbox.target.on("thread-paused", onPaused);
gToolbox.target.on("thread-resumed", onResumed);
panel.once("destroyed", () => {
panel.off("thread-paused", onPaused);
panel.off("thread-resumed", onResumed);
gToolbox.target.off("thread-paused", onPaused);
gToolbox.target.off("thread-resumed", onResumed);
});
}

View File

@ -72,6 +72,7 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
[browser_inspector_highlighter-cancel.js]
[browser_inspector_highlighter-comments.js]
[browser_inspector_highlighter-cssgrid_01.js]
[browser_inspector_highlighter-cssgrid_02.js]
[browser_inspector_highlighter-csstransform_01.js]
[browser_inspector_highlighter-csstransform_02.js]
[browser_inspector_highlighter-embed.js]

View File

@ -0,0 +1,44 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Test that grid layouts without items don't cause grid highlighter errors.
const TEST_URL = `
<style type='text/css'>
.grid {
display: grid;
grid-template-columns: 20px 20px;
grid-gap: 15px;
}
</style>
<div class="grid"></div>
`;
const HIGHLIGHTER_TYPE = "CssGridHighlighter";
add_task(function* () {
let {inspector, testActor} = yield openInspectorForURL(
"data:text/html;charset=utf-8," + encodeURIComponent(TEST_URL));
let front = inspector.inspector;
let highlighter = yield front.getHighlighterByType(HIGHLIGHTER_TYPE);
info("Try to show the highlighter on the grid container");
let node = yield getNodeFront(".grid", inspector);
yield highlighter.show(node);
let hidden = yield testActor.getHighlighterNodeAttribute(
"css-grid-canvas", "hidden", highlighter);
ok(!hidden, "The highlighter is visible");
info("Hiding the highlighter");
yield highlighter.hide();
hidden = yield testActor.getHighlighterNodeAttribute(
"css-grid-canvas", "hidden", highlighter);
ok(hidden, "The highlighter is hidden");
yield highlighter.finalize();
});

View File

@ -501,7 +501,7 @@ netmonitor.toolbar.cookies=Cookies
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
netmonitor.toolbar.setCookies=Set-Cookies
# LOCALIZATION NOTE (netmonitor.toolbar.cookies): This is the label displayed
# LOCALIZATION NOTE (netmonitor.toolbar.scheme): This is the label displayed
# in the network table toolbar, above the "scheme" column.
netmonitor.toolbar.scheme=Scheme

View File

@ -116,10 +116,6 @@ function processFlagFilter(type, value) {
}
}
function getSizeOrder(size) {
return Math.round(Math.log10(size));
}
function isFlagFilterMatch(item, { type, value, negative }) {
let match = true;
let { responseCookies = { cookies: [] } } = item;
@ -160,11 +156,11 @@ function isFlagFilterMatch(item, { type, value, negative }) {
if (item.fromCache) {
match = false;
} else {
match = getSizeOrder(value) === getSizeOrder(item.transferredSize);
match = isSizeMatch(value, item.transferredSize);
}
break;
case "size":
match = getSizeOrder(value) === getSizeOrder(item.contentSize);
match = isSizeMatch(value, item.contentSize);
break;
case "larger-than":
match = item.contentSize > value;
@ -216,6 +212,10 @@ function isFlagFilterMatch(item, { type, value, negative }) {
return match;
}
function isSizeMatch(value, size) {
return value >= (size - size / 10) && value < (size + size / 10);
}
function isTextFilterMatch({ url }, text) {
let lowerCaseUrl = url.toLowerCase();
let lowerCaseText = text.toLowerCase();

View File

@ -695,6 +695,18 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
return this.currentNode.getGridFragments().length > 0;
},
/**
* Is a given grid fragment valid? i.e. does it actually have tracks? In some cases, we
* may have a fragment that defines column tracks but doesn't have any rows (or vice
* versa). In which case we do not want to draw anything for that fragment.
*
* @param {Object} fragment
* @return {Boolean}
*/
isValidFragment(fragment) {
return fragment.cols.tracks.length && fragment.rows.tracks.length;
},
/**
* The AutoRefreshHighlighter's _hasMoved method returns true only if the
* element's quads have changed. Override it so it also returns true if the
@ -740,8 +752,7 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
// Start drawing the grid fragments.
for (let i = 0; i < this.gridData.length; i++) {
let fragment = this.gridData[i];
this.renderFragment(fragment);
this.renderFragment(this.gridData[i]);
}
// Display the grid area highlights if needed.
@ -1030,6 +1041,10 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
},
renderFragment(fragment) {
if (!this.isValidFragment(fragment)) {
return;
}
this.renderLines(fragment.cols, COLUMNS, "left", "top", "height",
this.getFirstRowLinePos(fragment),
this.getLastRowLinePos(fragment));

View File

@ -230,20 +230,9 @@ disconnectPrefixDesc=Parent prefix for imported commands
# commands removed.
disconnectReply=Removed %S commands.
# LOCALIZATION NOTE (globalDesc, globalWindowDesc, globalOutput): These
# strings describe the 'global' command and its parameters
globalDesc=Change the JS global
globalWindowDesc=The new window/global
globalOutput=JS global is now %S
# LOCALIZATION NOTE: These strings describe the 'clear' command
clearDesc=Clear the output area
# LOCALIZATION NOTE (langDesc, langOutput): These strings describe the 'lang'
# command and its parameters
langDesc=Enter commands in different languages
langOutput=You are now using %S
# LOCALIZATION NOTE (prefDesc, prefManual, prefListDesc, prefListManual,
# prefListSearchDesc, prefListSearchManual, prefShowDesc, prefShowManual,
# prefShowSettingDesc, prefShowSettingManual): These strings describe the
@ -292,12 +281,6 @@ prefOutputFilter=Filter
prefOutputName=Name
prefOutputValue=Value
# LOCALIZATION NOTE (introDesc, introManual): These strings describe the
# 'intro' command. The localization of 'Got it!' should be the same used in
# introTextGo.
introDesc=Show the opening message
introManual=Redisplay the message that is shown to new users until they click the Got it! button
# LOCALIZATION NOTE (introTextOpening3, introTextCommands, introTextKeys2,
# introTextF1Escape, introTextGo): These strings are displayed when the user
# first opens the developer toolbar to explain the command line, and is shown

View File

@ -21,6 +21,7 @@ interface nsIDocShellTreeItem;
interface nsIStructuredCloneContainer;
interface nsIBFCacheEntry;
interface nsIPrincipal;
interface nsISHistory;
%{C++
#include "nsRect.h"
@ -326,6 +327,11 @@ interface nsISHEntry : nsISupports
* if true == "manual", false == "auto".
*/
attribute boolean scrollRestorationIsManual;
/**
* Set the session history it belongs to. It's only set on root entries.
*/
[noscript] void setSHistory(in nsISHistory aSHistory);
};
[scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]

View File

@ -96,6 +96,16 @@ interface nsISHistoryInternal: nsISupports
*/
void evictAllContentViewers();
/**
* Add a BFCache entry to expiration tracker so it gets evicted on expiration.
*/
void addToExpirationTracker(in nsIBFCacheEntry aEntry);
/**
* Remove a BFCache entry from expiration tracker.
*/
void removeFromExpirationTracker(in nsIBFCacheEntry aEntry);
/**
* Remove dynamic entries found at given index.
*

View File

@ -5,17 +5,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsSHEntry.h"
#include <algorithm>
#include "nsDocShellEditorData.h"
#include "nsIContentViewer.h"
#include "nsIDocShellLoadInfo.h"
#include "nsIDocShellTreeItem.h"
#include "nsDocShellEditorData.h"
#include "nsSHEntryShared.h"
#include "nsILayoutHistoryState.h"
#include "nsIContentViewer.h"
#include "nsIStructuredCloneContainer.h"
#include "nsIInputStream.h"
#include "nsILayoutHistoryState.h"
#include "nsIStructuredCloneContainer.h"
#include "nsIURI.h"
#include "nsSHEntryShared.h"
#include "nsSHistory.h"
#include "mozilla/net/ReferrerPolicy.h"
#include <algorithm>
namespace dom = mozilla::dom;
@ -23,13 +27,13 @@ static uint32_t gEntryID = 0;
nsSHEntry::nsSHEntry()
: mShared(new nsSHEntryShared())
, mLoadReplace(false)
, mReferrerPolicy(mozilla::net::RP_Unset)
, mLoadType(0)
, mID(gEntryID++)
, mScrollPositionX(0)
, mScrollPositionY(0)
, mParent(nullptr)
, mLoadReplace(false)
, mURIWasModified(false)
, mIsSrcdocEntry(false)
, mScrollRestorationIsManual(false)
@ -40,7 +44,6 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
: mShared(aOther.mShared)
, mURI(aOther.mURI)
, mOriginalURI(aOther.mOriginalURI)
, mLoadReplace(aOther.mLoadReplace)
, mReferrerURI(aOther.mReferrerURI)
, mReferrerPolicy(aOther.mReferrerPolicy)
, mTitle(aOther.mTitle)
@ -50,12 +53,13 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
, mScrollPositionX(0) // XXX why not copy?
, mScrollPositionY(0) // XXX why not copy?
, mParent(aOther.mParent)
, mURIWasModified(aOther.mURIWasModified)
, mStateData(aOther.mStateData)
, mIsSrcdocEntry(aOther.mIsSrcdocEntry)
, mScrollRestorationIsManual(false)
, mSrcdocData(aOther.mSrcdocData)
, mBaseURI(aOther.mBaseURI)
, mLoadReplace(aOther.mLoadReplace)
, mURIWasModified(aOther.mURIWasModified)
, mIsSrcdocEntry(aOther.mIsSrcdocEntry)
, mScrollRestorationIsManual(false)
{
}
@ -958,3 +962,10 @@ nsSHEntry::SetLastTouched(uint32_t aLastTouched)
mShared->mLastTouched = aLastTouched;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetSHistory(nsISHistory* aSHistory)
{
mShared->mSHistory = do_GetWeakReference(aSHistory);
return NS_OK;
}

View File

@ -7,15 +7,13 @@
#ifndef nsSHEntry_h
#define nsSHEntry_h
// Helper Classes
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsString.h"
#include "mozilla/Attributes.h"
// Interfaces needed
#include "nsISHEntry.h"
#include "nsCOMPtr.h"
#include "nsISHContainer.h"
#include "nsISHEntry.h"
#include "nsString.h"
#include "mozilla/Attributes.h"
class nsSHEntryShared;
class nsIInputStream;
@ -49,7 +47,6 @@ private:
// See nsSHEntry.idl for comments on these members.
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIURI> mOriginalURI;
bool mLoadReplace;
nsCOMPtr<nsIURI> mReferrerURI;
uint32_t mReferrerPolicy;
nsString mTitle;
@ -60,12 +57,13 @@ private:
int32_t mScrollPositionY;
nsISHEntry* mParent;
nsCOMArray<nsISHEntry> mChildren;
bool mURIWasModified;
nsCOMPtr<nsIStructuredCloneContainer> mStateData;
bool mIsSrcdocEntry;
bool mScrollRestorationIsManual;
nsString mSrcdocData;
nsCOMPtr<nsIURI> mBaseURI;
bool mLoadReplace;
bool mURIWasModified;
bool mIsSrcdocEntry;
bool mScrollRestorationIsManual;
};
#endif /* nsSHEntry_h */

View File

@ -6,20 +6,21 @@
#include "nsSHEntryShared.h"
#include "nsIDOMDocument.h"
#include "nsISHistory.h"
#include "nsISHistoryInternal.h"
#include "nsIDocument.h"
#include "nsIWebNavigation.h"
#include "nsArray.h"
#include "nsDocShellEditorData.h"
#include "nsIContentViewer.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsDocShellEditorData.h"
#include "nsThreadUtils.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsILayoutHistoryState.h"
#include "nsISHistory.h"
#include "nsISHistoryInternal.h"
#include "nsIWebNavigation.h"
#include "nsThreadUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Preferences.h"
#include "nsArray.h"
namespace dom = mozilla::dom;
@ -29,78 +30,27 @@ uint64_t gSHEntrySharedID = 0;
} // namespace
#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout"
// Default this to time out unused content viewers after 30 minutes
#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase;
class HistoryTracker final : public HistoryTrackerBase
{
public:
explicit HistoryTracker(uint32_t aTimeout)
: HistoryTrackerBase(1000 * aTimeout / 2, "HistoryTracker")
{
}
protected:
virtual void NotifyExpired(nsSHEntryShared* aObj)
{
RemoveObject(aObj);
aObj->Expire();
}
};
static HistoryTracker* gHistoryTracker = nullptr;
void
nsSHEntryShared::EnsureHistoryTracker()
{
if (!gHistoryTracker) {
// nsExpirationTracker doesn't allow one to change the timer period,
// so just set it once when the history tracker is used for the first time.
gHistoryTracker = new HistoryTracker(
mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT));
}
}
void
nsSHEntryShared::Shutdown()
{
delete gHistoryTracker;
gHistoryTracker = nullptr;
}
nsSHEntryShared::nsSHEntryShared()
: mDocShellID({0})
, mLastTouched(0)
, mID(gSHEntrySharedID++)
, mViewerBounds(0, 0, 0, 0)
, mIsFrameNavigation(false)
, mSaveLayoutState(true)
, mSticky(true)
, mDynamicallyCreated(false)
, mLastTouched(0)
, mID(gSHEntrySharedID++)
, mExpired(false)
, mViewerBounds(0, 0, 0, 0)
{
}
nsSHEntryShared::~nsSHEntryShared()
{
RemoveFromExpirationTracker();
#ifdef DEBUG
if (gHistoryTracker) {
// Check that we're not still on track to expire. We shouldn't be, because
// we just removed ourselves!
nsExpirationTracker<nsSHEntryShared, 3>::Iterator iterator(gHistoryTracker);
nsSHEntryShared* elem;
while ((elem = iterator.Next()) != nullptr) {
NS_ASSERTION(elem != this, "Found dead entry still in the tracker!");
}
}
#endif
if (mContentViewer) {
RemoveFromBFCacheSync();
}
@ -131,8 +81,9 @@ nsSHEntryShared::Duplicate(nsSHEntryShared* aEntry)
void
nsSHEntryShared::RemoveFromExpirationTracker()
{
if (gHistoryTracker && GetExpirationState()->IsTracked()) {
gHistoryTracker->RemoveObject(this);
nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
if (shistory && GetExpirationState()->IsTracked()) {
shistory->RemoveFromExpirationTracker(this);
}
}
@ -173,34 +124,6 @@ nsSHEntryShared::DropPresentationState()
mEditorData = nullptr;
}
void
nsSHEntryShared::Expire()
{
// This entry has timed out. If we still have a content viewer, we need to
// evict it.
if (!mContentViewer) {
return;
}
nsCOMPtr<nsIDocShell> container;
mContentViewer->GetContainer(getter_AddRefs(container));
nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container);
if (!treeItem) {
return;
}
// We need to find the root DocShell since only that object has an
// SHistory and we need the SHistory to evict content viewers
nsCOMPtr<nsIDocShellTreeItem> root;
treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
nsCOMPtr<nsISHistory> history;
webNav->GetSessionHistory(getter_AddRefs(history));
nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
if (!historyInt) {
return;
}
historyInt->EvictExpiredContentViewerForEntry(this);
}
nsresult
nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer)
{
@ -214,8 +137,13 @@ nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer)
mContentViewer = aViewer;
if (mContentViewer) {
EnsureHistoryTracker();
gHistoryTracker->AddObject(this);
// mSHistory is only set for root entries, but in general bfcache only
// applies to root entries as well. BFCache for subframe navigation has been
// disabled since 2005 in bug 304860.
nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
if (shistory) {
shistory->AddToExpirationTracker(this);
}
nsCOMPtr<nsIDOMDocument> domDoc;
mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));

View File

@ -7,14 +7,16 @@
#ifndef nsSHEntryShared_h__
#define nsSHEntryShared_h__
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsExpirationTracker.h"
#include "nsIBFCacheEntry.h"
#include "nsIMutationObserver.h"
#include "nsExpirationTracker.h"
#include "nsRect.h"
#include "nsString.h"
#include "nsWeakPtr.h"
#include "mozilla/Attributes.h"
class nsSHEntry;
@ -53,12 +55,9 @@ private:
friend class nsSHEntry;
friend class HistoryTracker;
static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared* aEntry);
void RemoveFromExpirationTracker();
void Expire();
nsresult SyncPresentationState();
void DropPresentationState();
@ -73,10 +72,7 @@ private:
nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
nsCString mContentType;
bool mIsFrameNavigation;
bool mSaveLayoutState;
bool mSticky;
bool mDynamicallyCreated;
nsCOMPtr<nsISupports> mCacheKey;
uint32_t mLastTouched;
@ -86,12 +82,20 @@ private:
nsCOMPtr<nsIContentViewer> mContentViewer;
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
bool mExpired;
nsCOMPtr<nsISupports> mWindowState;
nsIntRect mViewerBounds;
nsCOMPtr<nsIMutableArray> mRefreshURIList;
nsExpirationState mExpirationState;
nsAutoPtr<nsDocShellEditorData> mEditorData;
nsWeakPtr mSHistory;
bool mIsFrameNavigation;
bool mSaveLayoutState;
bool mSticky;
bool mDynamicallyCreated;
// This flag is about necko cache, not bfcache.
bool mExpired;
};
#endif

View File

@ -8,8 +8,8 @@
#include "nsISHEntry.h"
nsSHTransaction::nsSHTransaction()
: mPersist(true)
, mPrev(nullptr)
: mPrev(nullptr)
, mPersist(true)
{
}

View File

@ -7,10 +7,7 @@
#ifndef nsSHTransaction_h
#define nsSHTransaction_h
// Helper Classes
#include "nsCOMPtr.h"
// Needed interfaces
#include "nsISHTransaction.h"
class nsISHEntry;
@ -27,11 +24,10 @@ protected:
virtual ~nsSHTransaction();
protected:
bool mPersist;
nsISHTransaction* mPrev; // Weak Reference
nsCOMPtr<nsISHTransaction> mNext;
nsCOMPtr<nsISHEntry> mSHEntry;
bool mPersist;
};
#endif /* nsSHTransaction_h */

View File

@ -5,41 +5,43 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsSHistory.h"
#include <algorithm>
// Helper Classes
#include "mozilla/Preferences.h"
#include "mozilla/StaticPtr.h"
// Interfaces Needed
#include "nsILayoutHistoryState.h"
#include "nsCOMArray.h"
#include "nsComponentManagerUtils.h"
#include "nsDocShell.h"
#include "nsIContentViewer.h"
#include "nsIDocShell.h"
#include "nsIDocShellLoadInfo.h"
#include "nsISHContainer.h"
#include "nsIDocShellTreeItem.h"
#include "nsIURI.h"
#include "nsIContentViewer.h"
#include "nsILayoutHistoryState.h"
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "nsISHContainer.h"
#include "nsISHEntry.h"
#include "nsISHistoryListener.h"
#include "nsISHTransaction.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsTArray.h"
#include "nsCOMArray.h"
#include "nsDocShell.h"
#include "prsystem.h"
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "nsISHEntry.h"
#include "nsISHTransaction.h"
#include "nsISHistoryListener.h"
#include "nsComponentManagerUtils.h"
#include "nsNetUtil.h"
// For calculating max history entries and max cachable contentviewers
#include "prsystem.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/TabGroup.h"
using namespace mozilla;
#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
#define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers"
#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout"
// Default this to time out unused content viewers after 30 minutes
#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
static const char* kObservedPrefs[] = {
PREF_SHISTORY_SIZE,
@ -233,10 +235,10 @@ nsSHistory::nsSHistory()
: mIndex(-1)
, mLength(0)
, mRequestedIndex(-1)
, mIsPartial(false)
, mGlobalIndexOffset(0)
, mEntriesInFollowingPartialHistories(0)
, mRootDocShell(nullptr)
, mIsPartial(false)
{
// Add this new SHistory object to the list
gSHistoryList.insertBack(this);
@ -254,6 +256,7 @@ NS_INTERFACE_MAP_BEGIN(nsSHistory)
NS_INTERFACE_MAP_ENTRY(nsISHistory)
NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
// static
@ -382,6 +385,8 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist)
{
NS_ENSURE_ARG(aSHEntry);
aSHEntry->SetSHistory(this);
// If we have a root docshell, update the docshell id of the root shentry to
// match the id of that docshell
if (mRootDocShell) {
@ -1299,6 +1304,30 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aEntry)
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aEntry)
{
RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
if (!mHistoryTracker || !entry) {
return NS_ERROR_FAILURE;
}
mHistoryTracker->AddObject(entry);
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aEntry)
{
RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
if (!mHistoryTracker || !entry) {
return NS_ERROR_FAILURE;
}
mHistoryTracker->RemoveObject(entry);
return NS_OK;
}
// Evicts all content viewers in all history objects. This is very
// inefficient, because it requires a linear search through all SHistory
// objects for each viewer to be evicted. However, this method is called
@ -1899,6 +1928,23 @@ NS_IMETHODIMP
nsSHistory::SetRootDocShell(nsIDocShell* aDocShell)
{
mRootDocShell = aDocShell;
// Init mHistoryTracker on setting mRootDocShell so we can bind its event
// target to the tabGroup.
if (mRootDocShell) {
nsCOMPtr<nsPIDOMWindowOuter> win = mRootDocShell->GetWindow();
if (!win) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<mozilla::dom::TabGroup> tabGroup = win->TabGroup();
mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
this,
mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
tabGroup->EventTargetFor(mozilla::TaskCategory::Other));
}
return NS_OK;
}

View File

@ -8,15 +8,18 @@
#define nsSHistory_h
#include "nsCOMPtr.h"
#include "nsExpirationTracker.h"
#include "nsIPartialSHistoryListener.h"
#include "nsISHistory.h"
#include "nsISHistoryInternal.h"
#include "nsIWebNavigation.h"
#include "nsISimpleEnumerator.h"
#include "nsIWebNavigation.h"
#include "nsSHEntryShared.h"
#include "nsTObserverArray.h"
#include "nsWeakPtr.h"
#include "nsIPartialSHistoryListener.h"
#include "nsWeakReference.h"
#include "mozilla/LinkedList.h"
#include "mozilla/UniquePtr.h"
class nsIDocShell;
class nsSHEnumerator;
@ -27,9 +30,37 @@ class nsISHTransaction;
class nsSHistory final : public mozilla::LinkedListElement<nsSHistory>,
public nsISHistory,
public nsISHistoryInternal,
public nsIWebNavigation
public nsIWebNavigation,
public nsSupportsWeakReference
{
public:
// The timer based history tracker is used to evict bfcache on expiration.
class HistoryTracker final : public nsExpirationTracker<nsSHEntryShared, 3>
{
public:
explicit HistoryTracker(nsSHistory* aSHistory,
uint32_t aTimeout,
nsIEventTarget* aEventTarget)
: nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker", aEventTarget)
{
MOZ_ASSERT(aSHistory);
mSHistory = aSHistory;
}
protected:
virtual void NotifyExpired(nsSHEntryShared* aObj)
{
RemoveObject(aObj);
mSHistory->EvictExpiredContentViewerForEntry(aObj);
}
private:
// HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker
// so it's safe to use raw pointer here.
nsSHistory* mSHistory;
};
nsSHistory();
NS_DECL_ISUPPORTS
NS_DECL_NSISHISTORY
@ -85,14 +116,14 @@ private:
// otherwise comparison is done to aIndex - 1.
bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
// Track all bfcache entries and evict on expiration.
mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
nsCOMPtr<nsISHTransaction> mListRoot;
int32_t mIndex;
int32_t mLength;
int32_t mRequestedIndex;
// Set to true if attached to a grouped session history.
bool mIsPartial;
// The number of entries before this session history object.
int32_t mGlobalIndexOffset;
@ -108,6 +139,9 @@ private:
// Weak reference. Do not refcount this.
nsIDocShell* mRootDocShell;
// Set to true if attached to a grouped session history.
bool mIsPartial;
// Max viewers allowed total, across all SHistory objects
static int32_t sHistoryMaxTotalViewers;
};

View File

@ -49,6 +49,7 @@ support-files =
[browser_bug1206879.js]
[browser_bug1309900_crossProcessHistoryNavigation.js]
[browser_bug1347823.js]
[browser_bug134911.js]
[browser_bug234628-1.js]
[browser_bug234628-10.js]

View File

@ -0,0 +1,67 @@
/**
* Test that session history's expiration tracker would remove bfcache on
* expiration.
*/
// With bfcache not expired.
add_task(async function testValidCache() {
// Make an unrealistic large timeout.
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionhistory.contentViewerTimeout", 86400]]
});
await BrowserTestUtils.withNewTab(
{gBrowser, url: "data:text/html;charset=utf-8,page1"},
async function(browser) {
// Make a simple modification for bfcache testing.
await ContentTask.spawn(browser, null, () => {
content.document.body.textContent = "modified";
});
// Load a random page.
BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,page2");
await BrowserTestUtils.browserLoaded(browser);
// Go back and verify text content.
let awaitPageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
browser.goBack();
await awaitPageShow;
await ContentTask.spawn(browser, null, () => {
is(content.document.body.textContent, "modified");
});
});
});
// With bfcache expired.
add_task(async function testExpiredCache() {
// Make bfcache timeout in 1 sec.
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionhistory.contentViewerTimeout", 1]]
});
await BrowserTestUtils.withNewTab(
{gBrowser, url: "data:text/html;charset=utf-8,page1"},
async function(browser) {
// Make a simple modification for bfcache testing.
await ContentTask.spawn(browser, null, () => {
content.document.body.textContent = "modified";
});
// Load a random page.
BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,page2");
await BrowserTestUtils.browserLoaded(browser);
// Wait for 3 times of expiration timeout, hopefully it's evicted...
await new Promise(resolve => {
setTimeout(resolve, 3000);
});
// Go back and verify text content.
let awaitPageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
browser.goBack();
await awaitPageShow;
await ContentTask.spawn(browser, null, () => {
is(content.document.body.textContent, "page1");
});
});
});

View File

@ -2690,10 +2690,12 @@ nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement,
return NS_ERROR_ILLEGAL_VALUE;
}
nsIPresShell* shell = element->GetComposedDoc()->GetShell();
RefPtr<nsStyleContext> styleContext = shell
? nsComputedDOMStyle::GetStyleContext(element, nullptr, shell)
: nullptr;
RefPtr<nsStyleContext> styleContext;
nsIDocument* doc = element->GetComposedDoc();
if (doc && doc->GetShell()) {
styleContext =
nsComputedDOMStyle::GetStyleContext(element, nullptr, doc->GetShell());
}
*aResult = v1.ComputeDistance(property, v2, styleContext);
return NS_OK;
}

View File

@ -107,7 +107,7 @@ CaptureStreamTestHelper.prototype = {
*/
isOpaquePixelNot: function(px, refColor, threshold) {
px[3] = refColor.data[3];
return h.isPixelNot(px, refColor, threshold);
return this.isPixelNot(px, refColor, threshold);
},
/*

View File

@ -45,8 +45,8 @@ GamepadEventChannelParent::GamepadEventChannelParent()
RefPtr<GamepadPlatformService> service =
GamepadPlatformService::GetParentService();
MOZ_ASSERT(service);
service->AddChannelParent(this);
mBackgroundThread = NS_GetCurrentThread();
service->AddChannelParent(this);
}
mozilla::ipc::IPCResult

View File

@ -5249,6 +5249,10 @@ void HTMLMediaElement::DecodeError(const MediaResult& aError)
AudioTracks()->EmptyTracks();
VideoTracks()->EmptyTracks();
if (mIsLoadingFromSourceChildren) {
if (mDecoder) {
// Shut down the exiting decoder before loading the next source child.
ShutdownDecoder();
}
mErrorSink->ResetError();
if (mSourceLoadCandidate) {
DispatchAsyncSourceError(mSourceLoadCandidate);

View File

@ -2658,20 +2658,14 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
INIT_CANONICAL(mCurrentPosition, TimeUnit::Zero()),
INIT_CANONICAL(mPlaybackOffset, 0),
INIT_CANONICAL(mIsAudioDataAudible, false)
#ifdef XP_WIN
, mShouldUseHiResTimers(Preferences::GetBool("media.hi-res-timers.enabled", true))
#endif
{
MOZ_COUNT_CTOR(MediaDecoderStateMachine);
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
InitVideoQueuePrefs();
#ifdef XP_WIN
// Ensure high precision timers are enabled on Windows, otherwise the state
// machine isn't woken up at reliable intervals to set the next frame, and we
// drop frames while painting. Note that multiple calls to this function
// per-process is OK, provided each call is matched by a corresponding
// timeEndPeriod() call.
timeBeginPeriod(1);
#endif
}
#undef INIT_WATCHABLE
@ -2684,7 +2678,7 @@ MediaDecoderStateMachine::~MediaDecoderStateMachine()
MOZ_COUNT_DTOR(MediaDecoderStateMachine);
#ifdef XP_WIN
timeEndPeriod(1);
MOZ_ASSERT(!mHiResTimersRequested);
#endif
}
@ -2895,6 +2889,12 @@ MediaDecoderStateMachine::StopPlayback()
if (IsPlaying()) {
mMediaSink->SetPlaying(false);
MOZ_ASSERT(!IsPlaying());
#ifdef XP_WIN
if (mHiResTimersRequested) {
mHiResTimersRequested = false;
timeEndPeriod(1);
}
#endif
}
}
@ -2918,6 +2918,20 @@ void MediaDecoderStateMachine::MaybeStartPlayback()
mOnPlaybackEvent.Notify(MediaEventType::PlaybackStarted);
StartMediaSink();
#ifdef XP_WIN
if (!mHiResTimersRequested && mShouldUseHiResTimers) {
mHiResTimersRequested = true;
// Ensure high precision timers are enabled on Windows, otherwise the state
// machine isn't woken up at reliable intervals to set the next frame, and we
// drop frames while painting. Note that each call must be matched by a
// corresponding timeEndPeriod() call. Enabling high precision timers causes
// the CPU to wake up more frequently on Windows 7 and earlier, which causes
// more CPU load and battery use. So we only enable high precision timers
// when we're actually playing.
timeBeginPeriod(1);
}
#endif
if (!IsPlaying()) {
mMediaSink->SetPlaying(true);
MOZ_ASSERT(IsPlaying());

View File

@ -779,6 +779,17 @@ public:
{
return &mIsAudioDataAudible;
}
#ifdef XP_WIN
// Whether we've called timeBeginPeriod(1) to request high resolution
// timers. We request high resolution timers when playback starts, and
// turn them off when playback is paused. Enabling high resolution
// timers can cause higher CPU usage and battery drain on Windows 7.
bool mHiResTimersRequested = false;
// Whether we should enable high resolution timers. This is initialized at
// MDSM construction, and mirrors the value of media.hi-res-timers.enabled.
const bool mShouldUseHiResTimers;
#endif
};
} // namespace mozilla

View File

@ -242,6 +242,17 @@ public:
return bytes.forget();
}
already_AddRefed<MediaByteBuffer> CachedReadAt(int64_t aOffset, uint32_t aCount)
{
RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
bool ok = bytes->SetLength(aCount, fallible);
NS_ENSURE_TRUE(ok, nullptr);
char* curr = reinterpret_cast<char*>(bytes->Elements());
nsresult rv = ReadFromCache(curr, aOffset, aCount);
NS_ENSURE_SUCCESS(rv, nullptr);
return bytes.forget();
}
// Report the current offset in bytes from the start of the stream.
// This is used to approximate where we currently are in the playback of a
// media.

View File

@ -725,7 +725,7 @@ GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
}
// DOM JSON parsing needs to run on the main thread.
return InvokeAsync<nsString&&>(
return InvokeAsync(
mMainThread, this, __func__,
&GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
}

View File

@ -642,7 +642,7 @@ GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(const nsAString& aDirecto
nsString dir(aDirectory);
RefPtr<GeckoMediaPluginServiceParent> self = this;
return InvokeAsync<nsString&&>(
return InvokeAsync(
thread, this, __func__,
&GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
->Then(

View File

@ -417,7 +417,7 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
}
StartUpdating();
mTrackBuffersManager->AppendData(data, mCurrentAttributes)
mTrackBuffersManager->AppendData(data.forget(), mCurrentAttributes)
->Then(mAbstractMainThread, __func__, this,
&SourceBuffer::AppendDataCompletedWithSuccess,
&SourceBuffer::AppendDataErrored)

View File

@ -47,7 +47,7 @@ protected:
class AppendBufferTask : public SourceBufferTask {
public:
AppendBufferTask(MediaByteBuffer* aData,
AppendBufferTask(already_AddRefed<MediaByteBuffer> aData,
const SourceBufferAttributes& aAttributes)
: mBuffer(aData)
, mAttributes(aAttributes)

View File

@ -116,24 +116,24 @@ TrackBuffersManager::~TrackBuffersManager()
}
RefPtr<TrackBuffersManager::AppendPromise>
TrackBuffersManager::AppendData(MediaByteBuffer* aData,
TrackBuffersManager::AppendData(already_AddRefed<MediaByteBuffer> aData,
const SourceBufferAttributes& aAttributes)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Appending %" PRIuSIZE " bytes", aData->Length());
RefPtr<MediaByteBuffer> data(aData);
MSE_DEBUG("Appending %" PRIuSIZE " bytes", data->Length());
mEnded = false;
return InvokeAsync<RefPtr<MediaByteBuffer>, SourceBufferAttributes&&>(
GetTaskQueue(), this, __func__,
&TrackBuffersManager::DoAppendData, aData, aAttributes);
return InvokeAsync(GetTaskQueue(), this, __func__,
&TrackBuffersManager::DoAppendData, data.forget(), aAttributes);
}
RefPtr<TrackBuffersManager::AppendPromise>
TrackBuffersManager::DoAppendData(MediaByteBuffer* aData,
TrackBuffersManager::DoAppendData(already_AddRefed<MediaByteBuffer> aData,
const SourceBufferAttributes& aAttributes)
{
RefPtr<AppendBufferTask> task = new AppendBufferTask(aData, aAttributes);
RefPtr<AppendBufferTask> task = new AppendBufferTask(Move(aData), aAttributes);
RefPtr<AppendPromise> p = task->mPromise.Ensure(__func__);
QueueTask(task);

View File

@ -98,7 +98,7 @@ public:
// Buffer Append Algorithm
// 3.5.5 Buffer Append Algorithm.
// http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
RefPtr<AppendPromise> AppendData(MediaByteBuffer* aData,
RefPtr<AppendPromise> AppendData(already_AddRefed<MediaByteBuffer> aData,
const SourceBufferAttributes& aAttributes);
// Queue a task to abort any pending AppendData.
@ -174,7 +174,7 @@ private:
friend class MediaSourceDemuxer;
~TrackBuffersManager();
// All following functions run on the taskqueue.
RefPtr<AppendPromise> DoAppendData(MediaByteBuffer* aData,
RefPtr<AppendPromise> DoAppendData(already_AddRefed<MediaByteBuffer> aData,
const SourceBufferAttributes& aAttributes);
void ScheduleSegmentParserLoop();
void SegmentParserLoop();

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script>
var htmlAudio = new Audio(URL.createObjectURL(new window.MediaSource()));
(new window.AudioContext("ringer")).createMediaElementSource(htmlAudio);
(new window.AudioContext("alarm")).createMediaElementSource(htmlAudio);
</script>
</head>
</html>

View File

@ -81,8 +81,6 @@ load 1157994.html
load 1158427.html
load 1185176.html
load 1185192.html
load 1223670.html
load 1228484.html
load 1304948.html
load 1319486.html
load 1291702.html

View File

@ -908,55 +908,94 @@ AudioStreamHelper.prototype = {
}
}
function VideoStreamHelper() {
class VideoFrameEmitter {
constructor(color1, color2) {
this._helper = new CaptureStreamTestHelper2D(50,50);
this._canvas = this._helper.createAndAppendElement('canvas', 'source_canvas');
this._color1 = color1 ? color1 : this._helper.green;
this._color2 = color2 ? color2 : this._helper.red;
// Make sure this is initted
this._helper.drawColor(this._canvas, this._helper.green);
this._stream = this._canvas.captureStream(10);
this._helper.drawColor(this._canvas, this._color1);
this._stream = this._canvas.captureStream();
this._started = false;
}
VideoStreamHelper.prototype = {
stream: function() {
stream() {
return this._stream;
},
}
startCapturingFrames: function() {
var i = 0;
var helper = this;
return setInterval(function() {
start() {
if (this._started) {
return;
}
let i = 0;
this._started = true;
this._intervalId = setInterval(() => {
try {
helper._helper.drawColor(helper._canvas,
i ? helper._helper.green : helper._helper.red);
this._helper.drawColor(this._canvas, i ? this._color1: this._color2);
i = 1 - i;
helper._stream.requestFrame();
} catch (e) {
// ignore; stream might have shut down, and we don't bother clearing
// the setInterval.
}
}, 500);
},
}
waitForFrames: function(canvas, timeout_value) {
var intervalId = this.startCapturingFrames();
timeout_value = timeout_value || 8000;
stop() {
if (this._started) {
clearInterval(this._intervalId);
this._started = false;
}
}
}
return addFinallyToPromise(timeout(
Promise.all([
this._helper.waitForPixelColor(canvas, this._helper.green, 128,
canvas.id + " should become green"),
this._helper.waitForPixelColor(canvas, this._helper.red, 128,
canvas.id + " should become red")
]),
timeout_value,
"Timed out waiting for frames")).finally(() => clearInterval(intervalId));
},
class VideoStreamHelper {
constructor() {
this._helper = new CaptureStreamTestHelper2D(50,50);
}
verifyNoFrames: function(canvas) {
return this.waitForFrames(canvas).then(
() => ok(false, "Color should not change"),
() => ok(true, "Color should not change")
);
checkHasFrame(video, offsetX, offsetY, threshold) {
const h = this._helper;
return h.waitForPixel(video, offsetX, offsetY, px => {
let result = h.isOpaquePixelNot(px, h.black, threshold);
info("Checking that we have a frame, got [" +
Array.slice(px) + "]. Ref=[" +
Array.slice(h.black.data) + "]. Threshold=" + threshold +
". Pass=" + result);
return result;
});
}
async checkVideoPlaying(video, offsetX, offsetY, threshold) {
const h = this._helper;
await this.checkHasFrame(video, offsetX, offsetY, threshold);
let startPixel = { data: h.getPixel(video, offsetX, offsetY)
, name: "startcolor"
};
return h.waitForPixel(video, offsetX, offsetY, px => {
let result = h.isPixelNot(px, startPixel, threshold)
info("Checking playing, [" +
Array.slice(px) + "] vs [" + Array.slice(startPixel.data) +
"]. Threshold=" + threshold + " Pass=" + result);
return result;
});
}
async checkVideoPaused(video, offsetX, offsetY, threshold, timeout) {
const h = this._helper;
await this.checkHasFrame(video, offsetX, offsetY, threshold);
let startPixel = { data: h.getPixel(video, offsetX, offsetY)
, name: "startcolor"
};
const changed = await h.waitForPixel(video, offsetX, offsetY, px => {
let result = h.isOpaquePixelNot(px, startPixel, threshold);
info("Checking paused, [" +
Array.slice(px) + "] vs [" + Array.slice(startPixel.data) +
"]. Threshold=" + threshold + " Pass=" + result);
return result;
}, timeout);
ok(!changed, "Frame shouldn't change within " + timeout / 1000 + " seconds.");
}
}

View File

@ -1004,13 +1004,13 @@ PeerConnectionWrapper.prototype = {
return Promise.all(constraintsList.map(constraints => {
return getUserMedia(constraints).then(stream => {
if (constraints.audio) {
stream.getAudioTracks().map(track => {
stream.getAudioTracks().forEach(track => {
info(this + " gUM local stream " + stream.id +
" with audio track " + track.id);
});
}
if (constraints.video) {
stream.getVideoTracks().map(track => {
stream.getVideoTracks().forEach(track => {
info(this + " gUM local stream " + stream.id +
" with video track " + track.id);
});

View File

@ -2,7 +2,6 @@
<html>
<head>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
<script type="application/javascript" src="head.js"></script>
</head>
<body>
<pre id="test">

View File

@ -2,7 +2,6 @@
<html>
<head>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
<script type="application/javascript" src="head.js"></script>
</head>
<body>
<pre id="test">

View File

@ -3,7 +3,6 @@
<head>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
<script type="application/javascript" src="head.js"></script>
</head>
<body>
<pre id="test">
@ -24,43 +23,11 @@ const offsetX = 20;
const offsetY = 20;
const threshold = 16;
const pausedTimeout = 1000;
const h = new CaptureStreamTestHelper2D(50, 50);
var checkHasFrame = video => h.waitForPixel(video, offsetX, offsetY, px => {
let result = h.isOpaquePixelNot(px, h.black, threshold);
info("Checking that we have a frame, got [" +
Array.slice(px) + "]. Pass=" + result);
return result;
});
var checkVideoPlaying = video => checkHasFrame(video)
.then(() => {
let startPixel = { data: h.getPixel(video, offsetX, offsetY)
, name: "startcolor"
};
return h.waitForPixel(video, offsetX, offsetY, px => {
let result = h.isPixelNot(px, startPixel, threshold)
info("Checking playing, [" + Array.slice(px) + "] vs [" +
Array.slice(startPixel.data) + "]. Pass=" + result);
return result;
});
});
var checkVideoPaused = video => checkHasFrame(video)
.then(() => {
let startPixel = { data: h.getPixel(video, offsetX, offsetY)
, name: "startcolor"
};
return h.waitForPixel(video, offsetX, offsetY, px => {
let result = h.isOpaquePixelNot(px, startPixel, threshold);
info("Checking paused, [" + Array.slice(px) + "] vs [" +
Array.slice(startPixel.data) + "]. Pass=" + result);
return result;
}, pausedTimeout);
}).then(result => ok(!result, "Frame shouldn't change within " + pausedTimeout / 1000 + " seconds."));
let h;
runTest(() => getUserMedia({video: true, fake: true})
.then(stream => {
h = new VideoStreamHelper();
gUMVideoElement =
createMediaElement("video", "gUMVideo");
gUMVideoElement.srcObject = stream;
@ -80,43 +47,45 @@ runTest(() => getUserMedia({video: true, fake: true})
let osc = createOscillatorStream(new AudioContext(), 1000);
captureStreamElement.srcObject.addTrack(osc.getTracks()[0]);
return checkVideoPlaying(captureStreamElement);
return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
})
.then(() => {
info("Video flowing. Pausing.");
gUMVideoElement.pause();
return checkVideoPaused(captureStreamElement);
return h.checkVideoPaused(captureStreamElement, 10, 10, 16, pausedTimeout);
})
.then(() => {
info("Video stopped flowing. Playing.");
gUMVideoElement.play();
return checkVideoPlaying(captureStreamElement);
return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
})
.then(() => {
info("Video flowing. Removing source.");
var stream = gUMVideoElement.srcObject;
gUMVideoElement.srcObject = null;
return checkVideoPaused(captureStreamElement).then(() => stream);
return h.checkVideoPaused(captureStreamElement, 10, 10, 16, pausedTimeout)
.then(() => stream);
})
.then(stream => {
info("Video stopped flowing. Setting source.");
gUMVideoElement.srcObject = stream;
return checkVideoPlaying(captureStreamElement);
return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
})
.then(() => {
info("Video flowing. Changing source by track manipulation. Remove first.");
var track = gUMVideoElement.srcObject.getTracks()[0];
gUMVideoElement.srcObject.removeTrack(track);
return checkVideoPaused(captureStreamElement).then(() => track);
return h.checkVideoPaused(captureStreamElement, 10, 10, 16, pausedTimeout)
.then(() => track);
})
.then(track => {
info("Video paused. Changing source by track manipulation. Add first.");
gUMVideoElement.srcObject.addTrack(track);
gUMVideoElement.play();
return checkVideoPlaying(captureStreamElement);
return h.checkVideoPlaying(captureStreamElement, 10, 10, 16);
})
.then(() => {
gUMVideoElement.srcObject.getTracks().forEach(t => t.stop());

View File

@ -1,7 +1,6 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="head.js"></script>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
</head>
<body>

View File

@ -11,9 +11,8 @@
title: "Renegotiation: add second audio stream"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
@ -21,10 +20,22 @@
[{audio: true}]);
return test.pcLocal.getAllUserMedia([{audio: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
// We test both tracks to avoid an ordering problem
is(test.pcRemote._pc.getReceivers().length, 2,
"pcRemote should have two receivers");
return Promise.all(test.pcRemote._pc.getReceivers().map(r => {
const analyser = new AudioStreamAnalyser(
new AudioContext(), new MediaStream([r.track]));
const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
}));
},
]
);
// TODO(bug 1093835): figure out how to verify if media flows through the new stream
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
});

View File

@ -11,11 +11,9 @@
title: "Renegotiation: add second audio stream, no bundle"
});
var test;
runNetworkTest(function (options) {
options = options || { };
runNetworkTest(function (options = {}) {
options.bundle = false;
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
@ -29,6 +27,19 @@
function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
test.pcRemote.expectIceChecking();
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
// We test both tracks to avoid an ordering problem
is(test.pcRemote._pc.getReceivers().length, 2,
"pcRemote should have two receivers");
return Promise.all(test.pcRemote._pc.getReceivers().map(r => {
const analyser = new AudioStreamAnalyser(
new AudioContext(), new MediaStream([r.track]));
const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
}));
},
]
);

View File

@ -2,6 +2,7 @@
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
@ -11,21 +12,30 @@
title: "Renegotiation: add second video stream"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{video: true}, {video: true}],
[{video: true}]);
return test.pcLocal.getAllUserMedia([{video: true}]);
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
},
],
[
function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
const h = new VideoStreamHelper();
is(test.pcRemote.remoteMediaElements.length, 2,
"Should have two remote media elements after renegotiation");
return Promise.all(test.pcRemote.remoteMediaElements.map(video =>
h.checkVideoPlaying(video, 10, 10, 16)));
},
]
);
// TODO(bug 1093835): figure out how to verify if media flows through the new stream
test.setMediaConstraints([{video: true}], [{video: true}]);
test.setMediaConstraints([{video: true, fake: true}], [{video: true}]);
test.run();
});
</script>

View File

@ -2,6 +2,7 @@
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
@ -11,11 +12,9 @@
title: "Renegotiation: add second video stream, no bundle"
});
var test;
runNetworkTest(function (options) {
options = options || { };
runNetworkTest(function (options = {}) {
options.bundle = false;
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
@ -24,16 +23,26 @@
// Since this is a NoBundle variant, adding a track will cause us to
// go back to checking.
test.pcLocal.expectIceChecking();
return test.pcLocal.getAllUserMedia([{video: true}]);
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
},
function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
test.pcRemote.expectIceChecking();
},
],
[
function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
const h = new VideoStreamHelper();
is(test.pcRemote.remoteMediaElements.length, 2,
"Should have two remote media elements after renegotiation");
return Promise.all(test.pcRemote.remoteMediaElements.map(video =>
h.checkVideoPlaying(video, 10, 10, 16)));
},
]
);
// TODO(bug 1093835): figure out how to verify if media flows through the new stream
test.setMediaConstraints([{video: true}], [{video: true}]);
test.setMediaConstraints([{video: true, fake: true}], [{video: true}]);
test.run();
});
</script>

View File

@ -1,7 +1,6 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="head.js"></script>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>

View File

@ -11,20 +11,42 @@
title: "Renegotiation: remove audio track"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
let receivedTrack, analyser, freq;
addRenegotiation(test.chain,
[
function PC_REMOTE_SETUP_ANALYSER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver before renegotiation");
receivedTrack = test.pcRemote._pc.getReceivers()[0].track;
is(receivedTrack.readyState, "live",
"The received track should be live");
analyser = new AudioStreamAnalyser(
new AudioContext(), new MediaStream([receivedTrack]));
freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
},
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
test.setOfferOptions({ offerToReceiveAudio: true });
return test.pcLocal.removeSender(0);
},
],
[
function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
is(test.pcRemote._pc.getReceivers().length, 0,
"pcRemote should have no more receivers");
is(receivedTrack.readyState, "ended",
"The received track should have ended");
return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
},
]
);
// TODO(bug 1093835): figure out how to verify that media stopped flowing from pcLocal
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
});

View File

@ -11,11 +11,16 @@
title: "Renegotiation: remove then add audio track"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
let originalTrack;
addRenegotiation(test.chain,
[
function PC_REMOTE_FIND_RECEIVER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver");
originalTrack = test.pcRemote._pc.getReceivers()[0].track;
},
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
return test.pcLocal.removeSender(0);
},
@ -26,10 +31,22 @@
test.pcLocal.disableRtpCountChecking = true;
return test.pcLocal.getAllUserMedia([{audio: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should still have one receiver");
const track = test.pcRemote._pc.getReceivers()[0].track;
isnot(originalTrack.id, track.id, "Receiver should have changed");
const analyser = new AudioStreamAnalyser(
new AudioContext(), new MediaStream([track]));
const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
},
]
);
// TODO(bug 1093835): figure out how to verify if media flows through the new stream
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
});

View File

@ -11,11 +11,16 @@
title: "Renegotiation: remove then add audio track"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
let originalTrack;
addRenegotiation(test.chain,
[
function PC_REMOTE_FIND_RECEIVER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver");
originalTrack = test.pcRemote._pc.getReceivers()[0].track;
},
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
// The new track's pipeline will start with a packet count of
// 0, but the remote side will keep its old pipeline and packet
@ -26,13 +31,25 @@
function PC_LOCAL_ADD_AUDIO_TRACK(test) {
return test.pcLocal.getAllUserMedia([{audio: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should still have one receiver");
const track = test.pcRemote._pc.getReceivers()[0].track;
isnot(originalTrack.id, track.id, "Receiver should have changed");
const analyser = new AudioStreamAnalyser(
new AudioContext(), new MediaStream([track]));
const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
},
]
);
test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
// TODO(bug 1093835): figure out how to verify if media flows through the new stream
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.run();
});

View File

@ -2,6 +2,7 @@
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
@ -11,25 +12,47 @@
title: "Renegotiation: remove then add video track"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
const helper = new VideoStreamHelper();
var originalTrack;
addRenegotiation(test.chain,
[
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
function PC_REMOTE_FIND_RECEIVER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver");
originalTrack = test.pcRemote._pc.getReceivers()[0].track;
},
function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
// The new track's pipeline will start with a packet count of
// 0, but the remote side will keep its old pipeline and packet
// count.
test.pcLocal.disableRtpCountChecking = true;
return test.pcLocal.removeSender(0);
},
function PC_LOCAL_ADD_AUDIO_TRACK(test) {
return test.pcLocal.getAllUserMedia([{video: true}]);
function PC_LOCAL_ADD_VIDEO_TRACK(test) {
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should still have one receiver");
const track = test.pcRemote._pc.getReceivers()[0].track;
isnot(originalTrack.id, track.id, "Receiver should have changed");
const vOriginal = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(originalTrack.id));
const vAdded = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(track.id));
ok(vOriginal.ended, "Original video element should have ended");
return helper.checkVideoPlaying(vAdded, 10, 10, 16);
},
]
);
// TODO(bug 1093835): figure out how to verify if media flows through the new stream
test.setMediaConstraints([{video: true}], [{video: true}]);
test.run();
});

View File

@ -2,6 +2,7 @@
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
@ -11,20 +12,43 @@
title: "Renegotiation: remove then add video track, no bundle"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
const helper = new VideoStreamHelper();
var originalTrack;
addRenegotiation(test.chain,
[
function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
function PC_REMOTE_FIND_RECEIVER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver");
originalTrack = test.pcRemote._pc.getReceivers()[0].track;
},
function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
// The new track's pipeline will start with a packet count of
// 0, but the remote side will keep its old pipeline and packet
// count.
test.pcLocal.disableRtpCountChecking = true;
return test.pcLocal.removeSender(0);
},
function PC_LOCAL_ADD_AUDIO_TRACK(test) {
return test.pcLocal.getAllUserMedia([{video: true}]);
function PC_LOCAL_ADD_VIDEO_TRACK(test) {
// Use fake:true here since the native fake device on linux doesn't
// change color as needed by checkVideoPlaying() below.
return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
},
],
[
function PC_REMOTE_CHECK_ADDED_TRACK(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should still have one receiver");
const track = test.pcRemote._pc.getReceivers()[0].track;
isnot(originalTrack.id, track.id, "Receiver should have changed");
const vOriginal = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(originalTrack.id));
const vAdded = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(track.id));
ok(vOriginal.ended, "Original video element should have ended");
return helper.checkVideoPlaying(vAdded, 10, 10, 16);
},
]
);
@ -32,7 +56,6 @@
test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
// TODO(bug 1093835): figure out how to verify if media flows through the new stream
test.setMediaConstraints([{video: true}], [{video: true}]);
test.run();
});

View File

@ -11,21 +11,41 @@
title: "Renegotiation: remove video track"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
let receivedTrack, element;
addRenegotiation(test.chain,
[
function PC_REMOTE_SETUP_HELPER(test) {
is(test.pcRemote._pc.getReceivers().length, 1,
"pcRemote should have one receiver before renegotiation");
receivedTrack = test.pcRemote._pc.getReceivers()[0].track;
is(receivedTrack.readyState, "live",
"The received track should be live");
element = createMediaElement("video", "pcRemoteReceivedVideo");
element.srcObject = new MediaStream([receivedTrack]);
return haveEvent(element, "loadeddata");
},
function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
test.setOfferOptions({ offerToReceiveVideo: true });
test.setMediaConstraints([], [{video: true}]);
return test.pcLocal.removeSender(0);
},
],
[
function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
is(test.pcRemote._pc.getReceivers().length, 0,
"pcRemote should have no more receivers");
is(receivedTrack.readyState, "ended",
"The received track should have ended");
is(element.ended, true,
"Element playing the removed track should have ended");
},
]
);
// TODO(bug 1093835): figure out how to verify that media stopped flowing from pcLocal
test.setMediaConstraints([{video: true}], [{video: true}]);
test.run();
});

View File

@ -2,6 +2,7 @@
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
@ -11,33 +12,74 @@
title: "Renegotiation: replaceTrack followed by adding a second video stream"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
const test = new PeerConnectionTest(options);
test.setMediaConstraints([{video:true}], [{video:true}]);
const helper = new VideoStreamHelper();
const emitter1 = new VideoFrameEmitter(CaptureStreamTestHelper.prototype.red,
CaptureStreamTestHelper.prototype.green);
const emitter2 = new VideoFrameEmitter(CaptureStreamTestHelper.prototype.blue,
CaptureStreamTestHelper.prototype.grey);
test.chain.replace("PC_LOCAL_GUM", [
function PC_LOCAL_ADDTRACK(test) {
test.pcLocal.attachLocalStream(emitter1.stream());
emitter1.start();
},
]);
addRenegotiation(test.chain,
[
function PC_LOCAL_REPLACE_VIDEO_TRACK_THEN_ADD_SECOND_STREAM(test) {
var oldstream = test.pcLocal._pc.getLocalStreams()[0];
var oldtrack = oldstream.getVideoTracks()[0];
var sender = test.pcLocal._pc.getSenders()[0];
return navigator.mediaDevices.getUserMedia({video:true})
.then(newstream => {
var newtrack = newstream.getVideoTracks()[0];
return test.pcLocal.senderReplaceTrack(0, newtrack, newstream.id);
})
emitter1.stop();
emitter2.start();
const newstream = emitter2.stream();
const newtrack = newstream.getVideoTracks()[0];
return test.pcLocal.senderReplaceTrack(0, newtrack, newstream.id)
.then(() => {
test.setMediaConstraints([{video: true}, {video: true}],
[{video: true}]);
return test.pcLocal.getAllUserMedia([{video: true}]);
// Use fake:true here since the native fake device on linux
// doesn't change color as needed by checkVideoPlaying() below.
return test.pcLocal.getAllUserMedia([{video: true, fake: true}]);
});
},
],
[
function PC_REMOTE_CHECK_ORIGINAL_TRACK_ENDED(test) {
const vremote = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(emitter1.stream().getTracks()[0].id));
if (!vremote) {
return Promise.reject(new Error("Couldn't find video element"));
}
ok(vremote.ended, "Original track should have ended after renegotiation");
},
function PC_REMOTE_CHECK_REPLACED_TRACK_FLOW(test) {
const vremote = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(test.pcLocal._pc.getSenders()[0].track.id));
if (!vremote) {
return Promise.reject(new Error("Couldn't find video element"));
}
return addFinallyToPromise(helper.checkVideoPlaying(vremote, 10, 10, 16))
.finally(() => emitter2.stop())
.then(() => {
const px = helper._helper.getPixel(vremote, 10, 10);
const isBlue = helper._helper.isPixel(
px, CaptureStreamTestHelper.prototype.blue, 5);
const isGrey = helper._helper.isPixel(
px, CaptureStreamTestHelper.prototype.grey, 5);
ok(isBlue || isGrey, "replaced track should be blue or grey");
});
},
function PC_REMOTE_CHECK_ADDED_TRACK_FLOW(test) {
const vremote = test.pcRemote.remoteMediaElements.find(
elem => elem.id.includes(test.pcLocal._pc.getSenders()[1].track.id));
if (!vremote) {
return Promise.reject(new Error("Couldn't find video element"));
}
return helper.checkVideoPlaying(vremote, 10, 10, 16);
},
]
);
// TODO(bug 1093835):
// figure out how to verify if media flows through the new stream
// figure out how to verify that media stopped flowing from old stream
test.run();
});
</script>

View File

@ -13,21 +13,20 @@
visible: true
});
var test;
var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
function addRIDExtension(pc, extensionId) {
var receivers = pc._pc.getReceivers();
const receivers = pc._pc.getReceivers();
is(receivers.length, 1, "We have exactly one RTP receiver");
var receiver = receivers[0];
const receiver = receivers[0];
SpecialPowers.wrap(pc._pc).mozAddRIDExtension(receiver, extensionId);
}
function selectRecvRID(pc, rid) {
var receivers = pc._pc.getReceivers();
const receivers = pc._pc.getReceivers();
is(receivers.length, 1, "We have exactly one RTP receiver");
var receiver = receivers[0];
const receiver = receivers[0];
SpecialPowers.wrap(pc._pc).mozAddRIDFilter(receiver, rid);
}
@ -38,23 +37,24 @@
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
['media.peerconnection.video.min_bitrate_estimate', 180*1000]).then(() => {
var helper;
let emitter, helper;
test = new PeerConnectionTest({bundle: false});
test.setMediaConstraints([{video: true}], [{video: true}]);
test.chain.replace("PC_REMOTE_GUM", [
function PC_REMOTE_CANVAS_CAPTURESTREAM(test) {
emitter = new VideoFrameEmitter();
helper = new VideoStreamHelper();
test.pcRemote.attachLocalStream(helper.stream());
test.pcRemote.attachLocalStream(emitter.stream());
}
]);
test.chain.insertAfter('PC_REMOTE_GET_OFFER', [
function PC_REMOTE_SET_RIDS(test) {
var senders = test.pcRemote._pc.getSenders();
const senders = test.pcRemote._pc.getSenders();
is(senders.length, 1, "We have exactly one RTP sender");
var sender = senders[0];
const sender = senders[0];
ok(sender.track, "Sender has a track");
return sender.setParameters({
@ -88,7 +88,7 @@
// has been created.
test.chain.insertAfter('PC_LOCAL_SET_REMOTE_DESCRIPTION',[
function PC_LOCAL_SET_RTP_FIRST_RID(test) {
var extmap_id = test._local_offer.sdp.match(
const extmap_id = test._local_offer.sdp.match(
"a=extmap:([0-9+])/recvonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
ok(extmap_id, "Local offer has extmap id for simulcast: " + extmap_id[1]);
// Cause pcLocal to filter out everything but RID "bar", only
@ -99,14 +99,16 @@
]);
test.chain.append([
function PC_LOCAL_WAIT_FOR_FRAMES() {
var vremote = test.pcLocal.remoteMediaElements[0];
async function PC_LOCAL_WAIT_FOR_FRAMES() {
const vremote = test.pcLocal.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcLocal");
return helper.waitForFrames(vremote);
emitter.start();
await helper.checkVideoPlaying(vremote, 10, 10, 16);
emitter.stop();
},
function PC_LOCAL_CHECK_SIZE_1() {
var vlocal = test.pcRemote.localMediaElements[0];
var vremote = test.pcLocal.remoteMediaElements[0];
const vlocal = test.pcRemote.localMediaElements[0];
const vremote = test.pcLocal.remoteMediaElements[0];
ok(vlocal, "Should have local video element for pcRemote");
ok(vremote, "Should have remote video element for pcLocal");
ok(vlocal.videoWidth > 0, "source width is positive");
@ -122,21 +124,25 @@
function PC_LOCAL_WAIT_FOR_SECOND_MEDIA_FLOW(test) {
return test.pcLocal.waitForMediaFlow();
},
function PC_LOCAL_WAIT_FOR_FRAMES_2() {
var vremote = test.pcLocal.remoteMediaElements[0];
async function PC_LOCAL_WAIT_FOR_FRAMES_2() {
const vremote = test.pcLocal.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcLocal");
return helper.waitForFrames(vremote);
emitter.start();
await helper.checkVideoPlaying(vremote, 10, 10, 16);
emitter.stop();
},
// For some reason, even though we're getting a 25x25 stream, sometimes
// the resolution isn't updated on the video element on the first frame.
function PC_LOCAL_WAIT_FOR_FRAMES_3() {
var vremote = test.pcLocal.remoteMediaElements[0];
async function PC_LOCAL_WAIT_FOR_FRAMES_3() {
const vremote = test.pcLocal.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcLocal");
return helper.waitForFrames(vremote);
emitter.start();
await helper.checkVideoPlaying(vremote, 10, 10, 16);
emitter.stop();
},
function PC_LOCAL_CHECK_SIZE_2() {
var vlocal = test.pcRemote.localMediaElements[0];
var vremote = test.pcLocal.remoteMediaElements[0];
const vlocal = test.pcRemote.localMediaElements[0];
const vremote = test.pcLocal.remoteMediaElements[0];
ok(vlocal, "Should have local video element for pcRemote");
ok(vremote, "Should have remote video element for pcLocal");
ok(vlocal.videoWidth > 0, "source width is positive");

View File

@ -13,21 +13,20 @@
visible: true
});
var test;
var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
function addRIDExtension(pc, extensionId) {
var receivers = pc._pc.getReceivers();
const receivers = pc._pc.getReceivers();
is(receivers.length, 1, "We have exactly one RTP receiver");
var receiver = receivers[0];
const receiver = receivers[0];
SpecialPowers.wrap(pc._pc).mozAddRIDExtension(receiver, extensionId);
}
function selectRecvRID(pc, rid) {
var receivers = pc._pc.getReceivers();
const receivers = pc._pc.getReceivers();
is(receivers.length, 1, "We have exactly one RTP receiver");
var receiver = receivers[0];
const receiver = receivers[0];
SpecialPowers.wrap(pc._pc).mozAddRIDFilter(receiver, rid);
}
@ -38,23 +37,24 @@
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
['media.peerconnection.video.min_bitrate_estimate', 180*1000]).then(() => {
var helper;
let emitter, helper;
test = new PeerConnectionTest({bundle: false});
const test = new PeerConnectionTest({bundle: false});
test.setMediaConstraints([{video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
emitter = new VideoFrameEmitter();
helper = new VideoStreamHelper();
test.pcLocal.attachLocalStream(helper.stream());
test.pcLocal.attachLocalStream(emitter.stream());
}
]);
test.chain.insertBefore('PC_LOCAL_CREATE_OFFER', [
function PC_LOCAL_SET_RIDS(test) {
var senders = test.pcLocal._pc.getSenders();
const senders = test.pcLocal._pc.getSenders();
is(senders.length, 1, "We have exactly one RTP sender");
var sender = senders[0];
const sender = senders[0];
ok(sender.track, "Sender has a track");
return sender.setParameters({
@ -80,7 +80,7 @@
// has been created.
test.chain.insertAfter('PC_REMOTE_SET_LOCAL_DESCRIPTION',[
function PC_REMOTE_SET_RTP_FIRST_RID(test) {
var extmap_id = test.originalOffer.sdp.match(
const extmap_id = test.originalOffer.sdp.match(
"a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
ok(extmap_id, "Original offer has extmap id for simulcast: " + extmap_id[1]);
// Cause pcRemote to filter out everything but RID "foo", only
@ -91,14 +91,16 @@
]);
test.chain.append([
function PC_REMOTE_WAIT_FOR_FRAMES() {
var vremote = test.pcRemote.remoteMediaElements[0];
async function PC_REMOTE_WAIT_FOR_FRAMES() {
const vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return helper.waitForFrames(vremote);
emitter.start();
await helper.checkVideoPlaying(vremote, 10, 10, 16);
emitter.stop();
},
function PC_REMOTE_CHECK_SIZE_1() {
var vlocal = test.pcLocal.localMediaElements[0];
var vremote = test.pcRemote.remoteMediaElements[0];
const vlocal = test.pcLocal.localMediaElements[0];
const vremote = test.pcRemote.remoteMediaElements[0];
ok(vlocal, "Should have local video element for pcLocal");
ok(vremote, "Should have remote video element for pcRemote");
ok(vlocal.videoWidth > 0, "source width is positive");
@ -114,21 +116,25 @@
function PC_REMOTE_WAIT_FOR_SECOND_MEDIA_FLOW(test) {
return test.pcRemote.waitForMediaFlow();
},
function PC_REMOTE_WAIT_FOR_FRAMES_2() {
var vremote = test.pcRemote.remoteMediaElements[0];
async function PC_REMOTE_WAIT_FOR_FRAMES_2() {
const vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return helper.waitForFrames(vremote);
emitter.start();
await helper.checkVideoPlaying(vremote, 10, 10, 16);
emitter.stop();
},
// For some reason, even though we're getting a 25x25 stream, sometimes
// the resolution isn't updated on the video element on the first frame.
function PC_REMOTE_WAIT_FOR_FRAMES_3() {
var vremote = test.pcRemote.remoteMediaElements[0];
async function PC_REMOTE_WAIT_FOR_FRAMES_3() {
const vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return helper.waitForFrames(vremote);
emitter.start();
await helper.checkVideoPlaying(vremote, 10, 10, 16);
emitter.stop();
},
function PC_REMOTE_CHECK_SIZE_2() {
var vlocal = test.pcLocal.localMediaElements[0];
var vremote = test.pcRemote.remoteMediaElements[0];
const vlocal = test.pcLocal.localMediaElements[0];
const vremote = test.pcRemote.remoteMediaElements[0];
ok(vlocal, "Should have local video element for pcLocal");
ok(vremote, "Should have remote video element for pcRemote");
ok(vlocal.videoWidth > 0, "source width is positive");

View File

@ -15,14 +15,14 @@
var test;
runNetworkTest(function (options) {
var helper;
const emitter = new VideoFrameEmitter();
const helper = new VideoStreamHelper();
test = new PeerConnectionTest(options);
test.chain.replace("PC_LOCAL_GUM", [
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
helper = new VideoStreamHelper();
test.pcLocal.attachLocalStream(helper.stream());
test.pcLocal.attachLocalStream(emitter.stream());
}
]);
@ -30,7 +30,9 @@
function PC_REMOTE_WAIT_FOR_FRAMES() {
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return helper.waitForFrames(vremote);
emitter.start();
return addFinallyToPromise(helper.checkVideoPlaying(vremote, 10, 10, 16))
.finally(() => emitter.stop());
}
]);
@ -47,7 +49,9 @@
function PC_REMOTE_ENSURE_NO_FRAMES() {
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return helper.verifyNoFrames(vremote);
emitter.start();
return addFinallyToPromise(helper.checkVideoPaused(vremote, 10, 10, 16, 5000))
.finally(() => emitter.stop());
},
]);
@ -60,7 +64,9 @@
function PC_REMOTE_WAIT_FOR_FRAMES_2() {
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return helper.waitForFrames(vremote);
emitter.start();
return addFinallyToPromise(helper.checkVideoPlaying(vremote, 10, 10, 16))
.finally(() => emitter.stop());
}
]);

View File

@ -197,16 +197,6 @@ AudioContext::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
/* static */ already_AddRefed<AudioContext>
AudioContext::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
return AudioContext::Constructor(aGlobal,
AudioChannelService::GetDefaultAudioChannel(),
aRv);
}
/* static */ already_AddRefed<AudioContext>
AudioContext::Constructor(const GlobalObject& aGlobal,
AudioChannel aChannel,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
@ -214,7 +204,9 @@ AudioContext::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
RefPtr<AudioContext> object = new AudioContext(window, false, aChannel);
RefPtr<AudioContext> object =
new AudioContext(window, false,
AudioChannelService::GetDefaultAudioChannel());
aRv = object->Init();
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;

View File

@ -154,12 +154,6 @@ public:
static already_AddRefed<AudioContext>
Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
// Constructor for regular AudioContext. A default audio channel is needed.
static already_AddRefed<AudioContext>
Constructor(const GlobalObject& aGlobal,
AudioChannel aChannel,
ErrorResult& aRv);
// Constructor for offline AudioContext
static already_AddRefed<AudioContext>
Constructor(const GlobalObject& aGlobal,

View File

@ -449,7 +449,7 @@ void WebMBufferedState::UpdateIndex(const MediaByteRangeSet& aRanges, MediaResou
while (length > 0) {
static const uint32_t BLOCK_SIZE = 1048576;
uint32_t block = std::min(length, BLOCK_SIZE);
RefPtr<MediaByteBuffer> bytes = aResource->MediaReadAt(offset, block);
RefPtr<MediaByteBuffer> bytes = aResource->CachedReadAt(offset, block);
if (!bytes) {
break;
}

View File

@ -8,9 +8,13 @@
#include "nsTArray.h"
#include "MediaDataDemuxer.h"
#include "MediaResource.h"
#include "NesteggPacketHolder.h"
#include "mozilla/Move.h"
#include <deque>
#include <stdint.h>
typedef struct nestegg nestegg;
namespace mozilla {

Some files were not shown because too many files have changed in this diff Show More