mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 23:05:42 +00:00
merge mozilla-central to mozilla-inbound. r=merge a=merge
This commit is contained in:
commit
3d05c8d3ca
@ -2239,7 +2239,18 @@ function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal) {
|
||||
}
|
||||
}
|
||||
|
||||
function focusAndSelectUrlBar() {
|
||||
/**
|
||||
* Focuses the location bar input field and selects its contents.
|
||||
*
|
||||
* @param [optional] userInitiatedFocus
|
||||
* Whether this focus is caused by an user interaction whose intention
|
||||
* was to use the location bar. For example, using a shortcut to go to
|
||||
* the location bar, or a contextual menu to search from it.
|
||||
* The default is false and should be used in all those cases where the
|
||||
* code focuses the location bar but that's not the primary user
|
||||
* intention, like when opening a new tab.
|
||||
*/
|
||||
function focusAndSelectUrlBar(userInitiatedFocus = false) {
|
||||
// In customize mode, the url bar is disabled. If a new tab is opened or the
|
||||
// user switches to a different tab, this function gets called before we've
|
||||
// finished leaving customize mode, and the url bar will still be disabled.
|
||||
@ -2247,7 +2258,7 @@ function focusAndSelectUrlBar() {
|
||||
// we've finished leaving customize mode.
|
||||
if (CustomizationHandler.isExitingCustomizeMode) {
|
||||
gNavToolbox.addEventListener("aftercustomization", function() {
|
||||
focusAndSelectUrlBar();
|
||||
focusAndSelectUrlBar(userInitiatedFocus);
|
||||
}, {once: true});
|
||||
|
||||
return true;
|
||||
@ -2257,7 +2268,9 @@ function focusAndSelectUrlBar() {
|
||||
if (window.fullScreen)
|
||||
FullScreen.showNavToolbox();
|
||||
|
||||
gURLBar.userInitiatedFocus = userInitiatedFocus;
|
||||
gURLBar.select();
|
||||
gURLBar.userInitiatedFocus = false;
|
||||
if (document.activeElement == gURLBar.inputField)
|
||||
return true;
|
||||
}
|
||||
@ -2265,7 +2278,7 @@ function focusAndSelectUrlBar() {
|
||||
}
|
||||
|
||||
function openLocation() {
|
||||
if (focusAndSelectUrlBar())
|
||||
if (focusAndSelectUrlBar(true))
|
||||
return;
|
||||
|
||||
if (window.location.href != getBrowserURL()) {
|
||||
@ -3815,7 +3828,7 @@ const BrowserSearch = {
|
||||
|
||||
let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
|
||||
if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
|
||||
focusAndSelectUrlBar();
|
||||
focusAndSelectUrlBar(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -133,8 +133,6 @@ var whitelist = [
|
||||
{file: "chrome://global/content/customizeToolbar.xul"},
|
||||
// Bug 1343837
|
||||
{file: "chrome://global/content/findUtils.js"},
|
||||
// Bug 1343843
|
||||
{file: "chrome://global/content/url-classifier/unittests.xul"},
|
||||
// Bug 1348362
|
||||
{file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux", "win"]},
|
||||
// Bug 1348525
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
// The order of the tests here matters!
|
||||
|
||||
const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
|
||||
@ -38,7 +39,7 @@ add_task(async function focus() {
|
||||
setupVisibleHint();
|
||||
gURLBar.blur();
|
||||
let popupPromise = promisePopupShown(gURLBar.popup);
|
||||
gURLBar.focus();
|
||||
focusAndSelectUrlBar(true);
|
||||
await popupPromise;
|
||||
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
|
||||
assertVisible(true);
|
||||
@ -64,23 +65,40 @@ add_task(async function focus() {
|
||||
await BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:blank");
|
||||
});
|
||||
|
||||
add_task(async function new_tab() {
|
||||
// Opening a new tab when the urlbar is unfocused, should focusing it and thus
|
||||
// open the popup in order to show the notification.
|
||||
add_task(async function click_on_focused() {
|
||||
// Even if the location bar is already focused, we should still show the popup
|
||||
// and the notification on click.
|
||||
setupVisibleHint();
|
||||
gURLBar.blur();
|
||||
// Won't show the hint since it's not user initiated.
|
||||
gURLBar.focus();
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
|
||||
|
||||
let popupPromise = promisePopupShown(gURLBar.popup);
|
||||
// openNewForegroundTab doesn't focus the urlbar.
|
||||
await BrowserTestUtils.synthesizeKey("t", { accelKey: true }, gBrowser.selectedBrowser);
|
||||
EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, { button: 0, type: "mousedown" });
|
||||
await popupPromise;
|
||||
|
||||
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
|
||||
assertVisible(true);
|
||||
assertFooterVisible(false);
|
||||
Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
gURLBar.blur();
|
||||
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
|
||||
});
|
||||
|
||||
add_task(async function new_tab() {
|
||||
// Opening a new tab when the urlbar is unfocused, should focus it but not
|
||||
// open the popup.
|
||||
setupVisibleHint();
|
||||
gURLBar.blur();
|
||||
// openNewForegroundTab doesn't focus the urlbar.
|
||||
await BrowserTestUtils.synthesizeKey("t", { accelKey: true }, gBrowser.selectedBrowser);
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
add_task(async function privateWindow() {
|
||||
// Since suggestions are disabled in private windows, the notification should
|
||||
// not appear even when suggestions are otherwise enabled.
|
||||
|
@ -184,6 +184,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
<property name="maxRows"
|
||||
onget="return this.popup.maxResults;"/>
|
||||
|
||||
<!--
|
||||
Set by focusAndSelectUrlBar to indicate whether the next focus event was
|
||||
initiated by an explicit user action. See the "focus" handler below.
|
||||
-->
|
||||
<field name="userInitiatedFocus">false</field>
|
||||
|
||||
<!--
|
||||
onBeforeValueGet is called by the base-binding's .value getter.
|
||||
It can return an object with a "value" property, to override the
|
||||
@ -1445,6 +1451,27 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="maybeShowSearchSuggestionsNotificationOnFocus">
|
||||
<parameter name="mouseFocused"/>
|
||||
<body><![CDATA[
|
||||
let whichNotification = this.whichSearchSuggestionsNotification;
|
||||
if (this._showSearchSuggestionNotificationOnMouseFocus &&
|
||||
mouseFocused) {
|
||||
// Force showing the opt-out notification.
|
||||
this._whichSearchSuggestionsNotification = whichNotification = "opt-out";
|
||||
}
|
||||
if (whichNotification == "opt-out") {
|
||||
try {
|
||||
this.popup.openAutocompletePopup(this, this);
|
||||
} finally {
|
||||
if (mouseFocused) {
|
||||
delete this._whichSearchSuggestionsNotification;
|
||||
this._showSearchSuggestionNotificationOnMouseFocus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
@ -1469,6 +1496,14 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
}
|
||||
]]></handler>
|
||||
|
||||
<handler event="mousedown"><![CDATA[
|
||||
// Eventually show the opt-out notification even if the location bar is
|
||||
// empty, focused, and the user clicks on it.
|
||||
if (event.button == 0 && this.focused && this.textValue == "") {
|
||||
this.maybeShowSearchSuggestionsNotificationOnFocus(true);
|
||||
}
|
||||
]]></handler>
|
||||
|
||||
<handler event="focus"><![CDATA[
|
||||
if (event.originalTarget == this.inputField) {
|
||||
this._hideURLTooltip();
|
||||
@ -1477,8 +1512,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
UpdatePopupNotificationsVisibility();
|
||||
}
|
||||
|
||||
// We show the opt-out notification on every kind of focus to the urlbar
|
||||
// included opening a new tab, but we want to enforce at least one
|
||||
// We show the opt-out notification when the mouse/keyboard focus the
|
||||
// urlbar, but in any case we want to enforce at least one
|
||||
// notification when the user focuses it with the mouse.
|
||||
let whichNotification = this.whichSearchSuggestionsNotification;
|
||||
if (whichNotification == "opt-out" &&
|
||||
@ -1486,25 +1521,16 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
this._showSearchSuggestionNotificationOnMouseFocus = true;
|
||||
}
|
||||
|
||||
// Check whether the focus change came from a user mouse action.
|
||||
// Check whether the focus change came from a keyboard/mouse action.
|
||||
let focusMethod = Services.focus.getLastFocusMethod(window);
|
||||
let mouseFocused = !!(focusMethod & Services.focus.FLAG_BYMOUSE);
|
||||
if (this._showSearchSuggestionNotificationOnMouseFocus &&
|
||||
mouseFocused) {
|
||||
// Force showing the opt-out notification.
|
||||
this._whichSearchSuggestionsNotification = whichNotification = "opt-out";
|
||||
// If it's a focus started by code and the primary user intention was
|
||||
// not to go to the location bar, don't show a notification.
|
||||
if (!focusMethod && !this.userInitiatedFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (whichNotification == "opt-out") {
|
||||
try {
|
||||
this.popup.openAutocompletePopup(this, this);
|
||||
} finally {
|
||||
if (mouseFocused) {
|
||||
delete this._whichSearchSuggestionsNotification;
|
||||
this._showSearchSuggestionNotificationOnMouseFocus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mouseFocused = !!(focusMethod & Services.focus.FLAG_BYMOUSE);
|
||||
this.maybeShowSearchSuggestionsNotificationOnFocus(mouseFocused);
|
||||
}
|
||||
]]></handler>
|
||||
|
||||
|
@ -2113,7 +2113,7 @@ BrowserGlue.prototype = {
|
||||
if (currentUIVersion < 52) {
|
||||
// Keep old devtools log persistence behavior after splitting netmonitor and
|
||||
// webconsole prefs (bug 1307881).
|
||||
if (Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
|
||||
if (Services.prefs.getBoolPref("devtools.webconsole.persistlog", false)) {
|
||||
Services.prefs.setBoolPref("devtools.netmonitor.persistlog", true);
|
||||
}
|
||||
}
|
||||
|
@ -382,6 +382,7 @@ var BookmarkPropertiesPanel = {
|
||||
if (this._batching)
|
||||
return;
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
this._topUndoEntry = PlacesTransactions.topUndoEntry;
|
||||
this._batchBlockingDeferred = PromiseUtils.defer();
|
||||
PlacesTransactions.batch(async () => {
|
||||
await this._batchBlockingDeferred.promise;
|
||||
@ -394,7 +395,7 @@ var BookmarkPropertiesPanel = {
|
||||
|
||||
_endBatch() {
|
||||
if (!this._batching)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
this._batchBlockingDeferred.resolve();
|
||||
@ -403,6 +404,9 @@ var BookmarkPropertiesPanel = {
|
||||
PlacesUtils.transactionManager.endBatch(false);
|
||||
}
|
||||
this._batching = false;
|
||||
let changed = this._topUndoEntry != PlacesTransactions.topUndoEntry;
|
||||
delete this._topUndoEntry;
|
||||
return changed;
|
||||
},
|
||||
|
||||
// nsISupports
|
||||
@ -446,11 +450,14 @@ var BookmarkPropertiesPanel = {
|
||||
// changes done as part of Undo may change the panel contents and by
|
||||
// that force it to commit more transactions.
|
||||
gEditItemOverlay.uninitPanel(true);
|
||||
this._endBatch();
|
||||
if (PlacesUIUtils.useAsyncTransactions)
|
||||
PlacesTransactions.undo().catch(Components.utils.reportError);
|
||||
else
|
||||
let changed = this._endBatch();
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
if (changed) {
|
||||
PlacesTransactions.undo().catch(Components.utils.reportError);
|
||||
}
|
||||
} else {
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
}
|
||||
window.arguments[0].performed = false;
|
||||
},
|
||||
|
||||
|
@ -21,6 +21,7 @@ support-files =
|
||||
[browser_bookmarkProperties_addKeywordForThisSearch.js]
|
||||
[browser_bookmarkProperties_addLivemark.js]
|
||||
[browser_bookmarkProperties_bookmarkAllTabs.js]
|
||||
[browser_bookmarkProperties_cancel.js]
|
||||
[browser_bookmarkProperties_editTagContainer.js]
|
||||
[browser_bookmarkProperties_readOnlyRoot.js]
|
||||
[browser_bookmarksProperties.js]
|
||||
|
@ -0,0 +1,118 @@
|
||||
"use strict";
|
||||
|
||||
/* global sinon */
|
||||
Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
|
||||
|
||||
const sandbox = sinon.sandbox.create();
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
sandbox.restore();
|
||||
delete window.sinon;
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
await PlacesTestUtils.clearHistory();
|
||||
});
|
||||
|
||||
let bookmarks; // Bookmarks added via insertTree.
|
||||
let bookmarkIds; // Map of Guids to Ids.
|
||||
|
||||
add_task(async function setup() {
|
||||
bookmarks = await PlacesUtils.bookmarks.insertTree({
|
||||
guid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
children: [{
|
||||
title: "bm1",
|
||||
url: "http://example.com",
|
||||
}, {
|
||||
title: "bm2",
|
||||
url: "http://example.com/2",
|
||||
}]
|
||||
});
|
||||
|
||||
bookmarkIds = await PlacesUtils.promiseManyItemIds([
|
||||
bookmarks[0].guid, bookmarks[1].guid
|
||||
]);
|
||||
|
||||
// Undo is called asynchronously - and not waited for. Since we're not
|
||||
// expecting undo to be called, we can only tell this by stubbing it.
|
||||
sandbox.stub(PlacesTransactions, "undo").returns(Promise.resolve());
|
||||
});
|
||||
|
||||
// Tests for bug 1391393 - Ensures that if the user cancels the bookmark properties
|
||||
// dialog without having done any changes, then no undo is called.
|
||||
add_task(async function test_cancel_with_no_changes() {
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
Assert.ok(true, "Skipping test as async transactions are turned off");
|
||||
return;
|
||||
}
|
||||
|
||||
await withSidebarTree("bookmarks", async (tree) => {
|
||||
tree.selectItems([bookmarkIds.get(bookmarks[0].guid)]);
|
||||
|
||||
// Delete the bookmark to put something in the undo history.
|
||||
// Rather than calling cmd_delete, we call the remove directly, so that we
|
||||
// can await on it finishing, and be guaranteed that there's something
|
||||
// in the history.
|
||||
await tree.controller.remove("Remove Selection");
|
||||
|
||||
tree.selectItems([bookmarkIds.get(bookmarks[1].guid)]);
|
||||
|
||||
// Now open the bookmarks dialog and cancel it.
|
||||
await withBookmarksDialog(
|
||||
true,
|
||||
function openDialog() {
|
||||
tree.controller.doCommand("placesCmd_show:info");
|
||||
},
|
||||
async function test(dialogWin) {
|
||||
let acceptButton = dialogWin.document.documentElement.getButton("accept");
|
||||
await BrowserTestUtils.waitForCondition(() => !acceptButton.disabled,
|
||||
"The accept button should be enabled");
|
||||
}
|
||||
);
|
||||
|
||||
Assert.ok(PlacesTransactions.undo.notCalled, "undo should not have been called");
|
||||
|
||||
// Check the bookmark is still removed.
|
||||
Assert.ok(!(await PlacesUtils.bookmarks.fetch(bookmarks[0].guid)),
|
||||
"The originally removed bookmark should not exist.");
|
||||
|
||||
Assert.ok(await PlacesUtils.bookmarks.fetch(bookmarks[1].guid),
|
||||
"The second bookmark should still exist");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_cancel_with_changes() {
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
Assert.ok(true, "Skipping test as async transactions are turned off");
|
||||
return;
|
||||
}
|
||||
|
||||
await withSidebarTree("bookmarks", async (tree) => {
|
||||
tree.selectItems([bookmarkIds.get(bookmarks[1].guid)]);
|
||||
|
||||
// Now open the bookmarks dialog and cancel it.
|
||||
await withBookmarksDialog(
|
||||
true,
|
||||
function openDialog() {
|
||||
tree.controller.doCommand("placesCmd_show:info");
|
||||
},
|
||||
async function test(dialogWin) {
|
||||
let acceptButton = dialogWin.document.documentElement.getButton("accept");
|
||||
await BrowserTestUtils.waitForCondition(() => !acceptButton.disabled,
|
||||
"The accept button should be enabled");
|
||||
|
||||
let promiseTitleChangeNotification = promiseBookmarksNotification(
|
||||
"onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "n");
|
||||
|
||||
fillBookmarkTextField("editBMPanel_namePicker", "n", dialogWin);
|
||||
|
||||
// The dialog is instant apply.
|
||||
await promiseTitleChangeNotification;
|
||||
|
||||
// Ensure that the addition really is finished before we hit cancel.
|
||||
await PlacesTestUtils.promiseAsyncUpdates();
|
||||
}
|
||||
);
|
||||
|
||||
Assert.ok(PlacesTransactions.undo.calledOnce,
|
||||
"undo should have been called once.");
|
||||
});
|
||||
});
|
@ -64,6 +64,9 @@ add_task(async function() {
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(uri);
|
||||
Assert.equal(tags.length, 1, "Found the right number of tags");
|
||||
Assert.ok(tags.includes("tag2"), "Found the expected tag");
|
||||
|
||||
// Ensure that the addition really is finished before we hit cancel.
|
||||
await PlacesTestUtils.promiseAsyncUpdates();
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -5,6 +5,16 @@
|
||||
"use strict";
|
||||
|
||||
add_task(async function test() {
|
||||
let bookmark = await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
title: "Plain Bob",
|
||||
url: "http://example.com"
|
||||
});
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
await PlacesUtils.bookmarks.remove(bookmark);
|
||||
});
|
||||
|
||||
let sidebarBox = document.getElementById("sidebar-box");
|
||||
is(sidebarBox.hidden, true, "The sidebar should be hidden");
|
||||
|
||||
|
@ -46,6 +46,11 @@ add_task(async function setup() {
|
||||
|
||||
async function run_drag_test(startBookmarkIndex, insertionIndex,
|
||||
realInsertionIndex, expectTransactionCreated = true) {
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
Assert.ok(true, "Skipping test as async transactions are turned off");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the stubs so that previous test runs don't count against us.
|
||||
PlacesUIUtils.getTransactionForData.reset();
|
||||
PlacesTransactions.batch.reset();
|
||||
|
@ -1086,24 +1086,32 @@ var gPrivacyPane = {
|
||||
safeBrowsingMalwarePref.value = enableSafeBrowsing.checked;
|
||||
|
||||
if (enableSafeBrowsing.checked) {
|
||||
blockDownloads.removeAttribute("disabled");
|
||||
if (blockDownloads.checked) {
|
||||
if (blockDownloads) {
|
||||
blockDownloads.removeAttribute("disabled");
|
||||
if (blockDownloads.checked) {
|
||||
blockUncommonUnwanted.removeAttribute("disabled");
|
||||
}
|
||||
} else {
|
||||
blockUncommonUnwanted.removeAttribute("disabled");
|
||||
}
|
||||
} else {
|
||||
blockDownloads.setAttribute("disabled", "true");
|
||||
if (blockDownloads) {
|
||||
blockDownloads.setAttribute("disabled", "true");
|
||||
}
|
||||
blockUncommonUnwanted.setAttribute("disabled", "true");
|
||||
}
|
||||
});
|
||||
|
||||
blockDownloads.addEventListener("command", function() {
|
||||
blockDownloadsPref.value = blockDownloads.checked;
|
||||
if (blockDownloads.checked) {
|
||||
blockUncommonUnwanted.removeAttribute("disabled");
|
||||
} else {
|
||||
blockUncommonUnwanted.setAttribute("disabled", "true");
|
||||
}
|
||||
});
|
||||
if (blockDownloads) {
|
||||
blockDownloads.addEventListener("command", function() {
|
||||
blockDownloadsPref.value = blockDownloads.checked;
|
||||
if (blockDownloads.checked) {
|
||||
blockUncommonUnwanted.removeAttribute("disabled");
|
||||
} else {
|
||||
blockUncommonUnwanted.setAttribute("disabled", "true");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
blockUncommonUnwanted.addEventListener("command", function() {
|
||||
blockUnwantedPref.value = blockUncommonUnwanted.checked;
|
||||
@ -1112,8 +1120,8 @@ var gPrivacyPane = {
|
||||
let malware = malwareTable.value
|
||||
.split(",")
|
||||
.filter(x => x !== "goog-unwanted-proto" &&
|
||||
x !== "goog-unwanted-shavar" &&
|
||||
x !== "test-unwanted-simple");
|
||||
x !== "goog-unwanted-shavar" &&
|
||||
x !== "test-unwanted-simple");
|
||||
|
||||
if (blockUncommonUnwanted.checked) {
|
||||
if (malware.indexOf("goog-malware-shavar") != -1) {
|
||||
@ -1135,13 +1143,18 @@ var gPrivacyPane = {
|
||||
|
||||
enableSafeBrowsing.checked = safeBrowsingPhishingPref.value && safeBrowsingMalwarePref.value;
|
||||
if (!enableSafeBrowsing.checked) {
|
||||
blockDownloads.setAttribute("disabled", "true");
|
||||
if (blockDownloads) {
|
||||
blockDownloads.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
blockUncommonUnwanted.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
blockDownloads.checked = blockDownloadsPref.value;
|
||||
if (!blockDownloadsPref.value) {
|
||||
blockUncommonUnwanted.setAttribute("disabled", "true");
|
||||
if (blockDownloads) {
|
||||
blockDownloads.checked = blockDownloadsPref.value;
|
||||
if (!blockDownloadsPref.value) {
|
||||
blockUncommonUnwanted.setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
|
||||
blockUncommonUnwanted.checked = blockUnwantedPref.value && blockUncommonPref.value;
|
||||
|
@ -732,9 +732,11 @@
|
||||
label="&enableSafeBrowsing.label;"
|
||||
accesskey="&enableSafeBrowsing.accesskey;" />
|
||||
<vbox class="indent">
|
||||
#ifdef MOZILLA_OFFICIAL
|
||||
<checkbox id="blockDownloads"
|
||||
label="&blockDownloads.label;"
|
||||
accesskey="&blockDownloads.accesskey;" />
|
||||
#endif
|
||||
<checkbox id="blockUncommonUnwanted"
|
||||
label="&blockUncommonAndUnwanted.label;"
|
||||
accesskey="&blockUncommonAndUnwanted.accesskey;" />
|
||||
|
@ -40,9 +40,14 @@ add_task(async function() {
|
||||
let blockDownloads = doc.getElementById("blockDownloads");
|
||||
let blockUncommon = doc.getElementById("blockUncommonUnwanted");
|
||||
let checked = checkbox.checked;
|
||||
if (!AppConstants.MOZILLA_OFFICIAL) {
|
||||
is(blockDownloads, undefined, "downloads protection is disabled in un-official builds");
|
||||
} else {
|
||||
is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
|
||||
}
|
||||
|
||||
is(checked, val1 && val2, "safebrowsing preference is initialized correctly");
|
||||
// should be disabled when checked is false (= pref is turned off)
|
||||
is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
|
||||
is(blockUncommon.hasAttribute("disabled"), !checked, "block uncommon checkbox is set correctly");
|
||||
|
||||
// scroll the checkbox into the viewport and click checkbox
|
||||
@ -57,8 +62,10 @@ add_task(async function() {
|
||||
|
||||
// check if the other checkboxes have updated
|
||||
checked = checkbox.checked;
|
||||
is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
|
||||
is(blockUncommon.hasAttribute("disabled"), !checked || !blockDownloads.checked, "block uncommon checkbox is set correctly");
|
||||
if (blockDownloads) {
|
||||
is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
|
||||
is(blockUncommon.hasAttribute("disabled"), !checked || !blockDownloads.checked, "block uncommon checkbox is set correctly");
|
||||
}
|
||||
}
|
||||
|
||||
await checkPrefSwitch(true, true);
|
||||
|
@ -36,6 +36,11 @@ add_task(async function() {
|
||||
|
||||
let doc = gBrowser.selectedBrowser.contentDocument;
|
||||
let checkbox = doc.getElementById("blockDownloads");
|
||||
if (!AppConstants.MOZILLA_OFFICIAL) {
|
||||
is(checkbox, undefined, "downloads protection is disabled in un-official builds");
|
||||
return;
|
||||
}
|
||||
|
||||
let blockUncommon = doc.getElementById("blockUncommonUnwanted");
|
||||
let checked = checkbox.checked;
|
||||
is(checked, val, "downloads preference is initialized correctly");
|
||||
|
@ -29,6 +29,8 @@ class ManageRecords {
|
||||
this._subStorageName = subStorageName;
|
||||
this._elements = elements;
|
||||
this._records = [];
|
||||
this._newRequest = false;
|
||||
this._isLoadingRecords = false;
|
||||
this.prefWin = window.opener;
|
||||
this.localizeDocument();
|
||||
window.addEventListener("DOMContentLoaded", this, {once: true});
|
||||
@ -70,17 +72,40 @@ class ManageRecords {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load records and render them.
|
||||
* Load records and render them. This function is a wrapper for _loadRecords
|
||||
* to ensure any reentrant will be handled well.
|
||||
*/
|
||||
async loadRecords() {
|
||||
// This function can be early returned when there is any reentrant happends.
|
||||
// "_newRequest" needs to be set to ensure all changes will be applied.
|
||||
if (this._isLoadingRecords) {
|
||||
this._newRequest = true;
|
||||
return;
|
||||
}
|
||||
this._isLoadingRecords = true;
|
||||
|
||||
await this._loadRecords();
|
||||
|
||||
// _loadRecords should be invoked again if there is any multiple entrant
|
||||
// during running _loadRecords(). This step ensures that the latest request
|
||||
// still is applied.
|
||||
while (this._newRequest) {
|
||||
this._newRequest = false;
|
||||
await this._loadRecords();
|
||||
}
|
||||
this._isLoadingRecords = false;
|
||||
|
||||
// For testing only: Notify when records are loaded
|
||||
this._elements.records.dispatchEvent(new CustomEvent("RecordsLoaded"));
|
||||
}
|
||||
|
||||
async _loadRecords() {
|
||||
let storage = await this.getStorage();
|
||||
let records = storage.getAll();
|
||||
// Sort by last modified time starting with most recent
|
||||
records.sort((a, b) => b.timeLastModified - a.timeLastModified);
|
||||
await this.renderRecordElements(records);
|
||||
this.updateButtonsStates(this._selectedOptions.length);
|
||||
// For testing only: Notify when records are loaded
|
||||
this._elements.records.dispatchEvent(new CustomEvent("RecordsLoaded"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,6 +51,25 @@
|
||||
}
|
||||
|
||||
#onboarding-overlay-button::after {
|
||||
content: " ";
|
||||
border-radius: 50%;
|
||||
margin-top: -1px;
|
||||
margin-inline-start: -13px;
|
||||
border: 2px solid #f2f2f2;
|
||||
background: #0A84FF;
|
||||
padding: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
display: block;
|
||||
box-sizing: content-box;
|
||||
float: inline-end;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#onboarding-overlay-button:hover::after,
|
||||
#onboarding-overlay-button.onboarding-speech-bubble::after {
|
||||
background: #0060df;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
@ -58,10 +77,11 @@
|
||||
box-sizing: content-box;
|
||||
font-weight: 400;
|
||||
content: attr(aria-label);
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
padding: 10px 16px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-width: 100px;
|
||||
max-width: 140px;
|
||||
white-space: pre-line;
|
||||
|
@ -21,6 +21,8 @@ const BRAND_SHORT_NAME = Services.strings
|
||||
.GetStringFromName("brandShortName");
|
||||
const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
|
||||
const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
|
||||
const ONBOARDING_MIN_WIDTH_PX = 960;
|
||||
const SPEECH_BUBBLE_MIN_WIDTH_PX = 1150;
|
||||
|
||||
/**
|
||||
* Add any number of tours, key is the tourId, value should follow the format below
|
||||
@ -380,11 +382,18 @@ class Onboarding {
|
||||
}
|
||||
|
||||
_resizeUI() {
|
||||
// Don't show the overlay UI before we get to a better, responsive design.
|
||||
if (this._window.document.body.getBoundingClientRect().width < 960) {
|
||||
let width = this._window.document.body.getBoundingClientRect().width;
|
||||
if (width < ONBOARDING_MIN_WIDTH_PX) {
|
||||
// Don't show the overlay UI before we get to a better, responsive design.
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this._initUI();
|
||||
if (this._isFirstSession && width >= SPEECH_BUBBLE_MIN_WIDTH_PX) {
|
||||
this._overlayIcon.classList.add("onboarding-speech-bubble");
|
||||
} else {
|
||||
this._initUI();
|
||||
this._overlayIcon.classList.remove("onboarding-speech-bubble");
|
||||
}
|
||||
}
|
||||
|
||||
@ -780,15 +789,31 @@ class Onboarding {
|
||||
}
|
||||
}
|
||||
|
||||
_muteNotificationOnFirstSession() {
|
||||
if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
|
||||
// There is a queue. We had prompted before, this must not be the 1st session.
|
||||
get _isFirstSession() {
|
||||
// Should only directly return on the "false" case. Consider:
|
||||
// 1. On the 1st session, `_firstSession` is true
|
||||
// 2. During the 1st session, user resizes window so that the UI is destroyed
|
||||
// 3. After the 1st mute session, user resizes window so that the UI is re-init
|
||||
if (this._firstSession === false) {
|
||||
return false;
|
||||
}
|
||||
this._firstSession = true;
|
||||
|
||||
let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
|
||||
if (muteDuration == 0) {
|
||||
// Don't mute when this is set to 0 on purpose.
|
||||
// There is a queue, which means we had prompted tour notifications before. Therefore this is not the 1st session.
|
||||
if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
|
||||
this._firstSession = false;
|
||||
}
|
||||
|
||||
// When this is set to 0 on purpose, always judge as not the 1st session
|
||||
if (Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms") === 0) {
|
||||
this._firstSession = false;
|
||||
}
|
||||
|
||||
return this._firstSession;
|
||||
}
|
||||
|
||||
_muteNotificationOnFirstSession() {
|
||||
if (!this._isFirstSession) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -802,6 +827,7 @@ class Onboarding {
|
||||
}]);
|
||||
return true;
|
||||
}
|
||||
let muteDuration = Services.prefs.getIntPref("browser.onboarding.notification.mute-duration-on-first-session-ms");
|
||||
return Date.now() - lastTime <= muteDuration;
|
||||
}
|
||||
|
||||
@ -869,6 +895,9 @@ class Onboarding {
|
||||
if (this._muteNotificationOnFirstSession()) {
|
||||
return;
|
||||
}
|
||||
// After the notification mute on the 1st session,
|
||||
// we don't want to show the speech bubble by default
|
||||
this._overlayIcon.classList.remove("onboarding-speech-bubble");
|
||||
|
||||
let queue = this._getNotificationQueue();
|
||||
let startQueueLength = queue.length;
|
||||
|
@ -205,9 +205,9 @@ l10n-check::
|
||||
$(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/unpack.py $(DIST)/l10n-stage/$(MOZ_PKG_DIR)$(_RESPATH)
|
||||
(cd $(DIST)/l10n-stage && test $$(cat $(MOZ_PKG_DIR)$(_RESPATH)/update.locale) = x-test)
|
||||
@# package langpack as web extension, too, run some tests on it
|
||||
$(MAKE) package-langpack-x-test L10NBASEDIR='$(PWD)' WEBEXT_LANGPACKS=1
|
||||
$(MAKE) analyze-langpack-x-test
|
||||
$(MAKE) check-clobber-l10n-x-test
|
||||
$(MAKE) package-langpack-x-test L10NBASEDIR='$(PWD)' WEBEXT_LANGPACKS=1 MOZ_SIMPLE_PACKAGE_NAME=
|
||||
$(MAKE) analyze-langpack-x-test MOZ_SIMPLE_PACKAGE_NAME=
|
||||
$(MAKE) check-clobber-l10n-x-test MOZ_SIMPLE_PACKAGE_NAME=
|
||||
|
||||
# Helper rules to have AB_CD set to the locale we test for testing
|
||||
# We need to split this out from l10n-check, as that needs AB_CD to be en-US
|
||||
@ -222,5 +222,5 @@ check-clobber-l10n-%:
|
||||
$(MAKE) clobber-x-test
|
||||
$(RM) -r $(DIST)/l10n-stage $(DIST)/xpi-stage/locale-$(AB_CD)
|
||||
$(RM) -r $(DIST)/*.$(AB_CD).*
|
||||
$(RM) -r $(UNPACKED_INSTALLER)
|
||||
$(RM) -r $(UNPACKED_INSTALLER) $(ABS_DIST)/$(LANGPACK)
|
||||
$(RM) -rf $(DIST)/install/sea/*.$(AB_CD).*
|
||||
|
@ -62,9 +62,9 @@ function ProgressGraphHelper(win, propertyCSSName, animationType, keyframes, dur
|
||||
|
||||
this.keyframes = keyframesObject;
|
||||
this.devtoolsKeyframes = keyframes;
|
||||
this.valueHelperFunction = this.getValueHelperFunction();
|
||||
this.animation = this.targetEl.animate(this.keyframes, effectTiming);
|
||||
this.animation.pause();
|
||||
this.valueHelperFunction = this.getValueHelperFunction();
|
||||
}
|
||||
|
||||
ProgressGraphHelper.prototype = {
|
||||
@ -216,10 +216,15 @@ ProgressGraphHelper.prototype = {
|
||||
getDiscreteValueHelperFunction: function () {
|
||||
const discreteValues = [];
|
||||
this.keyframes.forEach(keyframe => {
|
||||
if (!discreteValues.includes(keyframe.value)) {
|
||||
discreteValues.push(keyframe.value);
|
||||
// Set style once since the computed value may differ from specified keyframe value.
|
||||
this.targetEl.style[this.propertyJSName] = keyframe.value;
|
||||
const style = this.win.getComputedStyle(this.targetEl)[this.propertyJSName];
|
||||
if (!discreteValues.includes(style)) {
|
||||
discreteValues.push(style);
|
||||
}
|
||||
});
|
||||
this.targetEl.style[this.propertyJSName] = "unset";
|
||||
|
||||
return value => {
|
||||
return discreteValues.indexOf(value) / (discreteValues.length - 1);
|
||||
};
|
||||
|
@ -26,6 +26,15 @@ const TEST_CASES = [
|
||||
{ x: 1000, y: 1, color: "rgb(0, 255, 0)" }
|
||||
]
|
||||
},
|
||||
"background-repeat": {
|
||||
expectedClass: "discrete",
|
||||
expectedValues: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 1 },
|
||||
{ x: 1000, y: 1 },
|
||||
]
|
||||
},
|
||||
"font-size": {
|
||||
expectedClass: "length",
|
||||
expectedValues: [
|
||||
@ -74,6 +83,15 @@ const TEST_CASES = [
|
||||
{ x: 1000, y: 1, color: "rgb(255, 0, 0)" }
|
||||
]
|
||||
},
|
||||
"background-repeat": {
|
||||
expectedClass: "discrete",
|
||||
expectedValues: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 1 },
|
||||
{ x: 1000, y: 1 },
|
||||
]
|
||||
},
|
||||
"font-size": {
|
||||
expectedClass: "length",
|
||||
expectedValues: [
|
||||
@ -127,6 +145,17 @@ const TEST_CASES = [
|
||||
{ x: 1000, y: 1, color: "rgb(0, 255, 0)" }
|
||||
]
|
||||
},
|
||||
"background-repeat": {
|
||||
expectedClass: "discrete",
|
||||
expectedValues: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 249.999, y: 0 },
|
||||
{ x: 250, y: 1 },
|
||||
{ x: 749.999, y: 1 },
|
||||
{ x: 750, y: 0 },
|
||||
{ x: 1000, y: 0 },
|
||||
]
|
||||
},
|
||||
"font-size": {
|
||||
expectedClass: "length",
|
||||
expectedValues: [
|
||||
@ -184,6 +213,15 @@ const TEST_CASES = [
|
||||
{ x: 1000, y: 1, color: "rgb(0, 255, 0)" }
|
||||
]
|
||||
},
|
||||
"background-repeat": {
|
||||
expectedClass: "discrete",
|
||||
expectedValues: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 1 },
|
||||
{ x: 1000, y: 1 },
|
||||
]
|
||||
},
|
||||
"font-size": {
|
||||
expectedClass: "length",
|
||||
expectedValues: [
|
||||
|
@ -27,12 +27,14 @@
|
||||
|
||||
document.querySelector("#target1").animate(
|
||||
[{ backgroundColor: "red",
|
||||
backgroundRepeat: "space",
|
||||
fontSize: "10px",
|
||||
marginLeft: "0px",
|
||||
opacity: 0,
|
||||
textAlign: "right",
|
||||
transform: "translate(0px)" },
|
||||
{ backgroundColor: "lime",
|
||||
backgroundRepeat: "round",
|
||||
fontSize: "20px",
|
||||
marginLeft: "100px",
|
||||
opacity: 1,
|
||||
@ -41,12 +43,14 @@
|
||||
|
||||
document.querySelector("#target2").animate(
|
||||
[{ backgroundColor: "lime",
|
||||
backgroundRepeat: "space",
|
||||
fontSize: "20px",
|
||||
marginLeft: "100px",
|
||||
opacity: 1,
|
||||
textAlign: "center",
|
||||
transform: "translate(100px)" },
|
||||
{ backgroundColor: "red",
|
||||
backgroundRepeat: "round",
|
||||
fontSize: "10px",
|
||||
marginLeft: "0px",
|
||||
opacity: 0,
|
||||
@ -55,18 +59,21 @@
|
||||
|
||||
document.querySelector("#target3").animate(
|
||||
[{ backgroundColor: "red",
|
||||
backgroundRepeat: "space",
|
||||
fontSize: "10px",
|
||||
marginLeft: "0px",
|
||||
opacity: 0,
|
||||
textAlign: "right",
|
||||
transform: "translate(0px)" },
|
||||
{ backgroundColor: "blue",
|
||||
backgroundRepeat: "round",
|
||||
fontSize: "20px",
|
||||
marginLeft: "100px",
|
||||
opacity: 1,
|
||||
textAlign: "center",
|
||||
transform: "translate(100px)" },
|
||||
{ backgroundColor: "lime",
|
||||
backgroundRepeat: "space",
|
||||
fontSize: "10px",
|
||||
marginLeft: "0px",
|
||||
opacity: 0,
|
||||
@ -75,6 +82,7 @@
|
||||
|
||||
document.querySelector("#target4").animate(
|
||||
[{ backgroundColor: "red",
|
||||
backgroundRepeat: "space",
|
||||
fontSize: "10px",
|
||||
marginLeft: "0px",
|
||||
opacity: 0,
|
||||
@ -82,6 +90,7 @@
|
||||
transform: "translate(0px)",
|
||||
easing: "steps(2)" },
|
||||
{ backgroundColor: "lime",
|
||||
backgroundRepeat: "round",
|
||||
fontSize: "20px",
|
||||
marginLeft: "100px",
|
||||
opacity: 1,
|
||||
|
@ -278,7 +278,7 @@ Tools.performance = {
|
||||
inMenu: true,
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
return target.hasActor("profiler");
|
||||
return target.hasActor("performance");
|
||||
},
|
||||
|
||||
build: function (frame, target) {
|
||||
|
@ -2668,9 +2668,9 @@ Toolbox.prototype = {
|
||||
* necessary because of the WebConsole's `profile` and `profileEnd` methods.
|
||||
*/
|
||||
initPerformance: Task.async(function* () {
|
||||
// If target does not have profiler actor (addons), do not
|
||||
// If target does not have performance actor (addons), do not
|
||||
// even register the shared performance connection.
|
||||
if (!this.target.hasActor("profiler")) {
|
||||
if (!this.target.hasActor("performance")) {
|
||||
return promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
"use strict";
|
||||
|
||||
var Cu = Components.utils;
|
||||
const loaders = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
const loaders = Cu.import("resource://devtools/shared/base-loader.js", {});
|
||||
const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const { joinURI } = devtools.require("devtools/shared/path");
|
||||
const { assert } = devtools.require("devtools/shared/DevToolsUtils");
|
||||
@ -103,11 +103,9 @@ function BrowserLoaderBuilder({ baseURI, window, useOnlyShared, commonLibRequire
|
||||
}
|
||||
|
||||
const opts = {
|
||||
id: "browser-loader",
|
||||
sharedGlobal: true,
|
||||
sandboxPrototype: window,
|
||||
sandboxName: "DevTools (UI loader)",
|
||||
noSandboxAddonId: true,
|
||||
paths: Object.assign({}, dynamicPaths, loaderOptions.paths),
|
||||
invisibleToDebugger: loaderOptions.invisibleToDebugger,
|
||||
requireHook: (id, require) => {
|
||||
|
@ -5,16 +5,17 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Loader} = Components.utils.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
const {Loader, Require} =
|
||||
Components.utils.import("resource://devtools/shared/base-loader.js", {});
|
||||
|
||||
const loader = new Loader.Loader({
|
||||
const loader = new Loader({
|
||||
paths: {
|
||||
"": "resource://gre/modules/commonjs/",
|
||||
"devtools": "resource://devtools",
|
||||
},
|
||||
globals: {},
|
||||
});
|
||||
const require = Loader.Require(loader, { id: "undo-test" });
|
||||
const require = Require(loader, { id: "undo-test" });
|
||||
|
||||
const {UndoStack} = require("devtools/client/shared/undo");
|
||||
|
||||
|
@ -33,7 +33,7 @@ Starting with Firefox 36 (thanks to [bug 1069673](https://bugzilla.mozilla.org/s
|
||||
1. Detecting if the server has an actor: all you need is access to the `Toolbox` instance, which all panels do, when they get instantiated. Then you can do:
|
||||
|
||||
```js
|
||||
let hasProfilerActor = toolbox.target.hasActor("profiler");
|
||||
let hasPerformanceActor = toolbox.target.hasActor("performance");
|
||||
```
|
||||
|
||||
The `hasActor` method returns a boolean synchronously.
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
const {Ci, Cu} = require("chrome");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const {serializeStack, parseStack} = require("toolkit/loader");
|
||||
const {serializeStack, parseStack} = Cu.import("resource://devtools/shared/base-loader.js", {});
|
||||
|
||||
const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
|
||||
const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
|
||||
|
@ -47,7 +47,6 @@ DevToolsModules(
|
||||
'preference.js',
|
||||
'pretty-print-worker.js',
|
||||
'process.js',
|
||||
'profiler.js',
|
||||
'promises.js',
|
||||
'reflow.js',
|
||||
'root.js',
|
||||
@ -96,9 +95,6 @@ with Files('monitor.js'):
|
||||
with Files('performance*'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
|
||||
|
||||
with Files('profiler.js'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
|
||||
|
||||
with Files('source.js'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Debugger')
|
||||
|
||||
|
@ -1,50 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
|
||||
const { Profiler } = require("devtools/server/performance/profiler");
|
||||
const { actorBridgeWithSpec } = require("devtools/server/actors/common");
|
||||
const { profilerSpec } = require("devtools/shared/specs/profiler");
|
||||
|
||||
/**
|
||||
* This actor wraps the Profiler module at devtools/server/performance/profiler.js
|
||||
* and provides RDP definitions.
|
||||
*
|
||||
* @see devtools/server/performance/profiler.js for documentation.
|
||||
*/
|
||||
exports.ProfilerActor = ActorClassWithSpec(profilerSpec, {
|
||||
initialize: function (conn) {
|
||||
Actor.prototype.initialize.call(this, conn);
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
|
||||
this.bridge = new Profiler();
|
||||
this.bridge.on("*", this._onProfilerEvent);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.bridge.off("*", this._onProfilerEvent);
|
||||
this.bridge.destroy();
|
||||
Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
startProfiler: actorBridgeWithSpec("start"),
|
||||
stopProfiler: actorBridgeWithSpec("stop"),
|
||||
getProfile: actorBridgeWithSpec("getProfile"),
|
||||
getFeatures: actorBridgeWithSpec("getFeatures"),
|
||||
getBufferInfo: actorBridgeWithSpec("getBufferInfo"),
|
||||
getStartOptions: actorBridgeWithSpec("getStartOptions"),
|
||||
isActive: actorBridgeWithSpec("isActive"),
|
||||
sharedLibraries: actorBridgeWithSpec("sharedLibraries"),
|
||||
registerEventNotifications: actorBridgeWithSpec("registerEventNotifications"),
|
||||
unregisterEventNotifications: actorBridgeWithSpec("unregisterEventNotifications"),
|
||||
setProfilerStatusInterval: actorBridgeWithSpec("setProfilerStatusInterval"),
|
||||
|
||||
/**
|
||||
* Pipe events from Profiler module to this actor.
|
||||
*/
|
||||
_onProfilerEvent: function (eventName, ...data) {
|
||||
this.emit(eventName, ...data);
|
||||
},
|
||||
});
|
@ -549,11 +549,6 @@ var DebuggerServer = {
|
||||
type: { tab: true }
|
||||
});
|
||||
if ("nsIProfiler" in Ci) {
|
||||
this.registerModule("devtools/server/actors/profiler", {
|
||||
prefix: "profiler",
|
||||
constructor: "ProfilerActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/performance", {
|
||||
prefix: "performance",
|
||||
constructor: "PerformanceActor",
|
||||
|
@ -1,90 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether the profiler module and actor have the correct state on
|
||||
* initialization, activation, and when a clients' connection closes.
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
const MAX_PROFILER_ENTRIES = 10000000;
|
||||
|
||||
function run_test() {
|
||||
// Ensure the profiler is not running when the test starts (it could
|
||||
// happen if the MOZ_PROFILER_STARTUP environment variable is set).
|
||||
Profiler.StopProfiler();
|
||||
|
||||
get_chrome_actors((client1, form1) => {
|
||||
let actor1 = form1.profilerActor;
|
||||
get_chrome_actors((client2, form2) => {
|
||||
let actor2 = form2.profilerActor;
|
||||
test_activate(client1, actor1, client2, actor2, () => {
|
||||
do_test_finished();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_activate(client1, actor1, client2, actor2, callback) {
|
||||
// Profiler should be inactive at this point.
|
||||
client1.request({ to: actor1, type: "isActive" }, response => {
|
||||
do_check_false(Profiler.IsActive());
|
||||
do_check_false(response.isActive);
|
||||
do_check_eq(response.currentTime, undefined);
|
||||
do_check_true(typeof response.position === "number");
|
||||
do_check_true(typeof response.totalSize === "number");
|
||||
do_check_true(typeof response.generation === "number");
|
||||
|
||||
// Start the profiler on the first connection....
|
||||
client1.request(
|
||||
{ to: actor1, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
|
||||
do_check_true(Profiler.IsActive());
|
||||
do_check_true(response.started);
|
||||
do_check_true(typeof response.position === "number");
|
||||
do_check_true(typeof response.totalSize === "number");
|
||||
do_check_true(typeof response.generation === "number");
|
||||
do_check_true(response.position >= 0 && response.position < response.totalSize);
|
||||
do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
|
||||
|
||||
// On the next connection just make sure the actor has been instantiated.
|
||||
client2.request({ to: actor2, type: "isActive" }, response => {
|
||||
do_check_true(Profiler.IsActive());
|
||||
do_check_true(response.isActive);
|
||||
do_check_true(response.currentTime > 0);
|
||||
do_check_true(typeof response.position === "number");
|
||||
do_check_true(typeof response.totalSize === "number");
|
||||
do_check_true(typeof response.generation === "number");
|
||||
do_check_true(response.position >= 0 && response.position < response.totalSize);
|
||||
do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
|
||||
|
||||
let origConnectionClosed = DebuggerServer._connectionClosed;
|
||||
|
||||
DebuggerServer._connectionClosed = function (conn) {
|
||||
origConnectionClosed.call(this, conn);
|
||||
|
||||
// The first client is the only actor that started the profiler,
|
||||
// however the second client can request the accumulated profile data
|
||||
// at any moment, so the profiler module shouldn't have deactivated.
|
||||
do_check_true(Profiler.IsActive());
|
||||
|
||||
DebuggerServer._connectionClosed = function (conn) {
|
||||
origConnectionClosed.call(this, conn);
|
||||
|
||||
// Now there are no open clients at all, it should *definitely*
|
||||
// be deactivated by now.
|
||||
do_check_false(Profiler.IsActive());
|
||||
|
||||
callback();
|
||||
};
|
||||
client2.close();
|
||||
};
|
||||
client1.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether the profiler actor correctly handles the case where the
|
||||
* built-in module was already started.
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
function run_test() {
|
||||
// Ensure the profiler is already running when the test starts.
|
||||
Profiler.StartProfiler(1000000, 1, ["js"], 1);
|
||||
|
||||
DevToolsUtils.waitForTime(WAIT_TIME).then(() => {
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
test_start_time(client, actor, () => {
|
||||
client.close().then(do_test_finished);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_start_time(client, actor, callback) {
|
||||
// Profiler should already be active at this point.
|
||||
client.request({ to: actor, type: "isActive" }, firstResponse => {
|
||||
do_check_true(Profiler.IsActive());
|
||||
do_check_true(firstResponse.isActive);
|
||||
do_check_true(firstResponse.currentTime > 0);
|
||||
|
||||
client.request({ to: actor, type: "getProfile" }, secondResponse => {
|
||||
do_check_true("profile" in secondResponse);
|
||||
do_check_true(secondResponse.currentTime > firstResponse.currentTime);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow, max-nested-callbacks */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the profiler actor returns its buffer status via getBufferInfo.
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
const INITIAL_WAIT_TIME = 100; // ms
|
||||
const MAX_WAIT_TIME = 20000; // ms
|
||||
const MAX_PROFILER_ENTRIES = 10000000;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function run_test() {
|
||||
// Ensure the profiler is not running when the test starts (it could
|
||||
// happen if the MOZ_PROFILER_STARTUP environment variable is set).
|
||||
Profiler.StopProfiler();
|
||||
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
check_empty_buffer(client, actor, () => {
|
||||
activate_profiler(client, actor, startTime => {
|
||||
wait_for_samples(client, actor, () => {
|
||||
check_buffer(client, actor, () => {
|
||||
deactivate_profiler(client, actor, () => {
|
||||
client.close().then(do_test_finished);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function check_buffer(client, actor, callback) {
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(typeof response.position === "number");
|
||||
do_check_true(typeof response.totalSize === "number");
|
||||
do_check_true(typeof response.generation === "number");
|
||||
do_check_true(response.position > 0 && response.position < response.totalSize);
|
||||
do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
|
||||
// There's no way we'll fill the buffer in this test.
|
||||
do_check_true(response.generation === 0);
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function check_empty_buffer(client, actor, callback) {
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_false(Profiler.IsActive());
|
||||
do_check_false(response.isActive);
|
||||
do_check_true(response.position === void 0);
|
||||
do_check_true(response.totalSize === void 0);
|
||||
do_check_true(response.generation === void 0);
|
||||
do_check_false(response.isActive);
|
||||
do_check_eq(response.currentTime, undefined);
|
||||
calback();
|
||||
});
|
||||
}
|
||||
|
||||
function activate_profiler(client, actor, callback) {
|
||||
client.request(
|
||||
{ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
|
||||
do_check_true(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(response.isActive);
|
||||
callback(response.currentTime);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deactivate_profiler(client, actor, callback) {
|
||||
client.request({ to: actor, type: "stopProfiler" }, response => {
|
||||
do_check_false(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_false(response.isActive);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function wait_for_samples(client, actor, callback) {
|
||||
function attempt(delay) {
|
||||
// Spin for the requested time, then take a sample.
|
||||
let start = Date.now();
|
||||
|
||||
do_print("Attempt: delay = " + delay);
|
||||
while (Date.now() - start < delay) {
|
||||
/* Empty */
|
||||
}
|
||||
do_print("Attempt: finished waiting.");
|
||||
|
||||
client.request({ to: actor, type: "getProfile" }, response => {
|
||||
// At this point, we may or may not have samples, depending on
|
||||
// whether the spin loop above has given the profiler enough time
|
||||
// to get started.
|
||||
if (response.profile.threads[0].samples.length == 0) {
|
||||
if (delay < MAX_WAIT_TIME) {
|
||||
// Double the spin-wait time and try again.
|
||||
do_print("Attempt: no samples, going around again.");
|
||||
attempt(delay * 2);
|
||||
} else {
|
||||
// We've waited long enough, so just fail.
|
||||
do_print("Attempt: waited a long time, but no samples were collected.");
|
||||
do_print("Giving up.");
|
||||
do_check_true(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
// Start off with a 100 millisecond delay.
|
||||
attempt(INITIAL_WAIT_TIME);
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow, max-nested-callbacks */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether the profiler module is kept active when there are multiple
|
||||
* client consumers and one requests deactivation.
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
|
||||
function run_test() {
|
||||
get_chrome_actors((client1, form1) => {
|
||||
let actor1 = form1.profilerActor;
|
||||
get_chrome_actors((client2, form2) => {
|
||||
let actor2 = form2.profilerActor;
|
||||
test_close(client1, actor1, client2, actor2, () => {
|
||||
client1.close(() => {
|
||||
client2.close(() => {
|
||||
do_test_finished();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function activate_profiler(client, actor, callback) {
|
||||
client.request({ to: actor, type: "startProfiler" }, response => {
|
||||
do_check_true(response.started);
|
||||
do_check_true(Profiler.IsActive());
|
||||
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(response.isActive);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deactivate_profiler(client, actor, callback) {
|
||||
client.request({ to: actor, type: "stopProfiler" }, response => {
|
||||
do_check_false(response.started);
|
||||
do_check_true(Profiler.IsActive());
|
||||
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(response.isActive);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_close(client1, actor1, client2, actor2, callback) {
|
||||
activate_profiler(client1, actor1, () => {
|
||||
activate_profiler(client2, actor2, () => {
|
||||
deactivate_profiler(client1, actor1, () => {
|
||||
deactivate_profiler(client2, actor2, () => {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow, max-nested-callbacks */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the profiler actor can correctly retrieve a profile after
|
||||
* it is activated.
|
||||
*/
|
||||
|
||||
const INITIAL_WAIT_TIME = 100; // ms
|
||||
const MAX_WAIT_TIME = 20000; // ms
|
||||
|
||||
function run_test() {
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
activate_profiler(client, actor, startTime => {
|
||||
test_data(client, actor, startTime, () => {
|
||||
deactivate_profiler(client, actor, () => {
|
||||
client.close().then(do_test_finished);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function activate_profiler(client, actor, callback) {
|
||||
client.request({ to: actor, type: "startProfiler" }, response => {
|
||||
do_check_true(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(response.isActive);
|
||||
callback(response.currentTime);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deactivate_profiler(client, actor, callback) {
|
||||
client.request({ to: actor, type: "stopProfiler" }, response => {
|
||||
do_check_false(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_false(response.isActive);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_data(client, actor, startTime, callback) {
|
||||
function attempt(delay) {
|
||||
// No idea why, but Components.stack.sourceLine returns null.
|
||||
let funcLine = Components.stack.lineNumber - 2;
|
||||
|
||||
// Spin for the requested time, then take a sample.
|
||||
let start = Date.now();
|
||||
let stack;
|
||||
do_print("Attempt: delay = " + delay);
|
||||
while (Date.now() - start < delay) {
|
||||
stack = Components.stack;
|
||||
}
|
||||
do_print("Attempt: finished waiting.");
|
||||
|
||||
client.request({ to: actor, type: "getProfile", startTime }, response => {
|
||||
// Any valid getProfile response should have the following top
|
||||
// level structure.
|
||||
do_check_eq(typeof response.profile, "object");
|
||||
do_check_eq(typeof response.profile.meta, "object");
|
||||
do_check_eq(typeof response.profile.meta.platform, "string");
|
||||
do_check_eq(typeof response.profile.threads, "object");
|
||||
do_check_eq(typeof response.profile.threads[0], "object");
|
||||
do_check_eq(typeof response.profile.threads[0].samples, "object");
|
||||
|
||||
// At this point, we may or may not have samples, depending on
|
||||
// whether the spin loop above has given the profiler enough time
|
||||
// to get started.
|
||||
if (response.profile.threads[0].samples.length == 0) {
|
||||
if (delay < MAX_WAIT_TIME) {
|
||||
// Double the spin-wait time and try again.
|
||||
do_print("Attempt: no samples, going around again.");
|
||||
attempt(delay * 2);
|
||||
} else {
|
||||
// We've waited long enough, so just fail.
|
||||
do_print("Attempt: waited a long time, but no samples were collected.");
|
||||
do_print("Giving up.");
|
||||
do_check_true(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check the samples. At least one sample is expected to
|
||||
// have been in the busy wait above.
|
||||
let loc = stack.name + " (" + stack.filename + ":" + funcLine + ")";
|
||||
let thread0 = response.profile.threads[0];
|
||||
do_check_true(thread0.samples.data.some(sample => {
|
||||
let frames = getInflatedStackLocations(thread0, sample);
|
||||
return frames.length != 0 &&
|
||||
frames.some(location => (location == loc));
|
||||
}));
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
// Start off with a 100 millisecond delay.
|
||||
attempt(INITIAL_WAIT_TIME);
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests the event notification service for the profiler actor.
|
||||
*/
|
||||
|
||||
const { ProfilerFront } = require("devtools/shared/fronts/profiler");
|
||||
|
||||
add_task(function* () {
|
||||
let [client, form] = yield getChromeActors();
|
||||
let front = new ProfilerFront(client, form);
|
||||
|
||||
let events = [0, 0, 0, 0];
|
||||
front.on("console-api-profiler", () => events[0]++);
|
||||
front.on("profiler-started", () => events[1]++);
|
||||
front.on("profiler-stopped", () => events[2]++);
|
||||
client.addListener("eventNotification", (type, response) => {
|
||||
do_check_true(type === "eventNotification");
|
||||
events[3]++;
|
||||
});
|
||||
|
||||
yield front.startProfiler();
|
||||
yield front.stopProfiler();
|
||||
|
||||
// All should be empty without binding events
|
||||
do_check_true(events[0] === 0);
|
||||
do_check_true(events[1] === 0);
|
||||
do_check_true(events[2] === 0);
|
||||
do_check_true(events[3] === 0);
|
||||
|
||||
let ret = yield front.registerEventNotifications(
|
||||
{ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] });
|
||||
do_check_true(ret.registered.length === 3);
|
||||
|
||||
yield front.startProfiler();
|
||||
do_check_true(events[0] === 0);
|
||||
do_check_true(events[1] === 1);
|
||||
do_check_true(events[2] === 0);
|
||||
do_check_true(events[3] === 1, "compatibility events supported for eventNotifications");
|
||||
|
||||
yield front.stopProfiler();
|
||||
do_check_true(events[0] === 0);
|
||||
do_check_true(events[1] === 1);
|
||||
do_check_true(events[2] === 1);
|
||||
do_check_true(events[3] === 2, "compatibility events supported for eventNotifications");
|
||||
|
||||
ret = yield front.unregisterEventNotifications(
|
||||
{ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] });
|
||||
do_check_true(ret.registered.length === 3);
|
||||
});
|
||||
|
||||
function getChromeActors() {
|
||||
let deferred = defer();
|
||||
get_chrome_actors((client, form) => deferred.resolve([client, form]));
|
||||
return deferred.promise;
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests the event notification service for the profiler actor.
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
const MAX_PROFILER_ENTRIES = 10000000;
|
||||
const { ProfilerFront } = require("devtools/shared/fronts/profiler");
|
||||
const { waitForTime } = DevToolsUtils;
|
||||
|
||||
add_task(function* () {
|
||||
let [client, form] = yield getChromeActors();
|
||||
let front = new ProfilerFront(client, form);
|
||||
|
||||
// Ensure the profiler is not running when the test starts (it could
|
||||
// happen if the MOZ_PROFILER_STARTUP environment variable is set).
|
||||
Profiler.StopProfiler();
|
||||
let eventsCalled = 0;
|
||||
let handledThreeTimes = defer();
|
||||
|
||||
front.on("profiler-status", (response) => {
|
||||
dump("'profiler-status' fired\n");
|
||||
do_check_true(typeof response.position === "number");
|
||||
do_check_true(typeof response.totalSize === "number");
|
||||
do_check_true(typeof response.generation === "number");
|
||||
do_check_true(response.position > 0 && response.position < response.totalSize);
|
||||
do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
|
||||
// There's no way we'll fill the buffer in this test.
|
||||
do_check_true(response.generation === 0);
|
||||
|
||||
eventsCalled++;
|
||||
if (eventsCalled > 2) {
|
||||
handledThreeTimes.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
yield front.setProfilerStatusInterval(1);
|
||||
dump("Set the profiler-status event interval to 1\n");
|
||||
yield front.startProfiler();
|
||||
yield waitForTime(500);
|
||||
yield front.stopProfiler();
|
||||
|
||||
do_check_true(eventsCalled === 0,
|
||||
"No 'profiler-status' events should be fired before registering.");
|
||||
|
||||
let ret = yield front.registerEventNotifications({ events: ["profiler-status"] });
|
||||
do_check_true(ret.registered.length === 1);
|
||||
|
||||
yield front.startProfiler();
|
||||
yield handledThreeTimes.promise;
|
||||
yield front.stopProfiler();
|
||||
do_check_true(eventsCalled >= 3,
|
||||
"profiler-status fired atleast three times while recording");
|
||||
|
||||
let totalEvents = eventsCalled;
|
||||
yield waitForTime(50);
|
||||
do_check_true(totalEvents === eventsCalled,
|
||||
"No more profiler-status events after recording.");
|
||||
});
|
||||
|
||||
function getChromeActors() {
|
||||
let deferred = defer();
|
||||
get_chrome_actors((client, form) => deferred.resolve([client, form]));
|
||||
return deferred.promise;
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow, max-nested-callbacks */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the profiler actor returns its buffer status via getBufferInfo.
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
const INITIAL_WAIT_TIME = 100; // ms
|
||||
const MAX_WAIT_TIME = 20000; // ms
|
||||
const MAX_PROFILER_ENTRIES = 10000000;
|
||||
|
||||
function run_test() {
|
||||
// Ensure the profiler is not running when the test starts (it could
|
||||
// happen if the MOZ_PROFILER_STARTUP environment variable is set).
|
||||
Profiler.StopProfiler();
|
||||
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
check_empty_buffer(client, actor, () => {
|
||||
activate_profiler(client, actor, startTime => {
|
||||
wait_for_samples(client, actor, () => {
|
||||
check_buffer(client, actor, () => {
|
||||
deactivate_profiler(client, actor, () => {
|
||||
client.close().then(do_test_finished);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function check_empty_buffer(client, actor, callback) {
|
||||
client.request({ to: actor, type: "getBufferInfo" }, response => {
|
||||
do_check_true(response.position === 0);
|
||||
do_check_true(response.totalSize === 0);
|
||||
do_check_true(response.generation === 0);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function check_buffer(client, actor, callback) {
|
||||
client.request({ to: actor, type: "getBufferInfo" }, response => {
|
||||
do_check_true(typeof response.position === "number");
|
||||
do_check_true(typeof response.totalSize === "number");
|
||||
do_check_true(typeof response.generation === "number");
|
||||
do_check_true(response.position > 0 && response.position < response.totalSize);
|
||||
do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
|
||||
// There's no way we'll fill the buffer in this test.
|
||||
do_check_true(response.generation === 0);
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function activate_profiler(client, actor, callback) {
|
||||
client.request(
|
||||
{ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
|
||||
do_check_true(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(response.isActive);
|
||||
callback(response.currentTime);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deactivate_profiler(client, actor, callback) {
|
||||
client.request({ to: actor, type: "stopProfiler" }, response => {
|
||||
do_check_false(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_false(response.isActive);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function wait_for_samples(client, actor, callback) {
|
||||
function attempt(delay) {
|
||||
// Spin for the requested time, then take a sample.
|
||||
let start = Date.now();
|
||||
|
||||
do_print("Attempt: delay = " + delay);
|
||||
/* eslint-disable no-empty */
|
||||
while (Date.now() - start < delay) {}
|
||||
do_print("Attempt: finished waiting.");
|
||||
|
||||
client.request({ to: actor, type: "getProfile" }, response => {
|
||||
// At this point, we may or may not have samples, depending on
|
||||
// whether the spin loop above has given the profiler enough time
|
||||
// to get started.
|
||||
if (response.profile.threads[0].samples.length == 0) {
|
||||
if (delay < MAX_WAIT_TIME) {
|
||||
// Double the spin-wait time and try again.
|
||||
do_print("Attempt: no samples, going around again.");
|
||||
attempt(delay * 2);
|
||||
} else {
|
||||
// We've waited long enough, so just fail.
|
||||
do_print("Attempt: waited a long time, but no samples were collected.");
|
||||
do_print("Giving up.");
|
||||
do_check_true(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
// Start off with a 100 millisecond delay.
|
||||
attempt(INITIAL_WAIT_TIME);
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether the profiler responds to "getFeatures" adequately.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
test_getfeatures(client, actor, () => {
|
||||
client.close().then(() => {
|
||||
do_test_finished();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_getfeatures(client, actor, callback) {
|
||||
client.request({ to: actor, type: "getFeatures" }, response => {
|
||||
do_check_eq(typeof response.features, "object");
|
||||
do_check_true(response.features.length >= 1);
|
||||
do_check_eq(typeof response.features[0], "string");
|
||||
do_check_true(response.features.indexOf("js") != -1);
|
||||
callback();
|
||||
});
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether the profiler responds to "sharedLibraries" adequately.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
test_sharedlibraries(client, actor, () => {
|
||||
client.close().then(() => {
|
||||
do_test_finished();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_sharedlibraries(client, actor, callback) {
|
||||
client.request({ to: actor, type: "sharedLibraries" }, response => {
|
||||
const libs = response.sharedLibraries;
|
||||
do_check_eq(typeof libs, "object");
|
||||
do_check_true(Array.isArray(libs));
|
||||
do_check_eq(typeof libs, "object");
|
||||
do_check_true(libs.length >= 1);
|
||||
do_check_eq(typeof libs[0], "object");
|
||||
do_check_eq(typeof libs[0].name, "string");
|
||||
do_check_eq(typeof libs[0].path, "string");
|
||||
do_check_eq(typeof libs[0].debugName, "string");
|
||||
do_check_eq(typeof libs[0].debugPath, "string");
|
||||
do_check_eq(typeof libs[0].arch, "string");
|
||||
do_check_eq(typeof libs[0].start, "number");
|
||||
do_check_eq(typeof libs[0].end, "number");
|
||||
do_check_true(libs[0].start <= libs[0].end);
|
||||
callback();
|
||||
});
|
||||
}
|
@ -200,15 +200,6 @@ reason = only ran on B2G
|
||||
[test_source-01.js]
|
||||
[test_wasm_source-01.js]
|
||||
[test_breakpoint-actor-map.js]
|
||||
[test_profiler_activation-01.js]
|
||||
[test_profiler_activation-02.js]
|
||||
[test_profiler_close.js]
|
||||
[test_profiler_data.js]
|
||||
[test_profiler_events-01.js]
|
||||
[test_profiler_events-02.js]
|
||||
[test_profiler_getbufferinfo.js]
|
||||
[test_profiler_getfeatures.js]
|
||||
[test_profiler_sharedlibraries.js]
|
||||
[test_unsafeDereference.js]
|
||||
[test_add_actors.js]
|
||||
[test_ignore_caught_exceptions.js]
|
||||
|
@ -5,12 +5,13 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Manages the addon-sdk loader instance used to load the developer tools.
|
||||
* Manages the base loader (base-loader.js) instance used to load the developer tools.
|
||||
*/
|
||||
|
||||
var { utils: Cu } = Components;
|
||||
var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
var { Loader, descriptor, resolveURI } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
var { Loader, Require, resolveURI, unload } =
|
||||
Cu.import("resource://devtools/shared/base-loader.js", {});
|
||||
var { requireRawId } = Cu.import("resource://devtools/shared/loader-plugin-raw.jsm", {});
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools", "BuiltinProvider",
|
||||
@ -67,14 +68,11 @@ BuiltinProvider.prototype = {
|
||||
if (this.invisibleToDebugger) {
|
||||
paths.promise = "resource://gre/modules/Promise-backend.js";
|
||||
}
|
||||
this.loader = new Loader.Loader({
|
||||
id: "fx-devtools",
|
||||
this.loader = new Loader({
|
||||
paths,
|
||||
invisibleToDebugger: this.invisibleToDebugger,
|
||||
sharedGlobal: true,
|
||||
sharedGlobalBlocklist: [],
|
||||
sandboxName: "DevTools (Module loader)",
|
||||
noSandboxAddonId: true,
|
||||
requireHook: (id, require) => {
|
||||
if (id.startsWith("raw!")) {
|
||||
return requireRawId(id, require);
|
||||
@ -85,7 +83,7 @@ BuiltinProvider.prototype = {
|
||||
},
|
||||
|
||||
unload: function (reason) {
|
||||
Loader.unload(this.loader, reason);
|
||||
unload(this.loader, reason);
|
||||
delete this.loader;
|
||||
},
|
||||
};
|
||||
@ -168,7 +166,7 @@ DevToolsLoader.prototype = {
|
||||
this._provider.invisibleToDebugger = this.invisibleToDebugger;
|
||||
|
||||
this._provider.load();
|
||||
this.require = Loader.Require(this._provider.loader, { id: "devtools" });
|
||||
this.require = Require(this._provider.loader, { id: "devtools" });
|
||||
|
||||
// Fetch custom pseudo modules and globals
|
||||
let { modules, globals } = this.require("devtools/shared/builtin-modules");
|
||||
@ -193,7 +191,7 @@ DevToolsLoader.prototype = {
|
||||
|
||||
// Register custom globals to the current loader instance
|
||||
globals.loader.id = this.id;
|
||||
Object.defineProperties(loader.globals, descriptor(globals));
|
||||
Object.defineProperties(loader.globals, Object.getOwnPropertyDescriptors(globals));
|
||||
|
||||
// Expose lazy helpers on loader
|
||||
this.lazyGetter = globals.loader.lazyGetter;
|
||||
|
682
devtools/shared/base-loader.js
Normal file
682
devtools/shared/base-loader.js
Normal file
@ -0,0 +1,682 @@
|
||||
/* 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/. */
|
||||
|
||||
/* global __URI__ */
|
||||
/* exported Loader, resolveURI, Module, Require, unload */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Loader", "resolveURI", "Module", "Require", "unload"];
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
|
||||
results: Cr, manager: Cm } = Components;
|
||||
const systemPrincipal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
|
||||
const { loadSubScript } = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
const { notifyObservers } = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
const { normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "resProto",
|
||||
"@mozilla.org/network/protocol;1?name=resource",
|
||||
"nsIResProtocolHandler");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
const { defineLazyGetter } = XPCOMUtils;
|
||||
|
||||
// Define some shortcuts.
|
||||
const bind = Function.call.bind(Function.bind);
|
||||
function* getOwnIdentifiers(x) {
|
||||
yield* Object.getOwnPropertyNames(x);
|
||||
yield* Object.getOwnPropertySymbols(x);
|
||||
}
|
||||
|
||||
function isJSONURI(uri) {
|
||||
return uri.endsWith(".json");
|
||||
}
|
||||
function isJSMURI(uri) {
|
||||
return uri.endsWith(".jsm");
|
||||
}
|
||||
function isJSURI(uri) {
|
||||
return uri.endsWith(".js");
|
||||
}
|
||||
const AbsoluteRegExp = /^(resource|chrome|file|jar):/;
|
||||
function isAbsoluteURI(uri) {
|
||||
return AbsoluteRegExp.test(uri);
|
||||
}
|
||||
function isRelative(id) {
|
||||
return id.startsWith(".");
|
||||
}
|
||||
|
||||
function sourceURI(uri) {
|
||||
return String(uri).split(" -> ").pop();
|
||||
}
|
||||
|
||||
function isntLoaderFrame(frame) {
|
||||
return frame.fileName !== __URI__;
|
||||
}
|
||||
|
||||
function parseURI(uri) {
|
||||
return String(uri).split(" -> ").pop();
|
||||
}
|
||||
|
||||
function parseStack(stack) {
|
||||
let lines = String(stack).split("\n");
|
||||
return lines.reduce(function (frames, line) {
|
||||
if (line) {
|
||||
let atIndex = line.indexOf("@");
|
||||
let columnIndex = line.lastIndexOf(":");
|
||||
let lineIndex = line.lastIndexOf(":", columnIndex - 1);
|
||||
let fileName = parseURI(line.slice(atIndex + 1, lineIndex));
|
||||
let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex), 10);
|
||||
let columnNumber = parseInt(line.slice(columnIndex + 1), 10);
|
||||
let name = line.slice(0, atIndex).split("(").shift();
|
||||
frames.unshift({
|
||||
fileName: fileName,
|
||||
name: name,
|
||||
lineNumber: lineNumber,
|
||||
columnNumber: columnNumber
|
||||
});
|
||||
}
|
||||
return frames;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function serializeStack(frames) {
|
||||
return frames.reduce(function (stack, frame) {
|
||||
return frame.name + "@" +
|
||||
frame.fileName + ":" +
|
||||
frame.lineNumber + ":" +
|
||||
frame.columnNumber + "\n" +
|
||||
stack;
|
||||
}, "");
|
||||
}
|
||||
|
||||
function readURI(uri) {
|
||||
let nsURI = NetUtil.newURI(uri);
|
||||
if (nsURI.scheme == "resource") {
|
||||
// Resolve to a real URI, this will catch any obvious bad paths without
|
||||
// logging assertions in debug builds, see bug 1135219
|
||||
uri = resProto.resolveURI(nsURI);
|
||||
}
|
||||
|
||||
let stream = NetUtil.newChannel({
|
||||
uri: NetUtil.newURI(uri, "UTF-8"),
|
||||
loadUsingSystemPrincipal: true}
|
||||
).open2();
|
||||
let count = stream.available();
|
||||
let data = NetUtil.readInputStreamToString(stream, count, {
|
||||
charset: "UTF-8"
|
||||
});
|
||||
|
||||
stream.close();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Combines all arguments into a resolved, normalized path
|
||||
function join(base, ...paths) {
|
||||
// If this is an absolute URL, we need to normalize only the path portion,
|
||||
// or we wind up stripping too many slashes and producing invalid URLs.
|
||||
let match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(base);
|
||||
if (match) {
|
||||
return match[1] + normalize([match[2], ...paths].join("/"));
|
||||
}
|
||||
|
||||
return normalize([base, ...paths].join("/"));
|
||||
}
|
||||
|
||||
// Function takes set of options and returns a JS sandbox. Function may be
|
||||
// passed set of options:
|
||||
// - `name`: A string value which identifies the sandbox in about:memory. Will
|
||||
// throw exception if omitted.
|
||||
// - `prototype`: Ancestor for the sandbox that will be created. Defaults to
|
||||
// `{}`.
|
||||
// - `invisibleToDebugger`: True, if the sandbox is part of the debugger
|
||||
// implementation and should not be tracked by debugger API.
|
||||
// For more details see:
|
||||
// https://developer.mozilla.org/en/Components.utils.Sandbox
|
||||
function Sandbox(options) {
|
||||
// Normalize options and rename to match `Cu.Sandbox` expectations.
|
||||
options = {
|
||||
// Do not expose `Components` if you really need them (bad idea!) you
|
||||
// still can expose via prototype.
|
||||
wantComponents: false,
|
||||
sandboxName: options.name,
|
||||
sandboxPrototype: "prototype" in options ? options.prototype : {},
|
||||
invisibleToDebugger: "invisibleToDebugger" in options ?
|
||||
options.invisibleToDebugger : false,
|
||||
waiveInterposition: false
|
||||
};
|
||||
|
||||
let sandbox = Cu.Sandbox(systemPrincipal, options);
|
||||
|
||||
delete sandbox.Components;
|
||||
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
// Populates `exports` of the given CommonJS `module` object, in the context
|
||||
// of the given `loader` by evaluating code associated with it.
|
||||
function load(loader, module) {
|
||||
let { sandboxes, globals } = loader;
|
||||
let require = Require(loader, module);
|
||||
|
||||
// We expose set of properties defined by `CommonJS` specification via
|
||||
// prototype of the sandbox. Also globals are deeper in the prototype
|
||||
// chain so that each module has access to them as well.
|
||||
let descriptors = {
|
||||
require: {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: require
|
||||
},
|
||||
module: {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: module
|
||||
},
|
||||
exports: {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: module.exports
|
||||
},
|
||||
};
|
||||
|
||||
let sandbox;
|
||||
if (loader.useSharedGlobalSandbox) {
|
||||
// Create a new object in this sandbox, that will be used as
|
||||
// the scope object for this particular module
|
||||
sandbox = new loader.sharedGlobalSandbox.Object();
|
||||
descriptors.lazyRequire = {
|
||||
configurable: true,
|
||||
value: lazyRequire.bind(sandbox),
|
||||
};
|
||||
descriptors.lazyRequireModule = {
|
||||
configurable: true,
|
||||
value: lazyRequireModule.bind(sandbox),
|
||||
};
|
||||
|
||||
if ("console" in globals) {
|
||||
descriptors.console = {
|
||||
configurable: true,
|
||||
get() {
|
||||
return globals.console;
|
||||
},
|
||||
};
|
||||
}
|
||||
let define = Object.getOwnPropertyDescriptor(globals, "define");
|
||||
if (define && define.value) {
|
||||
descriptors.define = define;
|
||||
}
|
||||
if ("DOMParser" in globals) {
|
||||
descriptors.DOMParser = Object.getOwnPropertyDescriptor(globals, "DOMParser");
|
||||
}
|
||||
Object.defineProperties(sandbox, descriptors);
|
||||
} else {
|
||||
sandbox = Sandbox({
|
||||
name: module.uri,
|
||||
prototype: Object.create(globals, descriptors),
|
||||
invisibleToDebugger: loader.invisibleToDebugger
|
||||
});
|
||||
}
|
||||
sandboxes[module.uri] = sandbox;
|
||||
|
||||
let originalExports = module.exports;
|
||||
try {
|
||||
loadSubScript(module.uri, sandbox, "UTF-8");
|
||||
} catch (error) {
|
||||
let { message, fileName, lineNumber } = error;
|
||||
let stack = error.stack || Error().stack;
|
||||
let frames = parseStack(stack).filter(isntLoaderFrame);
|
||||
let toString = String(error);
|
||||
let file = sourceURI(fileName);
|
||||
|
||||
// Note that `String(error)` where error is from subscript loader does
|
||||
// not puts `:` after `"Error"` unlike regular errors thrown by JS code.
|
||||
// If there is a JS stack then this error has already been handled by an
|
||||
// inner module load.
|
||||
if (/^Error opening input stream/.test(String(error))) {
|
||||
let caller = frames.slice(0).pop();
|
||||
fileName = caller.fileName;
|
||||
lineNumber = caller.lineNumber;
|
||||
message = "Module `" + module.id + "` is not found at " + module.uri;
|
||||
toString = message;
|
||||
} else if (frames[frames.length - 1].fileName !== file) {
|
||||
// Workaround for a Bug 910653. Errors thrown by subscript loader
|
||||
// do not include `stack` field and above created error won't have
|
||||
// fileName or lineNumber of the module being loaded, so we ensure
|
||||
// it does.
|
||||
frames.push({ fileName: file, lineNumber: lineNumber, name: "" });
|
||||
}
|
||||
|
||||
let prototype = typeof (error) === "object" ? error.constructor.prototype :
|
||||
Error.prototype;
|
||||
|
||||
throw Object.create(prototype, {
|
||||
message: { value: message, writable: true, configurable: true },
|
||||
fileName: { value: fileName, writable: true, configurable: true },
|
||||
lineNumber: { value: lineNumber, writable: true, configurable: true },
|
||||
stack: { value: serializeStack(frames), writable: true, configurable: true },
|
||||
toString: { value: () => toString, writable: true, configurable: true },
|
||||
});
|
||||
}
|
||||
|
||||
// Only freeze the exports object if we created it ourselves. Modules
|
||||
// which completely replace the exports object and still want it
|
||||
// frozen need to freeze it themselves.
|
||||
if (module.exports === originalExports) {
|
||||
Object.freeze(module.exports);
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
// Utility function to normalize module `uri`s so they have `.js` extension.
|
||||
function normalizeExt(uri) {
|
||||
if (isJSURI(uri) || isJSONURI(uri) || isJSMURI(uri)) {
|
||||
return uri;
|
||||
}
|
||||
return uri + ".js";
|
||||
}
|
||||
|
||||
// Utility function to join paths. In common case `base` is a
|
||||
// `requirer.uri` but in some cases it may be `baseURI`. In order to
|
||||
// avoid complexity we require `baseURI` with a trailing `/`.
|
||||
function resolve(id, base) {
|
||||
if (!isRelative(id)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
let baseDir = dirname(base);
|
||||
|
||||
let resolved;
|
||||
if (baseDir.includes(":")) {
|
||||
resolved = join(baseDir, id);
|
||||
} else {
|
||||
resolved = normalize(`${baseDir}/${id}`);
|
||||
}
|
||||
|
||||
// Joining and normalizing removes the "./" from relative files.
|
||||
// We need to ensure the resolution still has the root
|
||||
if (base.startsWith("./")) {
|
||||
resolved = "./" + resolved;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function compileMapping(paths) {
|
||||
// Make mapping array that is sorted from longest path to shortest path.
|
||||
let mapping = Object.keys(paths)
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.map(path => [path, paths[path]]);
|
||||
|
||||
const PATTERN = /([.\\?+*(){}[\]^$])/g;
|
||||
const escapeMeta = str => str.replace(PATTERN, "\\$1");
|
||||
|
||||
let patterns = [];
|
||||
paths = {};
|
||||
|
||||
for (let [path, uri] of mapping) {
|
||||
// Strip off any trailing slashes to make comparisons simpler
|
||||
if (path.endsWith("/")) {
|
||||
path = path.slice(0, -1);
|
||||
uri = uri.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
paths[path] = uri;
|
||||
|
||||
// We only want to match path segments explicitly. Examples:
|
||||
// * "foo/bar" matches for "foo/bar"
|
||||
// * "foo/bar" matches for "foo/bar/baz"
|
||||
// * "foo/bar" does not match for "foo/bar-1"
|
||||
// * "foo/bar/" does not match for "foo/bar"
|
||||
// * "foo/bar/" matches for "foo/bar/baz"
|
||||
//
|
||||
// Check for an empty path, an exact match, or a substring match
|
||||
// with the next character being a forward slash.
|
||||
if (path == "") {
|
||||
patterns.push("");
|
||||
} else {
|
||||
patterns.push(`${escapeMeta(path)}(?=$|/)`);
|
||||
}
|
||||
}
|
||||
|
||||
let pattern = new RegExp(`^(${patterns.join("|")})`);
|
||||
|
||||
// This will replace the longest matching path mapping at the start of
|
||||
// the ID string with its mapped value.
|
||||
return id => {
|
||||
return id.replace(pattern, (m0, m1) => paths[m1]);
|
||||
};
|
||||
}
|
||||
|
||||
function resolveURI(id, mapping) {
|
||||
// Do not resolve if already a resource URI
|
||||
if (isAbsoluteURI(id)) {
|
||||
return normalizeExt(id);
|
||||
}
|
||||
|
||||
return normalizeExt(mapping(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines lazy getters on the given object, which lazily require the
|
||||
* given module the first time they are accessed, and then resolve that
|
||||
* module's exported properties.
|
||||
*
|
||||
* @param {object} obj
|
||||
* The target object on which to define the lazy getters.
|
||||
* @param {string} moduleId
|
||||
* The ID of the module to require, as passed to require().
|
||||
* @param {Array<string | object>} args
|
||||
* Any number of properties to import from the module. A string
|
||||
* will cause the property to be defined which resolves to the
|
||||
* same property in the module's exports. An object will define a
|
||||
* lazy getter for every value in the object which corresponds to
|
||||
* the given key in the module's exports, as in an ordinary
|
||||
* destructuring assignment.
|
||||
*/
|
||||
function lazyRequire(obj, moduleId, ...args) {
|
||||
let module;
|
||||
let getModule = () => {
|
||||
if (!module) {
|
||||
module = this.require(moduleId);
|
||||
}
|
||||
return module;
|
||||
};
|
||||
|
||||
for (let props of args) {
|
||||
if (typeof props !== "object") {
|
||||
props = {[props]: props};
|
||||
}
|
||||
|
||||
for (let [fromName, toName] of Object.entries(props)) {
|
||||
defineLazyGetter(obj, toName, () => getModule()[fromName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a lazy getter on the given object which causes a module to be
|
||||
* lazily imported the first time it is accessed.
|
||||
*
|
||||
* @param {object} obj
|
||||
* The target object on which to define the lazy getter.
|
||||
* @param {string} moduleId
|
||||
* The ID of the module to require, as passed to require().
|
||||
* @param {string} [prop = moduleId]
|
||||
* The name of the lazy getter property to define.
|
||||
*/
|
||||
function lazyRequireModule(obj, moduleId, prop = moduleId) {
|
||||
defineLazyGetter(obj, prop, () => this.require(moduleId));
|
||||
}
|
||||
|
||||
// Creates version of `require` that will be exposed to the given `module`
|
||||
// in the context of the given `loader`. Each module gets own limited copy
|
||||
// of `require` that is allowed to load only a modules that are associated
|
||||
// with it during link time.
|
||||
function Require(loader, requirer) {
|
||||
let {
|
||||
modules, mapping, mappingCache, requireHook
|
||||
} = loader;
|
||||
|
||||
function require(id) {
|
||||
if (!id) {
|
||||
// Throw if `id` is not passed.
|
||||
throw Error("You must provide a module name when calling require() from "
|
||||
+ requirer.id, requirer.uri);
|
||||
}
|
||||
|
||||
if (requireHook) {
|
||||
return requireHook(id, _require);
|
||||
}
|
||||
|
||||
return _require(id);
|
||||
}
|
||||
|
||||
function _require(id) {
|
||||
let { uri, requirement } = getRequirements(id);
|
||||
|
||||
let module = null;
|
||||
// If module is already cached by loader then just use it.
|
||||
if (uri in modules) {
|
||||
module = modules[uri];
|
||||
} else if (isJSMURI(uri)) {
|
||||
module = modules[uri] = Module(requirement, uri);
|
||||
module.exports = Cu.import(uri, {});
|
||||
} else if (isJSONURI(uri)) {
|
||||
let data;
|
||||
|
||||
// First attempt to load and parse json uri
|
||||
// ex: `test.json`
|
||||
// If that doesn"t exist, check for `test.json.js`
|
||||
// for node parity
|
||||
try {
|
||||
data = JSON.parse(readURI(uri));
|
||||
module = modules[uri] = Module(requirement, uri);
|
||||
module.exports = data;
|
||||
} catch (err) {
|
||||
// If error thrown from JSON parsing, throw that, do not
|
||||
// attempt to find .json.js file
|
||||
if (err && /JSON\.parse/.test(err.message)) {
|
||||
throw err;
|
||||
}
|
||||
uri = uri + ".js";
|
||||
}
|
||||
}
|
||||
|
||||
// If not yet cached, load and cache it.
|
||||
// We also freeze module to prevent it from further changes
|
||||
// at runtime.
|
||||
if (!(uri in modules)) {
|
||||
// Many of the loader's functionalities are dependent
|
||||
// on modules[uri] being set before loading, so we set it and
|
||||
// remove it if we have any errors.
|
||||
module = modules[uri] = Module(requirement, uri);
|
||||
try {
|
||||
Object.freeze(load(loader, module));
|
||||
} catch (e) {
|
||||
// Clear out modules cache so we can throw on a second invalid require
|
||||
delete modules[uri];
|
||||
// Also clear out the Sandbox that was created
|
||||
delete loader.sandboxes[uri];
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
// Resolution function taking a module name/path and
|
||||
// returning a resourceURI and a `requirement` used by the loader.
|
||||
// Used by both `require` and `require.resolve`.
|
||||
function getRequirements(id) {
|
||||
if (!id) {
|
||||
// Throw if `id` is not passed.
|
||||
throw Error("you must provide a module name when calling require() from "
|
||||
+ requirer.id, requirer.uri);
|
||||
}
|
||||
|
||||
let requirement, uri;
|
||||
|
||||
if (modules[id]) {
|
||||
uri = requirement = id;
|
||||
} else if (requirer) {
|
||||
// Resolve `id` to its requirer if it's relative.
|
||||
requirement = resolve(id, requirer.id);
|
||||
} else {
|
||||
requirement = id;
|
||||
}
|
||||
|
||||
// Resolves `uri` of module using loaders resolve function.
|
||||
if (!uri) {
|
||||
if (mappingCache.has(requirement)) {
|
||||
uri = mappingCache.get(requirement);
|
||||
} else {
|
||||
uri = resolveURI(requirement, mapping);
|
||||
mappingCache.set(requirement, uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Throw if `uri` can not be resolved.
|
||||
if (!uri) {
|
||||
throw Error("Module: Can not resolve '" + id + "' module required by " +
|
||||
requirer.id + " located at " + requirer.uri, requirer.uri);
|
||||
}
|
||||
|
||||
return { uri: uri, requirement: requirement };
|
||||
}
|
||||
|
||||
// Expose the `resolve` function for this `Require` instance
|
||||
require.resolve = _require.resolve = function (id) {
|
||||
let { uri } = getRequirements(id);
|
||||
return uri;
|
||||
};
|
||||
|
||||
// This is like webpack's require.context. It returns a new require
|
||||
// function that prepends the prefix to any requests.
|
||||
require.context = prefix => {
|
||||
return id => {
|
||||
return require(prefix + id);
|
||||
};
|
||||
};
|
||||
|
||||
return require;
|
||||
}
|
||||
|
||||
// Makes module object that is made available to CommonJS modules when they
|
||||
// are evaluated, along with `exports` and `require`.
|
||||
function Module(id, uri) {
|
||||
return Object.create(null, {
|
||||
id: { enumerable: true, value: id },
|
||||
exports: { enumerable: true, writable: true, value: Object.create(null),
|
||||
configurable: true },
|
||||
uri: { value: uri }
|
||||
});
|
||||
}
|
||||
|
||||
// Takes `loader`, and unload `reason` string and notifies all observers that
|
||||
// they should cleanup after them-self.
|
||||
function unload(loader, reason) {
|
||||
// subject is a unique object created per loader instance.
|
||||
// This allows any code to cleanup on loader unload regardless of how
|
||||
// it was loaded. To handle unload for specific loader subject may be
|
||||
// asserted against loader.destructor or require("@loader/unload")
|
||||
// Note: We don not destroy loader's module cache or sandboxes map as
|
||||
// some modules may do cleanup in subsequent turns of event loop. Destroying
|
||||
// cache may cause module identity problems in such cases.
|
||||
let subject = { wrappedJSObject: loader.destructor };
|
||||
notifyObservers(subject, "sdk:loader:destroy", reason);
|
||||
}
|
||||
|
||||
// Function makes new loader that can be used to load CommonJS modules.
|
||||
// Loader takes following options:
|
||||
// - `paths`: Mandatory dictionary of require path mapped to absolute URIs.
|
||||
// Object keys are path prefix used in require(), values are URIs where each
|
||||
// prefix should be mapped to.
|
||||
// - `sharedGlobal`: Boolean, if True, loads all module in a single, shared
|
||||
// global in order to create only one global and compartment.
|
||||
// - `globals`: Optional map of globals, that all module scopes will inherit
|
||||
// from. Map is also exposed under `globals` property of the returned loader
|
||||
// so it can be extended further later. Defaults to `{}`.
|
||||
// - `sandboxName`: String, name of the sandbox displayed in about:memory.
|
||||
// - `invisibleToDebugger`: Boolean. Should be true when loading debugger
|
||||
// modules, in order to ignore them from the Debugger API.
|
||||
// - `sandboxPrototype`: Object used to define globals on all module's
|
||||
// sandboxes.
|
||||
// - `requireHook`: Optional function used to replace native require function
|
||||
// from loader. This function receive the module path as first argument,
|
||||
// and native require method as second argument.
|
||||
function Loader(options) {
|
||||
let { paths, sharedGlobal, globals } = options;
|
||||
if (!globals) {
|
||||
globals = {};
|
||||
}
|
||||
|
||||
// We create an identity object that will be dispatched on an unload
|
||||
// event as subject. This way unload listeners will be able to assert
|
||||
// which loader is unloaded. Please note that we intentionally don"t
|
||||
// use `loader` as subject to prevent a loader access leakage through
|
||||
// observer notifications.
|
||||
let destructor = Object.create(null);
|
||||
|
||||
let mapping = compileMapping(paths);
|
||||
|
||||
// Define pseudo modules.
|
||||
let modules = {
|
||||
"@loader/unload": destructor,
|
||||
"@loader/options": options,
|
||||
"chrome": { Cc, Ci, Cu, Cr, Cm,
|
||||
CC: bind(CC, Components), components: Components,
|
||||
ChromeWorker
|
||||
}
|
||||
};
|
||||
|
||||
const builtinModuleExports = modules;
|
||||
modules = {};
|
||||
for (let id of Object.keys(builtinModuleExports)) {
|
||||
// We resolve `uri` from `id` since modules are cached by `uri`.
|
||||
let uri = resolveURI(id, mapping);
|
||||
let module = Module(id, uri);
|
||||
|
||||
// Lazily expose built-in modules in order to
|
||||
// allow them to be loaded lazily.
|
||||
Object.defineProperty(module, "exports", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return builtinModuleExports[id];
|
||||
}
|
||||
});
|
||||
|
||||
modules[uri] = module;
|
||||
}
|
||||
|
||||
// Create the unique sandbox we will be using for all modules,
|
||||
// so that we prevent creating a new compartment per module.
|
||||
// The side effect is that all modules will share the same
|
||||
// global objects.
|
||||
let sharedGlobalSandbox = Sandbox({
|
||||
name: options.sandboxName || "DevTools",
|
||||
invisibleToDebugger: options.invisibleToDebugger || false,
|
||||
prototype: options.sandboxPrototype || globals,
|
||||
});
|
||||
|
||||
if (options.sandboxPrototype) {
|
||||
// If we were given a sandboxPrototype, we have to define the globals on
|
||||
// the sandbox directly. Note that this will not work for callers who
|
||||
// depend on being able to add globals after the loader was created.
|
||||
for (let name of getOwnIdentifiers(globals)) {
|
||||
Object.defineProperty(sharedGlobalSandbox, name,
|
||||
Object.getOwnPropertyDescriptor(globals, name));
|
||||
}
|
||||
}
|
||||
|
||||
// Loader object is just a representation of a environment
|
||||
// state. We mark its properties non-enumerable
|
||||
// as they are pure implementation detail that no one should rely upon.
|
||||
let returnObj = {
|
||||
destructor: { enumerable: false, value: destructor },
|
||||
globals: { enumerable: false, value: globals },
|
||||
mapping: { enumerable: false, value: mapping },
|
||||
mappingCache: { enumerable: false, value: new Map() },
|
||||
// Map of module objects indexed by module URIs.
|
||||
modules: { enumerable: false, value: modules },
|
||||
useSharedGlobalSandbox: { enumerable: false, value: !!sharedGlobal },
|
||||
sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox },
|
||||
// Map of module sandboxes indexed by module URIs.
|
||||
sandboxes: { enumerable: false, value: {} },
|
||||
// Whether the modules loaded should be ignored by the debugger
|
||||
invisibleToDebugger: { enumerable: false,
|
||||
value: options.invisibleToDebugger || false },
|
||||
requireHook: { enumerable: false, value: options.requireHook },
|
||||
};
|
||||
|
||||
return Object.create(null, returnObj);
|
||||
}
|
@ -14,7 +14,6 @@
|
||||
*/
|
||||
|
||||
const { Cu, CC, Cc, Ci } = require("chrome");
|
||||
const { Loader } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
const jsmScope = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
const { Services } = jsmScope;
|
||||
@ -172,7 +171,6 @@ function lazyRequireGetter(obj, property, module, destructure) {
|
||||
// List of pseudo modules exposed to all devtools modules.
|
||||
exports.modules = {
|
||||
"Services": Object.create(Services),
|
||||
"toolkit/loader": Loader,
|
||||
promise,
|
||||
PromiseDebugging,
|
||||
ChromeUtils,
|
||||
|
@ -26,7 +26,6 @@ DevToolsModules(
|
||||
'performance-recording.js',
|
||||
'performance.js',
|
||||
'preference.js',
|
||||
'profiler.js',
|
||||
'promises.js',
|
||||
'reflow.js',
|
||||
'storage.js',
|
||||
|
@ -1,77 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const {
|
||||
Front,
|
||||
FrontClassWithSpec,
|
||||
custom
|
||||
} = require("devtools/shared/protocol");
|
||||
const { profilerSpec } = require("devtools/shared/specs/profiler");
|
||||
|
||||
/**
|
||||
* This can be used on older Profiler implementations, but the methods cannot
|
||||
* be changed -- you must introduce a new method, and detect the server.
|
||||
*/
|
||||
exports.ProfilerFront = FrontClassWithSpec(profilerSpec, {
|
||||
initialize: function (client, form) {
|
||||
Front.prototype.initialize.call(this, client, form);
|
||||
this.actorID = form.profilerActor;
|
||||
this.manage(this);
|
||||
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
this.on("*", this._onProfilerEvent);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.off("*", this._onProfilerEvent);
|
||||
Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* If using the protocol.js Fronts, then make stringify default,
|
||||
* since the read/write mechanisms will expose it as an object anyway, but
|
||||
* this lets other consumers who connect directly (xpcshell tests, Gecko Profiler) to
|
||||
* have unchanged behaviour.
|
||||
*/
|
||||
getProfile: custom(function (options) {
|
||||
return this._getProfile(Object.assign({ stringify: true }, options));
|
||||
}, {
|
||||
impl: "_getProfile"
|
||||
}),
|
||||
|
||||
/**
|
||||
* Also emit an old `eventNotification` for older consumers of the profiler.
|
||||
*/
|
||||
_onProfilerEvent: function (eventName, data) {
|
||||
// If this event already passed through once, don't repropagate
|
||||
if (data.relayed) {
|
||||
return;
|
||||
}
|
||||
data.relayed = true;
|
||||
|
||||
if (eventName === "eventNotification") {
|
||||
// If this is `eventNotification`, this is coming from an older Gecko (<Fx42)
|
||||
// that doesn't use protocol.js style events. Massage it to emit a protocol.js
|
||||
// style event as well.
|
||||
this.emit(data.topic, data);
|
||||
} else {
|
||||
// Otherwise if a modern protocol.js event, emit it also as `eventNotification`
|
||||
// for compatibility reasons on the client (like for any add-ons/Gecko Profiler
|
||||
// using this event) and log a deprecation message if there is a listener.
|
||||
this.conn.emit("eventNotification", {
|
||||
subject: data.subject,
|
||||
topic: data.topic,
|
||||
data: data.data,
|
||||
details: data.details
|
||||
});
|
||||
if (this.conn._getListeners("eventNotification").length) {
|
||||
Cu.reportError(`
|
||||
ProfilerActor's "eventNotification" on the DebuggerClient has been deprecated.
|
||||
Use the ProfilerFront found in "devtools/server/actors/profiler".`);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
@ -44,6 +44,7 @@ JAR_MANIFESTS += ['jar.mn']
|
||||
DevToolsModules(
|
||||
'async-storage.js',
|
||||
'async-utils.js',
|
||||
'base-loader.js',
|
||||
'builtin-modules.js',
|
||||
'content-observer.js',
|
||||
'debounce.js',
|
||||
|
@ -31,7 +31,6 @@ DevToolsModules(
|
||||
'performance-recording.js',
|
||||
'performance.js',
|
||||
'preference.js',
|
||||
'profiler.js',
|
||||
'promises.js',
|
||||
'reflow.js',
|
||||
'script.js',
|
||||
|
@ -1,121 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
const {
|
||||
Arg,
|
||||
Option,
|
||||
RetVal,
|
||||
generateActorSpec,
|
||||
types
|
||||
} = require("devtools/shared/protocol");
|
||||
|
||||
types.addType("profiler-data", {
|
||||
// On Fx42+, the profile is only deserialized on the front; older
|
||||
// servers will get the profiler data as an object from nsIProfiler,
|
||||
// causing one parse/stringify cycle, then again implicitly in a packet.
|
||||
read: (v) => {
|
||||
if (typeof v.profile === "string") {
|
||||
// Create a new response object since `profile` is read only.
|
||||
let newValue = Object.create(null);
|
||||
newValue.profile = JSON.parse(v.profile);
|
||||
newValue.currentTime = v.currentTime;
|
||||
return newValue;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
});
|
||||
|
||||
const profilerSpec = generateActorSpec({
|
||||
typeName: "profiler",
|
||||
|
||||
/**
|
||||
* The set of events the ProfilerActor emits over RDP.
|
||||
*/
|
||||
events: {
|
||||
"console-api-profiler": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
"profiler-started": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
"profiler-stopped": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
"profiler-status": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
|
||||
// Only for older geckos, pre-protocol.js ProfilerActor (<Fx42).
|
||||
// Emitted on other events as a transition from older profiler events
|
||||
// to newer ones.
|
||||
"eventNotification": {
|
||||
subject: Option(0, "json"),
|
||||
topic: Option(0, "string"),
|
||||
details: Option(0, "json")
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
startProfiler: {
|
||||
// Write out every property in the request, since we want all these options to be
|
||||
// on the packet's top-level for backwards compatibility, when the profiler actor
|
||||
// was not using protocol.js (<Fx42)
|
||||
request: {
|
||||
entries: Option(0, "nullable:number"),
|
||||
interval: Option(0, "nullable:number"),
|
||||
features: Option(0, "nullable:array:string"),
|
||||
threadFilters: Option(0, "nullable:array:string"),
|
||||
},
|
||||
response: RetVal("json"),
|
||||
},
|
||||
stopProfiler: {
|
||||
response: RetVal("json"),
|
||||
},
|
||||
getProfile: {
|
||||
request: {
|
||||
startTime: Option(0, "nullable:number"),
|
||||
stringify: Option(0, "nullable:boolean")
|
||||
},
|
||||
response: RetVal("profiler-data")
|
||||
},
|
||||
getFeatures: {
|
||||
response: RetVal("json")
|
||||
},
|
||||
getBufferInfo: {
|
||||
response: RetVal("json")
|
||||
},
|
||||
getStartOptions: {
|
||||
response: RetVal("json")
|
||||
},
|
||||
isActive: {
|
||||
response: RetVal("json")
|
||||
},
|
||||
sharedLibraries: {
|
||||
response: RetVal("json")
|
||||
},
|
||||
registerEventNotifications: {
|
||||
// Explicitly enumerate the arguments
|
||||
// @see ProfilerActor#startProfiler
|
||||
request: {
|
||||
events: Option(0, "nullable:array:string"),
|
||||
},
|
||||
response: RetVal("json")
|
||||
},
|
||||
unregisterEventNotifications: {
|
||||
// Explicitly enumerate the arguments
|
||||
// @see ProfilerActor#startProfiler
|
||||
request: {
|
||||
events: Option(0, "nullable:array:string"),
|
||||
},
|
||||
response: RetVal("json")
|
||||
},
|
||||
setProfilerStatusInterval: {
|
||||
request: { interval: Arg(0, "number") },
|
||||
oneway: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.profilerSpec = profilerSpec;
|
@ -279,6 +279,7 @@
|
||||
#include "nsIURIClassifier.h"
|
||||
#include "mozilla/DocumentStyleRootIterator.h"
|
||||
#include "mozilla/ServoRestyleManager.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
@ -319,6 +320,65 @@ GetHttpChannelHelper(nsIChannel* aChannel, nsIHttpChannel** aHttpChannel)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// PrincipalFlashClassifier
|
||||
|
||||
// Classify the flash based on the document principal.
|
||||
// The usage of this class is as follows:
|
||||
//
|
||||
// 1) Call AsyncClassify() as early as possible to asynchronously do
|
||||
// classification against all the flash blocking related tables
|
||||
// via nsIURIClassifier.asyncClassifyLocalWithTables.
|
||||
//
|
||||
// 2) At any time you need the classification result, call Result()
|
||||
// and it is guaranteed to give you the result. Note that you have
|
||||
// to specify "aIsThirdParty" to the function so please make sure
|
||||
// you can already correctly decide if the document is third-party.
|
||||
//
|
||||
// Behind the scenes, the sync classification API
|
||||
// (nsIURIClassifier.classifyLocalWithTable) may be called as a fallback to
|
||||
// synchronously get the result if the asyncClassifyLocalWithTables hasn't
|
||||
// been done yet.
|
||||
//
|
||||
// 3) You can call Result() as many times as you want and only the first time
|
||||
// it may unfortunately call the blocking sync API. The subsequent call
|
||||
// will just return the result that came out at the first time.
|
||||
//
|
||||
class PrincipalFlashClassifier final : public nsIURIClassifierCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIURICLASSIFIERCALLBACK
|
||||
|
||||
PrincipalFlashClassifier();
|
||||
|
||||
// Fire async classification based on the given principal.
|
||||
void AsyncClassify(nsIPrincipal* aPrincipal);
|
||||
|
||||
// Would block if the result hasn't come out.
|
||||
mozilla::dom::FlashClassification ClassifyMaybeSync(nsIPrincipal* aPrincipal,
|
||||
bool aIsThirdParty);
|
||||
|
||||
private:
|
||||
~PrincipalFlashClassifier() = default;
|
||||
|
||||
void Reset();
|
||||
bool EnsureUriClassifier();
|
||||
mozilla::dom::FlashClassification CheckIfClassifyNeeded(nsIPrincipal* aPrincipal);
|
||||
mozilla::dom::FlashClassification Resolve(bool aIsThirdParty);
|
||||
mozilla::dom::FlashClassification AsyncClassifyInternal(nsIPrincipal* aPrincipal);
|
||||
void GetClassificationTables(bool aIsThirdParty, nsACString& aTables);
|
||||
|
||||
// For the fallback sync classification.
|
||||
nsCOMPtr<nsIURI> mClassificationURI;
|
||||
|
||||
nsCOMPtr<nsIURIClassifier> mUriClassifier;
|
||||
bool mAsyncClassified;
|
||||
nsTArray<nsCString> mMatchedTables;
|
||||
mozilla::dom::FlashClassification mResult;
|
||||
};
|
||||
|
||||
|
||||
#define NAME_NOT_VALID ((nsSimpleContentList*)1)
|
||||
|
||||
nsIdentifierMapEntry::~nsIdentifierMapEntry()
|
||||
@ -1575,6 +1635,9 @@ nsDocument::nsDocument(const char* aContentType)
|
||||
|
||||
// void state used to differentiate an empty source from an unselected source
|
||||
mPreloadPictureFoundSource.SetIsVoid(true);
|
||||
// For determining if this is a flash document which should be
|
||||
// blocked based on its principal.
|
||||
mPrincipalFlashClassifier = new PrincipalFlashClassifier();
|
||||
}
|
||||
|
||||
void
|
||||
@ -2761,6 +2824,11 @@ nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
|
||||
aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
|
||||
}
|
||||
|
||||
// Perform a async flash classification based on the doc principal
|
||||
// in an early stage to reduce the blocking time.
|
||||
mFlashClassification = FlashClassification::Unclassified;
|
||||
mPrincipalFlashClassifier->AsyncClassify(GetPrincipal());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -13483,6 +13551,23 @@ nsIDocument::UpdateStyleBackendType()
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the classification of the Flash plugins in the document based on
|
||||
* the classification lists. We perform AsyncInitFlashClassification on
|
||||
* StartDocumentLoad() and the result may not be initialized when this function
|
||||
* gets called. In that case, We can only unfortunately have a blocking wait.
|
||||
*
|
||||
* For more information, see
|
||||
* toolkit/components/url-classifier/flash-block-lists.rst
|
||||
*/
|
||||
FlashClassification
|
||||
nsDocument::PrincipalFlashClassification()
|
||||
{
|
||||
MOZ_ASSERT(mPrincipalFlashClassifier);
|
||||
return mPrincipalFlashClassifier->ClassifyMaybeSync(GetPrincipal(),
|
||||
IsThirdParty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for |nsDocument::PrincipalFlashClassification|
|
||||
*
|
||||
@ -13523,27 +13608,279 @@ ArrayContainsTable(const nsTArray<nsCString>& aTableArray,
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the classification of the Flash plugins in the document based on
|
||||
* the classification lists.
|
||||
*
|
||||
* For more information, see
|
||||
* toolkit/components/url-classifier/flash-block-lists.rst
|
||||
*/
|
||||
FlashClassification
|
||||
nsDocument::PrincipalFlashClassification()
|
||||
namespace {
|
||||
|
||||
// An object to store all preferences we need for flash blocking feature.
|
||||
struct PrefStore
|
||||
{
|
||||
nsresult rv;
|
||||
PrefStore()
|
||||
{
|
||||
Preferences::AddBoolVarCache(&mFlashBlockEnabled,
|
||||
"plugins.flashBlock.enabled");
|
||||
Preferences::AddBoolVarCache(&mPluginsHttpOnly,
|
||||
"plugins.http_https_only");
|
||||
|
||||
bool httpOnly = Preferences::GetBool("plugins.http_https_only", true);
|
||||
bool flashBlock = Preferences::GetBool("plugins.flashBlock.enabled", false);
|
||||
// We only need to register string-typed preferences.
|
||||
Preferences::RegisterCallback(UpdateStringPrefs, "urlclassifier.flashAllowTable", this);
|
||||
Preferences::RegisterCallback(UpdateStringPrefs, "urlclassifier.flashAllowExceptTable", this);
|
||||
Preferences::RegisterCallback(UpdateStringPrefs, "urlclassifier.flashTable", this);
|
||||
Preferences::RegisterCallback(UpdateStringPrefs, "urlclassifier.flashExceptTable", this);
|
||||
Preferences::RegisterCallback(UpdateStringPrefs, "urlclassifier.flashSubDocTable", this);
|
||||
Preferences::RegisterCallback(UpdateStringPrefs, "urlclassifier.flashSubDocExceptTable", this);
|
||||
|
||||
// If neither pref is on, skip the null-principal and principal URI checks.
|
||||
if (!httpOnly && !flashBlock) {
|
||||
UpdateStringPrefs();
|
||||
}
|
||||
|
||||
~PrefStore()
|
||||
{
|
||||
Preferences::UnregisterCallback(UpdateStringPrefs, "urlclassifier.flashAllowTable", this);
|
||||
Preferences::UnregisterCallback(UpdateStringPrefs, "urlclassifier.flashAllowExceptTable", this);
|
||||
Preferences::UnregisterCallback(UpdateStringPrefs, "urlclassifier.flashTable", this);
|
||||
Preferences::UnregisterCallback(UpdateStringPrefs, "urlclassifier.flashExceptTable", this);
|
||||
Preferences::UnregisterCallback(UpdateStringPrefs, "urlclassifier.flashSubDocTable", this);
|
||||
Preferences::UnregisterCallback(UpdateStringPrefs, "urlclassifier.flashSubDocExceptTable", this);
|
||||
}
|
||||
|
||||
void UpdateStringPrefs()
|
||||
{
|
||||
Preferences::GetCString("urlclassifier.flashAllowTable", mAllowTables);
|
||||
Preferences::GetCString("urlclassifier.flashAllowExceptTable", mAllowExceptionsTables);
|
||||
Preferences::GetCString("urlclassifier.flashTable", mDenyTables);
|
||||
Preferences::GetCString("urlclassifier.flashExceptTable", mDenyExceptionsTables);
|
||||
Preferences::GetCString("urlclassifier.flashSubDocTable", mSubDocDenyTables);
|
||||
Preferences::GetCString("urlclassifier.flashSubDocExceptTable", mSubDocDenyExceptionsTables);
|
||||
}
|
||||
|
||||
static void UpdateStringPrefs(const char*, void* aClosure)
|
||||
{
|
||||
static_cast<PrefStore*>(aClosure)->UpdateStringPrefs();
|
||||
}
|
||||
|
||||
bool mFlashBlockEnabled;
|
||||
bool mPluginsHttpOnly;
|
||||
|
||||
nsCString mAllowTables;
|
||||
nsCString mAllowExceptionsTables;
|
||||
nsCString mDenyTables;
|
||||
nsCString mDenyExceptionsTables;
|
||||
nsCString mSubDocDenyTables;
|
||||
nsCString mSubDocDenyExceptionsTables;
|
||||
};
|
||||
|
||||
static const
|
||||
PrefStore& GetPrefStore()
|
||||
{
|
||||
static UniquePtr<PrefStore> sPrefStore;
|
||||
if (!sPrefStore) {
|
||||
sPrefStore.reset(new PrefStore());
|
||||
ClearOnShutdown(&sPrefStore);
|
||||
}
|
||||
return *sPrefStore;
|
||||
}
|
||||
|
||||
} // end of unnamed namespace.
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// PrincipalFlashClassifier implementation.
|
||||
|
||||
NS_IMPL_ISUPPORTS(PrincipalFlashClassifier, nsIURIClassifierCallback)
|
||||
|
||||
PrincipalFlashClassifier::PrincipalFlashClassifier()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void
|
||||
PrincipalFlashClassifier::Reset()
|
||||
{
|
||||
mAsyncClassified = false;
|
||||
mMatchedTables.Clear();
|
||||
mResult = FlashClassification::Unclassified;
|
||||
}
|
||||
|
||||
void
|
||||
PrincipalFlashClassifier::GetClassificationTables(bool aIsThirdParty,
|
||||
nsACString& aTables)
|
||||
{
|
||||
aTables.Truncate();
|
||||
auto& prefs = GetPrefStore();
|
||||
|
||||
MaybeAddTableToTableList(prefs.mAllowTables, aTables);
|
||||
MaybeAddTableToTableList(prefs.mAllowExceptionsTables, aTables);
|
||||
MaybeAddTableToTableList(prefs.mDenyTables, aTables);
|
||||
MaybeAddTableToTableList(prefs.mDenyExceptionsTables, aTables);
|
||||
|
||||
if (aIsThirdParty) {
|
||||
MaybeAddTableToTableList(prefs.mSubDocDenyTables, aTables);
|
||||
MaybeAddTableToTableList(prefs.mSubDocDenyExceptionsTables, aTables);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
PrincipalFlashClassifier::EnsureUriClassifier()
|
||||
{
|
||||
if (!mUriClassifier) {
|
||||
mUriClassifier = do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID);
|
||||
}
|
||||
|
||||
return !!mUriClassifier;
|
||||
}
|
||||
|
||||
FlashClassification
|
||||
PrincipalFlashClassifier::ClassifyMaybeSync(nsIPrincipal* aPrincipal, bool aIsThirdParty)
|
||||
{
|
||||
if (FlashClassification::Unclassified != mResult) {
|
||||
// We already have the result. Just return it.
|
||||
return mResult;
|
||||
}
|
||||
|
||||
// TODO: Bug 1342333 - Entirely remove the use of the sync API
|
||||
// (ClassifyLocalWithTables).
|
||||
if (!mAsyncClassified) {
|
||||
|
||||
//
|
||||
// We may
|
||||
// 1) have called AsyncClassifyLocalWithTables but OnClassifyComplete
|
||||
// hasn't been called.
|
||||
// 2) haven't even called AsyncClassifyLocalWithTables.
|
||||
//
|
||||
// In both cases we need to do the synchronous classification as the fallback.
|
||||
//
|
||||
|
||||
if (!EnsureUriClassifier()) {
|
||||
return FlashClassification::Denied;
|
||||
}
|
||||
mResult = CheckIfClassifyNeeded(aPrincipal);
|
||||
if (FlashClassification::Unclassified != mResult) {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsAutoCString classificationTables;
|
||||
GetClassificationTables(aIsThirdParty, classificationTables);
|
||||
|
||||
if (!mClassificationURI) {
|
||||
rv = aPrincipal->GetURI(getter_AddRefs(mClassificationURI));
|
||||
if (NS_FAILED(rv) || !mClassificationURI) {
|
||||
mResult = FlashClassification::Denied;
|
||||
return mResult;
|
||||
}
|
||||
}
|
||||
|
||||
rv = mUriClassifier->ClassifyLocalWithTables(mClassificationURI,
|
||||
classificationTables,
|
||||
mMatchedTables);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
if (rv == NS_ERROR_MALFORMED_URI) {
|
||||
// This means that the URI had no hostname (ex: file://doc.html). In this
|
||||
// case, we allow the default (Unknown plugin) behavior.
|
||||
mResult = FlashClassification::Unknown;
|
||||
} else {
|
||||
mResult = FlashClassification::Denied;
|
||||
}
|
||||
return mResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the result based on mMatchedTables and aIsThirdParty.
|
||||
mResult = Resolve(aIsThirdParty);
|
||||
MOZ_ASSERT(FlashClassification::Unclassified != mResult);
|
||||
|
||||
// The subsequent call of Result() will return the resolved result
|
||||
// and never reach here until Reset() is called.
|
||||
return mResult;
|
||||
}
|
||||
|
||||
/*virtual*/ nsresult
|
||||
PrincipalFlashClassifier::OnClassifyComplete(nsresult /*aErrorCode*/,
|
||||
const nsACString& aLists, // Only this matters.
|
||||
const nsACString& /*aProvider*/,
|
||||
const nsACString& /*aPrefix*/)
|
||||
{
|
||||
mAsyncClassified = true;
|
||||
|
||||
if (FlashClassification::Unclassified != mResult) {
|
||||
// Result() has been called prior to this callback.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// TODO: Bug 1364804 - We should use a callback type which notifies
|
||||
// the result as a string array rather than a formatted string.
|
||||
|
||||
// We only populate the matched list without resolving the classification
|
||||
// result because we are not sure if the parent doc has been properly set.
|
||||
// We also parse the comma-separated tables to array. (the code is copied
|
||||
// from Classifier::SplitTables.)
|
||||
nsACString::const_iterator begin, iter, end;
|
||||
aLists.BeginReading(begin);
|
||||
aLists.EndReading(end);
|
||||
while (begin != end) {
|
||||
iter = begin;
|
||||
FindCharInReadable(',', iter, end);
|
||||
nsDependentCSubstring table = Substring(begin,iter);
|
||||
if (!table.IsEmpty()) {
|
||||
mMatchedTables.AppendElement(Substring(begin, iter));
|
||||
}
|
||||
begin = iter;
|
||||
if (begin != end) {
|
||||
begin++;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// We resolve the classification result based on aIsThirdParty
|
||||
// and the matched tables we got ealier on (via either sync or async API).
|
||||
FlashClassification
|
||||
PrincipalFlashClassifier::Resolve(bool aIsThirdParty)
|
||||
{
|
||||
MOZ_ASSERT(FlashClassification::Unclassified == mResult,
|
||||
"We already have resolved classification result.");
|
||||
|
||||
if (mMatchedTables.IsEmpty()) {
|
||||
return FlashClassification::Unknown;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
|
||||
auto& prefs = GetPrefStore();
|
||||
if (ArrayContainsTable(mMatchedTables, prefs.mDenyTables) &&
|
||||
!ArrayContainsTable(mMatchedTables, prefs.mDenyExceptionsTables)) {
|
||||
return FlashClassification::Denied;
|
||||
} else if (ArrayContainsTable(mMatchedTables, prefs.mAllowTables) &&
|
||||
!ArrayContainsTable(mMatchedTables, prefs.mAllowExceptionsTables)) {
|
||||
return FlashClassification::Allowed;
|
||||
}
|
||||
|
||||
if (aIsThirdParty && ArrayContainsTable(mMatchedTables, prefs.mSubDocDenyTables) &&
|
||||
!ArrayContainsTable(mMatchedTables, prefs.mSubDocDenyExceptionsTables)) {
|
||||
return FlashClassification::Denied;
|
||||
}
|
||||
|
||||
return FlashClassification::Unknown;
|
||||
}
|
||||
|
||||
void
|
||||
PrincipalFlashClassifier::AsyncClassify(nsIPrincipal* aPrincipal)
|
||||
{
|
||||
MOZ_ASSERT(FlashClassification::Unclassified == mResult,
|
||||
"The old classification result should be reset first.");
|
||||
Reset();
|
||||
mResult = AsyncClassifyInternal(aPrincipal);
|
||||
}
|
||||
|
||||
FlashClassification
|
||||
PrincipalFlashClassifier::CheckIfClassifyNeeded(nsIPrincipal* aPrincipal)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
auto& prefs = GetPrefStore();
|
||||
|
||||
// If neither pref is on, skip the null-principal and principal URI checks.
|
||||
if (prefs.mPluginsHttpOnly && !prefs.mFlashBlockEnabled) {
|
||||
return FlashClassification::Unknown;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal = aPrincipal;
|
||||
if (principal->GetIsNullPrincipal()) {
|
||||
return FlashClassification::Denied;
|
||||
}
|
||||
@ -13554,7 +13891,7 @@ nsDocument::PrincipalFlashClassification()
|
||||
return FlashClassification::Denied;
|
||||
}
|
||||
|
||||
if (httpOnly) {
|
||||
if (prefs.mPluginsHttpOnly) {
|
||||
// Only allow plugins for documents from an HTTP/HTTPS origin. This should
|
||||
// allow dependent data: URIs to load plugins, but not:
|
||||
// * chrome documents
|
||||
@ -13570,49 +13907,50 @@ nsDocument::PrincipalFlashClassification()
|
||||
|
||||
// If flash blocking is disabled, it is equivalent to all sites being
|
||||
// on neither list.
|
||||
if (!flashBlock) {
|
||||
if (!prefs.mFlashBlockEnabled) {
|
||||
return FlashClassification::Unknown;
|
||||
}
|
||||
|
||||
nsAutoCString allowTables, allowExceptionsTables,
|
||||
denyTables, denyExceptionsTables,
|
||||
subDocDenyTables, subDocDenyExceptionsTables,
|
||||
tables;
|
||||
Preferences::GetCString("urlclassifier.flashAllowTable", allowTables);
|
||||
MaybeAddTableToTableList(allowTables, tables);
|
||||
Preferences::GetCString("urlclassifier.flashAllowExceptTable",
|
||||
allowExceptionsTables);
|
||||
MaybeAddTableToTableList(allowExceptionsTables, tables);
|
||||
Preferences::GetCString("urlclassifier.flashTable", denyTables);
|
||||
MaybeAddTableToTableList(denyTables, tables);
|
||||
Preferences::GetCString("urlclassifier.flashExceptTable",
|
||||
denyExceptionsTables);
|
||||
MaybeAddTableToTableList(denyExceptionsTables, tables);
|
||||
return FlashClassification::Unclassified;
|
||||
}
|
||||
|
||||
bool isThirdPartyDoc = IsThirdParty();
|
||||
if (isThirdPartyDoc) {
|
||||
Preferences::GetCString("urlclassifier.flashSubDocTable",
|
||||
subDocDenyTables);
|
||||
MaybeAddTableToTableList(subDocDenyTables, tables);
|
||||
Preferences::GetCString("urlclassifier.flashSubDocExceptTable",
|
||||
subDocDenyExceptionsTables);
|
||||
MaybeAddTableToTableList(subDocDenyExceptionsTables, tables);
|
||||
// Using nsIURIClassifier.asyncClassifyLocalWithTables to do classification
|
||||
// against the flash related tables based on the given principal.
|
||||
FlashClassification
|
||||
PrincipalFlashClassifier::AsyncClassifyInternal(nsIPrincipal* aPrincipal)
|
||||
{
|
||||
auto result = CheckIfClassifyNeeded(aPrincipal);
|
||||
if (FlashClassification::Unclassified != result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We haven't been able to decide if it's a third party document
|
||||
// since determining if a document is third-party may depend on its
|
||||
// parent document. At the time we call AsyncClassifyInternal
|
||||
// (i.e. StartDocumentLoad) the parent document may not have been
|
||||
// set. As a result, we wait until Resolve() to be called to
|
||||
// take "is third party" into account. At this point, we just assume
|
||||
// it's third-party to include every list.
|
||||
nsAutoCString tables;
|
||||
GetClassificationTables(true, tables);
|
||||
|
||||
if (tables.IsEmpty()) {
|
||||
return FlashClassification::Unknown;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURIClassifier> uriClassifier =
|
||||
do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
if (!EnsureUriClassifier()) {
|
||||
return FlashClassification::Denied;
|
||||
}
|
||||
|
||||
nsTArray<nsCString> results;
|
||||
rv = uriClassifier->ClassifyLocalWithTables(classificationURI,
|
||||
tables,
|
||||
results);
|
||||
nsresult rv = aPrincipal->GetURI(getter_AddRefs(mClassificationURI));
|
||||
if (NS_FAILED(rv) || !mClassificationURI) {
|
||||
return FlashClassification::Denied;
|
||||
}
|
||||
|
||||
rv = mUriClassifier->AsyncClassifyLocalWithTables(mClassificationURI,
|
||||
tables,
|
||||
this);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
if (rv == NS_ERROR_MALFORMED_URI) {
|
||||
// This means that the URI had no hostname (ex: file://doc.html). In this
|
||||
@ -13623,24 +13961,7 @@ nsDocument::PrincipalFlashClassification()
|
||||
}
|
||||
}
|
||||
|
||||
if (results.IsEmpty()) {
|
||||
return FlashClassification::Unknown;
|
||||
}
|
||||
|
||||
if (ArrayContainsTable(results, denyTables) &&
|
||||
!ArrayContainsTable(results, denyExceptionsTables)) {
|
||||
return FlashClassification::Denied;
|
||||
} else if (ArrayContainsTable(results, allowTables) &&
|
||||
!ArrayContainsTable(results, allowExceptionsTables)) {
|
||||
return FlashClassification::Allowed;
|
||||
}
|
||||
|
||||
if (isThirdPartyDoc && ArrayContainsTable(results, subDocDenyTables) &&
|
||||
!ArrayContainsTable(results, subDocDenyExceptionsTables)) {
|
||||
return FlashClassification::Denied;
|
||||
}
|
||||
|
||||
return FlashClassification::Unknown;
|
||||
return FlashClassification::Unclassified;
|
||||
}
|
||||
|
||||
FlashClassification
|
||||
|
@ -72,6 +72,7 @@
|
||||
#include "CustomElementRegistry.h"
|
||||
#include "mozilla/dom/Performance.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "nsIURIClassifier.h"
|
||||
|
||||
#define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
|
||||
#define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
|
||||
@ -346,6 +347,9 @@ protected:
|
||||
bool mHaveShutDown;
|
||||
};
|
||||
|
||||
// For classifying a flash document based on its principal.
|
||||
class PrincipalFlashClassifier;
|
||||
|
||||
// Base class for our document implementations.
|
||||
class nsDocument : public nsIDocument,
|
||||
public nsIDOMDocument,
|
||||
@ -1184,6 +1188,7 @@ protected:
|
||||
// non-null when this document is in fullscreen mode.
|
||||
nsWeakPtr mFullscreenRoot;
|
||||
|
||||
RefPtr<PrincipalFlashClassifier> mPrincipalFlashClassifier;
|
||||
mozilla::dom::FlashClassification mFlashClassification;
|
||||
// Do not use this value directly. Call the |IsThirdParty()| method, which
|
||||
// caches its result here.
|
||||
|
@ -266,6 +266,17 @@ IMEStateManager::NotifyIMEOfBlurForChildProcess()
|
||||
}
|
||||
|
||||
MOZ_ASSERT(sFocusedIMEWidget);
|
||||
|
||||
if (MOZ_LOG_TEST(sISMLog, LogLevel::Debug) && sTextCompositions) {
|
||||
RefPtr<TextComposition> composition =
|
||||
sTextCompositions->GetCompositionFor(sFocusedIMEWidget);
|
||||
if (composition) {
|
||||
MOZ_LOG(sISMLog, LogLevel::Debug,
|
||||
(" NotifyIMEOfBlurForChildProcess(), sFocusedIMEWidget still has "
|
||||
"composition"));
|
||||
}
|
||||
}
|
||||
|
||||
NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMETabParent);
|
||||
|
||||
MOZ_ASSERT(!sFocusedIMETabParent);
|
||||
@ -486,9 +497,12 @@ IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
|
||||
if (composition) {
|
||||
// However, don't commit the composition if we're being inactivated
|
||||
// but the composition should be kept even during deactive.
|
||||
// Note that oldWidget and sFocusedIMEWidget may be different here (in
|
||||
// such case, sFocusedIMEWidget is perhaps nullptr). For example, IME
|
||||
// may receive only blur notification but still has composition.
|
||||
// We need to clean up only the oldWidget's composition state here.
|
||||
if (aPresContext ||
|
||||
!sFocusedIMEWidget->IMENotificationRequestsRef().
|
||||
WantDuringDeactive()) {
|
||||
!oldWidget->IMENotificationRequestsRef().WantDuringDeactive()) {
|
||||
MOZ_LOG(sISMLog, LogLevel::Info,
|
||||
(" OnChangeFocusInternal(), requesting to commit composition to "
|
||||
"the (previous) focused widget"));
|
||||
|
@ -3837,6 +3837,8 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
||||
mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING, "HTMLMediaElement::mReadyState"),
|
||||
mLoadWaitStatus(NOT_WAITING),
|
||||
mVolume(1.0),
|
||||
mIsAudioTrackAudible(false),
|
||||
mMuted(0),
|
||||
mPreloadAction(PRELOAD_UNDEFINED),
|
||||
mLastCurrentTime(0.0),
|
||||
mFragmentStart(-1.0),
|
||||
@ -3850,8 +3852,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
||||
mLoadedDataFired(false),
|
||||
mAutoplaying(true),
|
||||
mAutoplayEnabled(true),
|
||||
mPaused(true),
|
||||
mMuted(0),
|
||||
mPaused(true, *this),
|
||||
mStatsShowing(false),
|
||||
mAllowCasting(false),
|
||||
mIsCasting(false),
|
||||
@ -3881,7 +3882,6 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
||||
mHasUserInteraction(false),
|
||||
mFirstFrameLoaded(false),
|
||||
mDefaultPlaybackStartPosition(0.0),
|
||||
mIsAudioTrackAudible(false),
|
||||
mHasSuspendTaint(false),
|
||||
mMediaTracksConstructed(false),
|
||||
mVisibilityState(Visibility::UNTRACKED),
|
||||
@ -3896,8 +3896,6 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
||||
double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
|
||||
SetVolume(defaultVolume, rv);
|
||||
|
||||
mPaused.SetOuter(this);
|
||||
|
||||
RegisterActivityObserver();
|
||||
NotifyOwnerDocumentActivityChanged();
|
||||
|
||||
@ -4188,17 +4186,16 @@ void
|
||||
HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mOuter);
|
||||
|
||||
bool playing = !mValue;
|
||||
bool isAudible = mOuter->Volume() > 0.0 &&
|
||||
!mOuter->mMuted &&
|
||||
mOuter->mIsAudioTrackAudible;
|
||||
bool isAudible = mOuter.Volume() > 0.0 &&
|
||||
!mOuter.mMuted &&
|
||||
mOuter.mIsAudioTrackAudible;
|
||||
// when playing audible media.
|
||||
if (playing && isAudible) {
|
||||
mOuter->WakeLockCreate();
|
||||
mOuter.WakeLockCreate();
|
||||
} else {
|
||||
mOuter->WakeLockRelease();
|
||||
mOuter.WakeLockRelease();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -818,15 +818,13 @@ protected:
|
||||
|
||||
class WakeLockBoolWrapper {
|
||||
public:
|
||||
explicit WakeLockBoolWrapper(bool val = false)
|
||||
: mValue(val)
|
||||
, mOuter(nullptr)
|
||||
WakeLockBoolWrapper(bool aVal, HTMLMediaElement& aOuter)
|
||||
: mValue(aVal)
|
||||
, mOuter(aOuter)
|
||||
{}
|
||||
|
||||
~WakeLockBoolWrapper() {};
|
||||
|
||||
void SetOuter(HTMLMediaElement* outer) { mOuter = outer; }
|
||||
|
||||
MOZ_IMPLICIT operator bool() const { return mValue; }
|
||||
|
||||
WakeLockBoolWrapper& operator=(bool val);
|
||||
@ -836,7 +834,7 @@ protected:
|
||||
void UpdateWakeLock();
|
||||
private:
|
||||
bool mValue;
|
||||
HTMLMediaElement* mOuter;
|
||||
HTMLMediaElement& mOuter;
|
||||
};
|
||||
|
||||
// Holds references to the DOM wrappers for the MediaStreams that we're
|
||||
@ -1450,6 +1448,18 @@ protected:
|
||||
// Current audio volume
|
||||
double mVolume;
|
||||
|
||||
// True if the audio track is not silent.
|
||||
bool mIsAudioTrackAudible;
|
||||
|
||||
enum MutedReasons {
|
||||
MUTED_BY_CONTENT = 0x01,
|
||||
MUTED_BY_INVALID_PLAYBACK_RATE = 0x02,
|
||||
MUTED_BY_AUDIO_CHANNEL = 0x04,
|
||||
MUTED_BY_AUDIO_TRACK = 0x08
|
||||
};
|
||||
|
||||
uint32_t mMuted;
|
||||
|
||||
UniquePtr<const MetadataTags> mTags;
|
||||
|
||||
// URI of the resource we're attempting to load. This stores the value we
|
||||
@ -1551,15 +1561,6 @@ protected:
|
||||
// 'Pause' method, or playback not yet having started.
|
||||
WakeLockBoolWrapper mPaused;
|
||||
|
||||
enum MutedReasons {
|
||||
MUTED_BY_CONTENT = 0x01,
|
||||
MUTED_BY_INVALID_PLAYBACK_RATE = 0x02,
|
||||
MUTED_BY_AUDIO_CHANNEL = 0x04,
|
||||
MUTED_BY_AUDIO_TRACK = 0x08
|
||||
};
|
||||
|
||||
uint32_t mMuted;
|
||||
|
||||
// True if the media statistics are currently being shown by the builtin
|
||||
// video controls
|
||||
bool mStatsShowing;
|
||||
@ -1780,9 +1781,6 @@ private:
|
||||
// be seeked even before the media is loaded.
|
||||
double mDefaultPlaybackStartPosition;
|
||||
|
||||
// True if the audio track is not silent.
|
||||
bool mIsAudioTrackAudible;
|
||||
|
||||
// True if media element has been marked as 'tainted' and can't
|
||||
// participate in video decoder suspending.
|
||||
bool mHasSuspendTaint;
|
||||
|
@ -401,9 +401,7 @@ class MediaRecorder::Session: public nsIObserver,
|
||||
// safe to delete this Session.
|
||||
// Also avoid to run if this session already call stop before
|
||||
if (!mSession->mStopIssued) {
|
||||
ErrorResult result;
|
||||
mSession->mStopIssued = true;
|
||||
recorder->Stop(result);
|
||||
recorder->StopForSessionDestruction();
|
||||
if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(mSession.forget())))) {
|
||||
MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
|
||||
}
|
||||
@ -818,10 +816,12 @@ private:
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
CleanupStreams();
|
||||
|
||||
NS_DispatchToMainThread(
|
||||
new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
mRecorder->ForceInactive();
|
||||
NS_DispatchToMainThread(
|
||||
NewRunnableMethod<nsresult>("dom::MediaRecorder::NotifyError",
|
||||
mRecorder,
|
||||
@ -1475,6 +1475,25 @@ MediaRecorder::GetSourceMediaStream()
|
||||
return mPipeStream ? mPipeStream.get() : mAudioNode->GetStream();
|
||||
}
|
||||
|
||||
void
|
||||
MediaRecorder::ForceInactive()
|
||||
{
|
||||
LOG(LogLevel::Debug, ("MediaRecorder.ForceInactive %p", this));
|
||||
mState = RecordingState::Inactive;
|
||||
}
|
||||
|
||||
void
|
||||
MediaRecorder::StopForSessionDestruction()
|
||||
{
|
||||
LOG(LogLevel::Debug, ("MediaRecorder.StopForSessionDestruction %p", this));
|
||||
MediaRecorderReporter::RemoveMediaRecorder(this);
|
||||
// We do not perform a mState != RecordingState::Recording) check here as
|
||||
// we may already be inactive due to ForceInactive().
|
||||
mState = RecordingState::Inactive;
|
||||
MOZ_ASSERT(mSessions.Length() > 0);
|
||||
mSessions.LastElement()->Stop();
|
||||
}
|
||||
|
||||
void
|
||||
MediaRecorder::InitializeDomExceptions()
|
||||
{
|
||||
|
@ -140,6 +140,13 @@ protected:
|
||||
// available at the time the error event is fired. Note, depending on when
|
||||
// this is called there may not be a JS stack to capture.
|
||||
void InitializeDomExceptions();
|
||||
// Set the recorder state to inactive. This is needed to handle error states
|
||||
// in the recorder where state must transition to inactive before full
|
||||
// stoppage can be reached.
|
||||
void ForceInactive();
|
||||
// Stop the recorder and its internal session. This should be used by
|
||||
// sessions that are in the process of being destroyed.
|
||||
void StopForSessionDestruction();
|
||||
// DOM wrapper for source media stream. Will be null when input is audio node.
|
||||
RefPtr<DOMMediaStream> mDOMStream;
|
||||
// Source audio node. Will be null when input is a media stream.
|
||||
|
@ -67,7 +67,7 @@ RemoteVideoDecoder::Init()
|
||||
mConversion = mActor->NeedsConversion();
|
||||
return InitPromise::CreateAndResolve(aTrack, __func__);
|
||||
},
|
||||
[self, this](const MediaResult& aError) {
|
||||
[self](const MediaResult& aError) {
|
||||
return InitPromise::CreateAndReject(aError, __func__);
|
||||
});
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include "WMFDecoderModule.h"
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
#endif
|
||||
#ifdef MOZ_FFVPX
|
||||
#include "FFVPXRuntimeLinker.h"
|
||||
@ -341,7 +342,7 @@ PDMFactory::CreatePDMs()
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
if (MediaPrefs::PDMWMFEnabled()) {
|
||||
if (MediaPrefs::PDMWMFEnabled() && !IsWin7AndPre2000Compatible()) {
|
||||
m = new WMFDecoderModule();
|
||||
RefPtr<PlatformDecoderModule> remote = new dom::RemoteDecoderModule(m);
|
||||
StartupPDM(remote);
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <initguid.h>
|
||||
#include <stdint.h>
|
||||
#include "mozilla/mscom/EnsureMTA.h"
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
|
||||
#ifdef WMF_MUST_DEFINE_AAC_MFT_CLSID
|
||||
// Some SDK versions don't define the AAC decoder CLSID.
|
||||
@ -228,6 +229,16 @@ LoadDLLs()
|
||||
HRESULT
|
||||
MFStartup()
|
||||
{
|
||||
if (IsWin7AndPre2000Compatible()) {
|
||||
/*
|
||||
* Specific exclude the usage of WMF on Win 7 with compatibility mode
|
||||
* prior to Win 2000 as we may crash while trying to startup WMF.
|
||||
* Using GetVersionEx API which takes compatibility mode into account.
|
||||
* See Bug 1279171.
|
||||
*/
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
HRESULT hr = LoadDLLs();
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
|
7
dom/media/test/reftest/gizmo.mp4.55thframe-ref.html
Normal file
7
dom/media/test/reftest/gizmo.mp4.55thframe-ref.html
Normal file
File diff suppressed because one or more lines are too long
@ -1,21 +1,21 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html class="reftest-wait">
|
||||
<!--This testing should match the 15th frame of street.mp4. The
|
||||
15th frame's time is 0.466666, so seek to a time which is a little
|
||||
greater than 0.466666, the display frame should be the 15th frame.
|
||||
<!--This testing should match the 55th frame of gizmo.mp4. The
|
||||
55th frame's time is 1.8s, so seek to a time which is a little
|
||||
greater than 1.8s, the display frame should be the 55th frame.
|
||||
-->
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
function doTest() {
|
||||
var video = document.getElementById("v1");
|
||||
video.src = "../street.mp4";
|
||||
video.src = "../gizmo.mp4";
|
||||
video.preload = "metadata";
|
||||
|
||||
video.currentTime = 0.466667;
|
||||
video.currentTime = 1.801;
|
||||
|
||||
video.addEventListener("seeked", function() {
|
||||
// Since the our media pipeline send the frame to imageBridge, then fire
|
||||
// seeked event, the target frame may not be showns on the screen.
|
||||
// seeked event, the target frame may not be shown on the screen.
|
||||
// So using canvas to access the target frame in the imageContainer in
|
||||
// videoElement.
|
||||
var canvas = document.getElementById("canvas");
|
@ -1,4 +1,4 @@
|
||||
skip-if(Android) fuzzy-if(OSX,22,49977) skip-if(winWidget) fuzzy-if(gtkWidget&&layersGPUAccelerated,70,600) HTTP(..) == short.mp4.firstframe.html short.mp4.firstframe-ref.html
|
||||
skip-if(Android) fuzzy-if(OSX,23,51392) fuzzy-if(winWidget,59,76797) fuzzy-if(gtkWidget&&layersGPUAccelerated,60,1800) HTTP(..) == short.mp4.lastframe.html short.mp4.lastframe-ref.html
|
||||
skip-if(Android) skip-if(winWidget) fuzzy-if(gtkWidget&&layersGPUAccelerated,55,4281) fuzzy-if(OSX,3,111852) HTTP(..) == bipbop_300_215kbps.mp4.lastframe.html bipbop_300_215kbps.mp4.lastframe-ref.html
|
||||
skip-if(Android) fuzzy-if(winWidget,65,307093) HTTP(..) == street.mp4.seek.html street.mp4.15thframe-ref.html
|
||||
skip-if(Android) fuzzy-if(OSX,25,175921) fuzzy-if(winWidget,64,179198) HTTP(..) == gizmo.mp4.seek.html gizmo.mp4.55thframe-ref.html
|
||||
|
File diff suppressed because one or more lines are too long
@ -27,8 +27,7 @@ function cloneLoaded(event) {
|
||||
manager.finished(e.token);
|
||||
}
|
||||
|
||||
function tryClone(event) {
|
||||
var e = event.target;
|
||||
function tryClone(e) {
|
||||
var clone = e.cloneNode(false);
|
||||
clone.token = `${e.token}(cloned)`;
|
||||
manager.started(clone.token);
|
||||
@ -70,15 +69,26 @@ function tryClone(event) {
|
||||
// does a network fetch it will get a resource with the wrong length and we get a test
|
||||
// failure.
|
||||
|
||||
function initTest(test, token) {
|
||||
async function initTest(test, token) {
|
||||
var e = document.createElement("video");
|
||||
e.preload = "auto";
|
||||
e.src = test.name;
|
||||
e._expectedDuration = test.duration;
|
||||
ok(true, `Trying to load ${test.name}, duration=${test.duration}`);
|
||||
e.addEventListener("loadeddata", tryClone, {once: true});
|
||||
e.token = token;
|
||||
manager.started(token);
|
||||
|
||||
// Since 320x240.ogv is less than 32KB, we need to wait for the
|
||||
// 'suspend' event to ensure the partial block is flushed to the cache
|
||||
// otherwise the cloned resource will create a new channel when it
|
||||
// has no data to read at position 0. The new channel will download
|
||||
// a different file than the original resource and fail the duration
|
||||
// test.
|
||||
let p1 = once(e, "loadeddata");
|
||||
let p2 = once(e, "suspend");
|
||||
await p1;
|
||||
await p2;
|
||||
tryClone(e);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
@ -26,7 +26,7 @@ function startTest() {
|
||||
'Events fired from onerror should include an error with a stack trace indicating ' +
|
||||
'an error in this test');
|
||||
is(mediaRecorder.mimeType, '', 'mimetype should be empty');
|
||||
is(mediaRecorder.state, 'recording', 'state is recording');
|
||||
is(mediaRecorder.state, 'inactive', 'state is inactive');
|
||||
info('onerror callback fired');
|
||||
callbackStep = 1;
|
||||
};
|
||||
|
@ -45,6 +45,8 @@ SpecialPowers.pushPrefEnv({"set": [["media.ogg.enabled", false]]},
|
||||
'Mime type in ondataavailable = ' + expectedMimeType);
|
||||
};
|
||||
mediaRecorder.onerror = function(evt) {
|
||||
is(evt.target.state, 'inactive',
|
||||
'Media recorder is inactive after firing error');
|
||||
ok(evt instanceof MediaRecorderErrorEvent,
|
||||
'Events fired from onerror should be MediaRecorderErrorEvent');
|
||||
is(evt.type, 'error',
|
||||
@ -56,13 +58,17 @@ SpecialPowers.pushPrefEnv({"set": [["media.ogg.enabled", false]]},
|
||||
ok(evt.error.stack.includes('test_mediarecorder_getencodeddata.html'),
|
||||
'Events fired from onerror should include an error with a stack trace indicating ' +
|
||||
'an error in this test');
|
||||
try {
|
||||
mediaRecorder.requestData();
|
||||
ok(false, 'requestdata should fire an exception if called on an inactive recorder');
|
||||
} catch (e) {
|
||||
ok(e instanceof DOMException, 'requestdata should fire an exception ' +
|
||||
'if called on an inactive recorder');
|
||||
is(e.name, 'InvalidStateError', 'Exception name should be InvalidStateError');
|
||||
}
|
||||
onErrorFired = true;
|
||||
};
|
||||
mediaRecorder.start(0);
|
||||
is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
|
||||
is(mediaRecorder.stream, stream,
|
||||
'Media recorder stream = element stream at the start of recording');
|
||||
mediaRecorder.requestData();
|
||||
}, 100);
|
||||
}
|
||||
);
|
||||
|
@ -24,9 +24,8 @@ function startTest() {
|
||||
|
||||
// Expected callback sequence should be:
|
||||
// 1. onerror (from start)
|
||||
// 2. onerror (from pause)
|
||||
// 3. ondataavailable
|
||||
// 4. onstop
|
||||
// 2. ondataavailable
|
||||
// 3. onstop
|
||||
var callbackStep = 0;
|
||||
var mediaRecorder = new MediaRecorder(stream);
|
||||
|
||||
@ -37,19 +36,22 @@ function startTest() {
|
||||
if (callbackStep == 1) {
|
||||
try {
|
||||
mediaRecorder.pause();
|
||||
ok(false, 'pause should fire an exception if called on an inactive recorder');
|
||||
} catch(e) {
|
||||
ok(false, 'Should not get exception in pause call.');
|
||||
ok(e instanceof DOMException, 'pause should fire an exception ' +
|
||||
'if called on an inactive recorder');
|
||||
is(e.name, 'InvalidStateError', 'Exception name should be InvalidStateError');
|
||||
}
|
||||
}
|
||||
ok(callbackStep < 3, 'onerror callback fired as expected.');
|
||||
ok(callbackStep == 1, 'onerror callback should handle be the 1st event fired');
|
||||
is(e.error.name, 'UnknownError', 'Error name should be UnknownError.');
|
||||
ok(e.error.stack.includes('test_mediarecorder_unsupported_src.html'),
|
||||
'Events fired from onerror should include an error with a stack trace indicating ' +
|
||||
'an error in this test');
|
||||
is(mediaRecorder.mimeType, '', 'mimetype should be empty');
|
||||
is(mediaRecorder.state, 'recording', 'state is recording');
|
||||
is(mediaRecorder.state, 'inactive', 'state is inactive');
|
||||
info('onerror callback fired');
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.onwarning = function () {
|
||||
ok(false, 'Unexpected onwarning callback fired.');
|
||||
@ -58,7 +60,7 @@ function startTest() {
|
||||
mediaRecorder.ondataavailable = function (evt) {
|
||||
callbackStep++;
|
||||
info('ondataavailable callback fired');
|
||||
is(callbackStep, 3, 'should fired ondataavailable callback');
|
||||
is(callbackStep, 2, 'ondataavailable callback should handle the 2nd event fired');
|
||||
is(evt.data.size, 0, 'data size should be zero');
|
||||
ok(evt instanceof BlobEvent,
|
||||
'Events fired from ondataavailable should be BlobEvent');
|
||||
@ -69,7 +71,7 @@ function startTest() {
|
||||
callbackStep++;
|
||||
info('onstop callback fired');
|
||||
is(mediaRecorder.state, 'inactive', 'state should be inactive');
|
||||
is(callbackStep, 4, 'should fired onstop callback');
|
||||
is(callbackStep, 3, 'onstop callback should handle the 3rd event fired');
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
|
@ -36,27 +36,6 @@ using namespace workers;
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper classes
|
||||
class MOZ_STACK_CLASS PerformanceEntryComparator final
|
||||
{
|
||||
public:
|
||||
bool Equals(const PerformanceEntry* aElem1,
|
||||
const PerformanceEntry* aElem2) const
|
||||
{
|
||||
MOZ_ASSERT(aElem1 && aElem2,
|
||||
"Trying to compare null performance entries");
|
||||
return aElem1->StartTime() == aElem2->StartTime();
|
||||
}
|
||||
|
||||
bool LessThan(const PerformanceEntry* aElem1,
|
||||
const PerformanceEntry* aElem2) const
|
||||
{
|
||||
MOZ_ASSERT(aElem1 && aElem2,
|
||||
"Trying to compare null performance entries");
|
||||
return aElem1->StartTime() < aElem2->StartTime();
|
||||
}
|
||||
};
|
||||
|
||||
class PrefEnabledRunnable final
|
||||
: public WorkerCheckAPIExposureOnMainThreadRunnable
|
||||
{
|
||||
|
@ -94,6 +94,27 @@ protected:
|
||||
nsString mEntryType;
|
||||
};
|
||||
|
||||
// Helper classes
|
||||
class MOZ_STACK_CLASS PerformanceEntryComparator final
|
||||
{
|
||||
public:
|
||||
bool Equals(const PerformanceEntry* aElem1,
|
||||
const PerformanceEntry* aElem2) const
|
||||
{
|
||||
MOZ_ASSERT(aElem1 && aElem2,
|
||||
"Trying to compare null performance entries");
|
||||
return aElem1->StartTime() == aElem2->StartTime();
|
||||
}
|
||||
|
||||
bool LessThan(const PerformanceEntry* aElem1,
|
||||
const PerformanceEntry* aElem2) const
|
||||
{
|
||||
MOZ_ASSERT(aElem1 && aElem2,
|
||||
"Trying to compare null performance entries");
|
||||
return aElem1->StartTime() < aElem2->StartTime();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -113,12 +113,13 @@ PerformanceObserver::Notify()
|
||||
RefPtr<PerformanceObserverEntryList> list =
|
||||
new PerformanceObserverEntryList(this, mQueuedEntries);
|
||||
|
||||
mQueuedEntries.Clear();
|
||||
|
||||
ErrorResult rv;
|
||||
mCallback->Call(this, *list, *this, rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
rv.SuppressException();
|
||||
}
|
||||
mQueuedEntries.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
@ -169,6 +170,17 @@ PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
|
||||
mEntryTypes.SwapElements(validEntryTypes);
|
||||
|
||||
mPerformance->AddObserver(this);
|
||||
|
||||
if (aOptions.mBuffered) {
|
||||
for (auto entryType : mEntryTypes) {
|
||||
nsTArray<RefPtr<PerformanceEntry>> existingEntries;
|
||||
mPerformance->GetEntriesByType(entryType, existingEntries);
|
||||
if (!existingEntries.IsEmpty()) {
|
||||
mQueuedEntries.AppendElements(existingEntries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mConnected = true;
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,7 @@ PerformanceObserverEntryList::GetEntries(
|
||||
|
||||
aRetval.AppendElement(entry);
|
||||
}
|
||||
aRetval.Sort(PerformanceEntryComparator());
|
||||
}
|
||||
|
||||
void
|
||||
@ -79,6 +80,7 @@ PerformanceObserverEntryList::GetEntriesByType(
|
||||
aRetval.AppendElement(entry);
|
||||
}
|
||||
}
|
||||
aRetval.Sort(PerformanceEntryComparator());
|
||||
}
|
||||
|
||||
void
|
||||
@ -88,9 +90,18 @@ PerformanceObserverEntryList::GetEntriesByName(
|
||||
nsTArray<RefPtr<PerformanceEntry>>& aRetval)
|
||||
{
|
||||
aRetval.Clear();
|
||||
const bool typePassed = aEntryType.WasPassed();
|
||||
for (const RefPtr<PerformanceEntry>& entry : mEntries) {
|
||||
if (entry->GetName().Equals(aName)) {
|
||||
aRetval.AppendElement(entry);
|
||||
if (!entry->GetName().Equals(aName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typePassed &&
|
||||
!entry->GetEntryType().Equals(aEntryType.Value())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
aRetval.AppendElement(entry);
|
||||
}
|
||||
aRetval.Sort(PerformanceEntryComparator());
|
||||
}
|
||||
|
@ -60,27 +60,13 @@ var _fromByTestLists =
|
||||
midComp: "35px",
|
||||
toComp: "40px"}),
|
||||
],
|
||||
lengthNoUnitsSVG: [
|
||||
new AnimTestcaseFromBy("0", "50", { fromComp: "0",
|
||||
midComp: "25",
|
||||
toComp: "50"}),
|
||||
new AnimTestcaseFromBy("30", "10", { fromComp: "30",
|
||||
midComp: "35",
|
||||
toComp: "40"}),
|
||||
],
|
||||
lengthPx: [
|
||||
new AnimTestcaseFromBy("0px", "8px", { fromComp: "0px",
|
||||
midComp: "4px",
|
||||
toComp: "8px"}),
|
||||
new AnimTestcaseFromBy("1px", "10px", { midComp: "6px", toComp: "11px"}),
|
||||
],
|
||||
lengthPxSVG: [
|
||||
new AnimTestcaseFromBy("0px", "8px", { fromComp: "0",
|
||||
midComp: "4",
|
||||
toComp: "8"}),
|
||||
new AnimTestcaseFromBy("1px", "10px", { fromComp: "1",
|
||||
midComp: "6",
|
||||
toComp: "11"}),
|
||||
new AnimTestcaseFromBy("1px", "10px", { fromComp: "1px",
|
||||
midComp: "6px",
|
||||
toComp: "11px"}),
|
||||
],
|
||||
opacity: [
|
||||
new AnimTestcaseFromBy("1", "-1", { midComp: "0.5", toComp: "0"}),
|
||||
@ -176,6 +162,6 @@ var gFromByBundles =
|
||||
new AnimTestcaseFromBy("1", "2, 3"),
|
||||
]),
|
||||
new TestcaseBundle(gPropList.stroke_width,
|
||||
[].concat(_fromByTestLists.lengthNoUnitsSVG,
|
||||
_fromByTestLists.lengthPxSVG))
|
||||
[].concat(_fromByTestLists.lengthNoUnits,
|
||||
_fromByTestLists.lengthPx))
|
||||
];
|
||||
|
@ -88,38 +88,19 @@ var _fromToTestLists = {
|
||||
midComp: "55px",
|
||||
toComp: "80px"}),
|
||||
],
|
||||
lengthNoUnitsSVG: [
|
||||
new AnimTestcaseFromTo("0", "20", { fromComp: "0",
|
||||
midComp: "10",
|
||||
toComp: "20"}),
|
||||
new AnimTestcaseFromTo("50", "0", { fromComp: "50",
|
||||
midComp: "25",
|
||||
toComp: "0"}),
|
||||
new AnimTestcaseFromTo("30", "80", { fromComp: "30",
|
||||
midComp: "55",
|
||||
toComp: "80"}),
|
||||
],
|
||||
lengthPx: [
|
||||
new AnimTestcaseFromTo("0px", "12px", { fromComp: "0px",
|
||||
midComp: "6px"}),
|
||||
new AnimTestcaseFromTo("16px", "0px", { midComp: "8px",
|
||||
toComp: "0px"}),
|
||||
new AnimTestcaseFromTo("10px", "20px", { midComp: "15px"}),
|
||||
new AnimTestcaseFromTo("41px", "1px", { midComp: "21px"}),
|
||||
],
|
||||
lengthPxSVG: [
|
||||
new AnimTestcaseFromTo("0px", "12px", { fromComp: "0",
|
||||
midComp: "6",
|
||||
toComp: "12"}),
|
||||
new AnimTestcaseFromTo("16px", "0px", { fromComp: "16",
|
||||
midComp: "8",
|
||||
toComp: "0"}),
|
||||
new AnimTestcaseFromTo("10px", "20px", { fromComp: "10",
|
||||
midComp: "15",
|
||||
toComp: "20"}),
|
||||
new AnimTestcaseFromTo("41px", "1px", { fromComp: "41",
|
||||
midComp: "21",
|
||||
toComp: "1"}),
|
||||
midComp: "6px",
|
||||
toComp: "12px"}),
|
||||
new AnimTestcaseFromTo("16px", "0px", { fromComp: "16px",
|
||||
midComp: "8px",
|
||||
toComp: "0px"}),
|
||||
new AnimTestcaseFromTo("10px", "20px", { fromComp: "10px",
|
||||
midComp: "15px",
|
||||
toComp: "20px"}),
|
||||
new AnimTestcaseFromTo("41px", "1px", { fromComp: "41px",
|
||||
midComp: "21px",
|
||||
toComp: "1px"}),
|
||||
],
|
||||
lengthPctSVG: [
|
||||
new AnimTestcaseFromTo("20.5%", "0.5%", { midComp: "10.5%" }),
|
||||
@ -130,12 +111,12 @@ var _fromToTestLists = {
|
||||
"px and percent values"),
|
||||
],
|
||||
lengthPxNoUnitsSVG: [
|
||||
new AnimTestcaseFromTo("10", "20px", { fromComp: "10",
|
||||
midComp: "15",
|
||||
toComp: "20"}),
|
||||
new AnimTestcaseFromTo("10px", "20", { fromComp: "10",
|
||||
midComp: "15",
|
||||
toComp: "20"}),
|
||||
new AnimTestcaseFromTo("10", "20px", { fromComp: "10px",
|
||||
midComp: "15px",
|
||||
toComp: "20px"}),
|
||||
new AnimTestcaseFromTo("10px", "20", { fromComp: "10px",
|
||||
midComp: "15px",
|
||||
toComp: "20px"}),
|
||||
],
|
||||
opacity: [
|
||||
new AnimTestcaseFromTo("1", "0", { midComp: "0.5" }),
|
||||
@ -410,8 +391,8 @@ var gFromToBundles = [
|
||||
midComp: "1, 3, 3, 5, 5, 2, 2, 4, 4, 6"}),
|
||||
])),
|
||||
new TestcaseBundle(gPropList.stroke_dashoffset,
|
||||
[].concat(_fromToTestLists.lengthNoUnitsSVG,
|
||||
_fromToTestLists.lengthPxSVG,
|
||||
[].concat(_fromToTestLists.lengthNoUnits,
|
||||
_fromToTestLists.lengthPx,
|
||||
_fromToTestLists.lengthPxPctSVG,
|
||||
_fromToTestLists.lengthPctSVG,
|
||||
_fromToTestLists.lengthPxNoUnitsSVG)),
|
||||
@ -429,13 +410,13 @@ var gFromToBundles = [
|
||||
]),
|
||||
new TestcaseBundle(gPropList.stroke_opacity, _fromToTestLists.opacity),
|
||||
new TestcaseBundle(gPropList.stroke_width,
|
||||
[].concat(_fromToTestLists.lengthNoUnitsSVG,
|
||||
_fromToTestLists.lengthPxSVG,
|
||||
[].concat(_fromToTestLists.lengthNoUnits,
|
||||
_fromToTestLists.lengthPx,
|
||||
_fromToTestLists.lengthPxPctSVG,
|
||||
_fromToTestLists.lengthPctSVG,
|
||||
_fromToTestLists.lengthPxNoUnitsSVG, [
|
||||
new AnimTestcaseFromTo("inherit", "7px",
|
||||
{ fromComp: "1", midComp: "4", toComp: "7" }),
|
||||
{ fromComp: "1px", midComp: "4px", toComp: "7px" }),
|
||||
])),
|
||||
new TestcaseBundle(gPropList.text_anchor, [
|
||||
new AnimTestcaseFromTo("start", "middle"),
|
||||
|
@ -87,22 +87,6 @@ var _pacedTestLists =
|
||||
comp1: "8px"
|
||||
}),
|
||||
],
|
||||
lengthNoUnitsSVG : [
|
||||
new AnimTestcasePaced("2; 0; 4",
|
||||
{ comp0: "2",
|
||||
comp1_6: "1",
|
||||
comp1_3: "0",
|
||||
comp2_3: "2",
|
||||
comp1: "4"
|
||||
}),
|
||||
new AnimTestcasePaced("10; 12; 8",
|
||||
{ comp0: "10",
|
||||
comp1_6: "11",
|
||||
comp1_3: "12",
|
||||
comp2_3: "10",
|
||||
comp1: "8"
|
||||
}),
|
||||
],
|
||||
lengthPx : [
|
||||
new AnimTestcasePaced("0px; 2px; 6px",
|
||||
{ comp0: "0px",
|
||||
@ -119,22 +103,6 @@ var _pacedTestLists =
|
||||
comp1: "8px"
|
||||
}),
|
||||
],
|
||||
lengthPxSVG : [
|
||||
new AnimTestcasePaced("0px; 2px; 6px",
|
||||
{ comp0: "0",
|
||||
comp1_6: "1",
|
||||
comp1_3: "2",
|
||||
comp2_3: "4",
|
||||
comp1: "6"
|
||||
}),
|
||||
new AnimTestcasePaced("10px; 12px; 8px",
|
||||
{ comp0: "10",
|
||||
comp1_6: "11",
|
||||
comp1_3: "12",
|
||||
comp2_3: "10",
|
||||
comp1: "8"
|
||||
}),
|
||||
],
|
||||
lengthPctSVG : [
|
||||
new AnimTestcasePaced("5%; 6%; 4%",
|
||||
{ comp0: "5%",
|
||||
@ -308,13 +276,13 @@ var gPacedBundles =
|
||||
}),
|
||||
])),
|
||||
new TestcaseBundle(gPropList.stroke_dashoffset,
|
||||
[].concat(_pacedTestLists.lengthNoUnitsSVG,
|
||||
_pacedTestLists.lengthPxSVG,
|
||||
[].concat(_pacedTestLists.lengthNoUnits,
|
||||
_pacedTestLists.lengthPx,
|
||||
_pacedTestLists.lengthPctSVG,
|
||||
_pacedTestLists.lengthPxPctSVG)),
|
||||
new TestcaseBundle(gPropList.stroke_width,
|
||||
[].concat(_pacedTestLists.lengthNoUnitsSVG,
|
||||
_pacedTestLists.lengthPxSVG,
|
||||
[].concat(_pacedTestLists.lengthNoUnits,
|
||||
_pacedTestLists.lengthPx,
|
||||
_pacedTestLists.lengthPctSVG,
|
||||
_pacedTestLists.lengthPxPctSVG)),
|
||||
// XXXdholbert TODO: test 'stroke-dasharray' once we support animating it
|
||||
|
@ -19,16 +19,13 @@ support-files =
|
||||
[test_smilBackwardsSeeking.xhtml]
|
||||
[test_smilCSSFontStretchRelative.xhtml]
|
||||
[test_smilCSSFromBy.xhtml]
|
||||
skip-if = stylo
|
||||
[test_smilCSSFromTo.xhtml]
|
||||
skip-if = stylo
|
||||
# [test_smilCSSInherit.xhtml]
|
||||
# disabled until bug 501183 is fixed
|
||||
[test_smilCSSInvalidValues.xhtml]
|
||||
[test_smilCSSPaced.xhtml]
|
||||
skip-if = stylo
|
||||
[test_smilChangeAfterFrozen.xhtml]
|
||||
skip-if = stylo
|
||||
skip-if = stylo # bug 1358955.
|
||||
[test_smilConditionalProcessing.html]
|
||||
[test_smilContainerBinding.xhtml]
|
||||
[test_smilCrossContainer.xhtml]
|
||||
@ -44,11 +41,8 @@ skip-if = toolkit == 'android'
|
||||
[test_smilKeyTimes.xhtml]
|
||||
[test_smilKeyTimesPacedMode.xhtml]
|
||||
[test_smilMappedAttrFromBy.xhtml]
|
||||
skip-if = stylo
|
||||
[test_smilMappedAttrFromTo.xhtml]
|
||||
skip-if = stylo
|
||||
[test_smilMappedAttrPaced.xhtml]
|
||||
skip-if = stylo
|
||||
[test_smilMinTiming.html]
|
||||
[test_smilRepeatDuration.html]
|
||||
[test_smilRepeatTiming.xhtml]
|
||||
@ -60,7 +54,6 @@ skip-if = toolkit == 'android' #TIMED_OUT
|
||||
[test_smilSyncTransform.xhtml]
|
||||
[test_smilSyncbaseTarget.xhtml]
|
||||
[test_smilTextZoom.xhtml]
|
||||
skip-if = stylo
|
||||
[test_smilTimeEvents.xhtml]
|
||||
[test_smilTiming.xhtml]
|
||||
[test_smilTimingZeroIntervals.xhtml]
|
||||
|
@ -113,6 +113,9 @@ var SMILUtil =
|
||||
getMotionFakeAttributeName : function() {
|
||||
return "_motion";
|
||||
},
|
||||
|
||||
// Return stripped px value from specified value.
|
||||
stripPx: str => str.replace(/px\s*$/, ''),
|
||||
};
|
||||
|
||||
|
||||
@ -404,6 +407,19 @@ AnimTestcase.prototype =
|
||||
for (var i in aSeekList) {
|
||||
var entry = aSeekList[i];
|
||||
SMILUtil.getSVGRoot().setCurrentTime(entry[0]);
|
||||
|
||||
// Bug 1379908: The computed value of stroke-* properties should be
|
||||
// serialized with px units, but currently Gecko and Servo don't do that
|
||||
// when animating these values.
|
||||
if (['stroke-width',
|
||||
'stroke-dasharray',
|
||||
'stroke-dashoffset'].includes(aTargetAttr.attrName)) {
|
||||
var attr = SMILUtil.stripPx(
|
||||
SMILUtil.getAttributeValue(aTargetElem, aTargetAttr));
|
||||
var expectedVal = SMILUtil.stripPx(entry[1]);
|
||||
is(attr, expectedVal, entry[2]);
|
||||
return;
|
||||
}
|
||||
is(SMILUtil.getAttributeValue(aTargetElem, aTargetAttr),
|
||||
entry[1], entry[2]);
|
||||
}
|
||||
|
@ -30,6 +30,16 @@ SimpleTest.waitForExplicitFinish();
|
||||
function verifyStyle(aNode, aPropertyName, aExpectedVal)
|
||||
{
|
||||
var computedVal = SMILUtil.getComputedStyleSimple(aNode, aPropertyName);
|
||||
|
||||
// Bug 1379908: The computed value of stroke-* properties should be
|
||||
// serialized with px units, but currently Gecko and Servo don't do that
|
||||
// when animating these values.
|
||||
if ('stroke-width' == aPropertyName) {
|
||||
var expectedVal = SMILUtil.stripPx(aExpectedVal);
|
||||
var unitlessComputedVal = SMILUtil.stripPx(computedVal);
|
||||
is(unitlessComputedVal, expectedVal, "computed value of " + aPropertyName);
|
||||
return;
|
||||
}
|
||||
is(computedVal, aExpectedVal, "computed value of " + aPropertyName);
|
||||
}
|
||||
|
||||
@ -58,16 +68,16 @@ function main()
|
||||
verifyStyle(rect, "stroke-width", "5px");
|
||||
svg.setCurrentTime(1);
|
||||
verifyStyle(text, "font-size", "20px");
|
||||
verifyStyle(rect, "stroke-width", "20");
|
||||
verifyStyle(rect, "stroke-width", "20px");
|
||||
svg.setCurrentTime(1.5);
|
||||
verifyStyle(text, "font-size", "30px");
|
||||
verifyStyle(rect, "stroke-width", "30");
|
||||
verifyStyle(rect, "stroke-width", "30px");
|
||||
svg.setCurrentTime(2);
|
||||
verifyStyle(text, "font-size", "40px");
|
||||
verifyStyle(rect, "stroke-width", "40");
|
||||
verifyStyle(rect, "stroke-width", "40px");
|
||||
svg.setCurrentTime(3);
|
||||
verifyStyle(text, "font-size", "40px");
|
||||
verifyStyle(rect, "stroke-width", "40");
|
||||
verifyStyle(rect, "stroke-width", "40px");
|
||||
} catch (e) {
|
||||
// If anything goes wrong, make sure we restore textZoom before bubbling
|
||||
// the exception upwards, so that we don't mess up subsequent tests.
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
dictionary PerformanceObserverInit {
|
||||
required sequence<DOMString> entryTypes;
|
||||
boolean buffered = false;
|
||||
};
|
||||
|
||||
callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries, PerformanceObserver observer);
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/dom/NodeListBinding.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/ServoStyleSet.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
using namespace mozilla;
|
||||
@ -709,77 +710,9 @@ nsBindingManager::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
typedef nsTHashtable<nsPtrHashKey<nsIStyleRuleProcessor> > RuleProcessorSet;
|
||||
|
||||
static RuleProcessorSet*
|
||||
GetContentSetRuleProcessors(nsTHashtable<nsRefPtrHashKey<nsIContent>>* aContentSet)
|
||||
{
|
||||
RuleProcessorSet* set = nullptr;
|
||||
|
||||
for (auto iter = aContentSet->Iter(); !iter.Done(); iter.Next()) {
|
||||
nsIContent* boundContent = iter.Get()->GetKey();
|
||||
for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
|
||||
binding = binding->GetBaseBinding()) {
|
||||
nsIStyleRuleProcessor* ruleProc =
|
||||
binding->PrototypeBinding()->GetRuleProcessor();
|
||||
if (ruleProc) {
|
||||
if (!set) {
|
||||
set = new RuleProcessorSet;
|
||||
}
|
||||
set->PutEntry(ruleProc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
void
|
||||
nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
ElementDependentRuleProcessorData* aData)
|
||||
{
|
||||
if (!mBoundContentSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoPtr<RuleProcessorSet> set;
|
||||
set = GetContentSetRuleProcessors(mBoundContentSet);
|
||||
if (!set) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto iter = set->Iter(); !iter.Done(); iter.Next()) {
|
||||
nsIStyleRuleProcessor* ruleProcessor = iter.Get()->GetKey();
|
||||
(*(aFunc))(ruleProcessor, aData);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext,
|
||||
bool* aRulesChanged)
|
||||
{
|
||||
*aRulesChanged = false;
|
||||
if (!mBoundContentSet) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoPtr<RuleProcessorSet> set;
|
||||
set = GetContentSetRuleProcessors(mBoundContentSet);
|
||||
if (!set) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
for (auto iter = set->Iter(); !iter.Done(); iter.Next()) {
|
||||
nsIStyleRuleProcessor* ruleProcessor = iter.Get()->GetKey();
|
||||
bool thisChanged = ruleProcessor->MediumFeaturesChanged(aPresContext);
|
||||
*aRulesChanged = *aRulesChanged || thisChanged;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsBindingManager::AppendAllSheets(nsTArray<StyleSheet*>& aArray)
|
||||
nsBindingManager::EnumerateBoundContentBindings(
|
||||
const BoundContentBindingCallback& aCallback) const
|
||||
{
|
||||
if (!mBoundContentSet) {
|
||||
return;
|
||||
@ -787,13 +720,93 @@ nsBindingManager::AppendAllSheets(nsTArray<StyleSheet*>& aArray)
|
||||
|
||||
for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
|
||||
nsIContent* boundContent = iter.Get()->GetKey();
|
||||
for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
|
||||
for (nsXBLBinding* binding = boundContent->GetXBLBinding();
|
||||
binding;
|
||||
binding = binding->GetBaseBinding()) {
|
||||
binding->PrototypeBinding()->AppendStyleSheetsTo(aArray);
|
||||
aCallback(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
ElementDependentRuleProcessorData* aData)
|
||||
{
|
||||
EnumerateBoundContentBindings([=](nsXBLBinding* aBinding) {
|
||||
nsIStyleRuleProcessor* ruleProcessor =
|
||||
aBinding->PrototypeBinding()->GetRuleProcessor();
|
||||
if (ruleProcessor) {
|
||||
(*(aFunc))(ruleProcessor, aData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext)
|
||||
{
|
||||
bool rulesChanged = false;
|
||||
RefPtr<nsPresContext> presContext = aPresContext;
|
||||
bool isStyledByServo = mDocument->IsStyledByServo();
|
||||
|
||||
EnumerateBoundContentBindings([=, &rulesChanged](nsXBLBinding* aBinding) {
|
||||
if (isStyledByServo) {
|
||||
ServoStyleSet* styleSet = aBinding->PrototypeBinding()->GetServoStyleSet();
|
||||
if (styleSet) {
|
||||
bool styleSetChanged = false;
|
||||
|
||||
if (styleSet->IsPresContextChanged(presContext)) {
|
||||
styleSetChanged = styleSet->SetPresContext(presContext);
|
||||
} else {
|
||||
// PresContext is not changed. This means aPresContext is still
|
||||
// alive since the last time it initialized this XBL styleset.
|
||||
// It's safe to check whether medium features changed.
|
||||
bool viewportUnitsUsed = false;
|
||||
styleSetChanged =
|
||||
styleSet->MediumFeaturesChangedRules(&viewportUnitsUsed);
|
||||
MOZ_ASSERT(!viewportUnitsUsed,
|
||||
"Non-master stylesets shouldn't get flagged as using "
|
||||
"viewport units!");
|
||||
}
|
||||
rulesChanged = rulesChanged || styleSetChanged;
|
||||
}
|
||||
} else {
|
||||
nsIStyleRuleProcessor* ruleProcessor =
|
||||
aBinding->PrototypeBinding()->GetRuleProcessor();
|
||||
if (ruleProcessor) {
|
||||
bool thisChanged = ruleProcessor->MediumFeaturesChanged(presContext);
|
||||
rulesChanged = rulesChanged || thisChanged;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return rulesChanged;
|
||||
}
|
||||
|
||||
void
|
||||
nsBindingManager::UpdateBoundContentBindingsForServo(nsPresContext* aPresContext)
|
||||
{
|
||||
MOZ_ASSERT(mDocument->IsStyledByServo(),
|
||||
"This should be called only by servo-backend!");
|
||||
|
||||
RefPtr<nsPresContext> presContext = aPresContext;
|
||||
|
||||
EnumerateBoundContentBindings([=](nsXBLBinding* aBinding) {
|
||||
nsXBLPrototypeBinding* protoBinding = aBinding->PrototypeBinding();
|
||||
ServoStyleSet* styleSet = protoBinding->GetServoStyleSet();
|
||||
if (styleSet && styleSet->StyleSheetsHaveChanged()) {
|
||||
protoBinding->ComputeServoStyleSet(presContext);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
nsBindingManager::AppendAllSheets(nsTArray<StyleSheet*>& aArray)
|
||||
{
|
||||
EnumerateBoundContentBindings([&aArray](nsXBLBinding* aBinding) {
|
||||
aBinding->PrototypeBinding()->AppendStyleSheetsTo(aArray);
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
InsertAppendedContent(XBLChildrenElement* aPoint,
|
||||
nsIContent* aFirstNewContent)
|
||||
|
@ -130,13 +130,16 @@ public:
|
||||
|
||||
void WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
ElementDependentRuleProcessorData* aData);
|
||||
/**
|
||||
* Do any processing that needs to happen as a result of a change in
|
||||
* the characteristics of the medium, and return whether this rule
|
||||
* processor's rules have changed (e.g., because of media queries).
|
||||
*/
|
||||
nsresult MediumFeaturesChanged(nsPresContext* aPresContext,
|
||||
bool* aRulesChanged);
|
||||
|
||||
// Do any processing that needs to happen as a result of a change in the
|
||||
// characteristics of the medium, and return whether this rule processor's
|
||||
// rules or the servo style set have changed (e.g., because of media
|
||||
// queries).
|
||||
bool MediumFeaturesChanged(nsPresContext* aPresContext);
|
||||
|
||||
// Update the content bindings in mBoundContentSet due to medium features
|
||||
// changed.
|
||||
void UpdateBoundContentBindingsForServo(nsPresContext* aPresContext);
|
||||
|
||||
void AppendAllSheets(nsTArray<mozilla::StyleSheet*>& aArray);
|
||||
|
||||
@ -193,6 +196,12 @@ protected:
|
||||
// Call PostProcessAttachedQueueEvent() on a timer.
|
||||
static void PostPAQEventCallback(nsITimer* aTimer, void* aClosure);
|
||||
|
||||
// Enumerate each bound content's bindings (including its base bindings)
|
||||
// in mBoundContentSet.
|
||||
using BoundContentBindingCallback = std::function<void (nsXBLBinding*)>;
|
||||
void EnumerateBoundContentBindings(
|
||||
const BoundContentBindingCallback& aCallback) const;
|
||||
|
||||
// MEMBER VARIABLES
|
||||
protected:
|
||||
// A set of nsIContent that currently have a binding installed.
|
||||
|
@ -569,6 +569,14 @@ nsXBLPrototypeBinding::GetRuleProcessor()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
nsXBLPrototypeBinding::ComputeServoStyleSet(nsPresContext* aPresContext)
|
||||
{
|
||||
if (mResources) {
|
||||
mResources->ComputeServoStyleSet(aPresContext);
|
||||
}
|
||||
}
|
||||
|
||||
ServoStyleSet*
|
||||
nsXBLPrototypeBinding::GetServoStyleSet() const
|
||||
{
|
||||
|
@ -131,6 +131,7 @@ public:
|
||||
void AppendStyleSheetsTo(nsTArray<mozilla::StyleSheet*>& aResult) const;
|
||||
|
||||
nsIStyleRuleProcessor* GetRuleProcessor();
|
||||
void ComputeServoStyleSet(nsPresContext* aPresContext);
|
||||
mozilla::ServoStyleSet* GetServoStyleSet() const;
|
||||
|
||||
nsresult FlushSkinSheets();
|
||||
|
@ -167,23 +167,14 @@ nsXBLPrototypeResources::GatherRuleProcessor()
|
||||
void
|
||||
nsXBLPrototypeResources::ComputeServoStyleSet(nsPresContext* aPresContext)
|
||||
{
|
||||
mServoStyleSet.reset(new ServoStyleSet(ServoStyleSet::Kind::ForXBL));
|
||||
mServoStyleSet->Init(aPresContext, nullptr);
|
||||
nsTArray<RefPtr<ServoStyleSheet>> sheets(mStyleSheetList.Length());
|
||||
for (StyleSheet* sheet : mStyleSheetList) {
|
||||
MOZ_ASSERT(sheet->IsServo(),
|
||||
"This should only be called with Servo-flavored style backend!");
|
||||
// The XBL style sheets aren't document level sheets, but we need to
|
||||
// decide a particular SheetType to add them to style set. This type
|
||||
// doesn't affect the place where we pull those rules from
|
||||
// stylist::push_applicable_declarations_as_xbl_only_stylist().
|
||||
mServoStyleSet->AppendStyleSheet(SheetType::Doc, sheet->AsServo());
|
||||
sheets.AppendElement(sheet->AsServo());
|
||||
}
|
||||
mServoStyleSet->UpdateStylistIfNeeded();
|
||||
|
||||
// The PresContext of the bound document could be destroyed anytime later,
|
||||
// which shouldn't be used for XBL styleset, so we clear it here to avoid
|
||||
// dangling pointer.
|
||||
mServoStyleSet->ClearPresContext();
|
||||
mServoStyleSet = ServoStyleSet::CreateXBLServoStyleSet(aPresContext, sheets);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -59,7 +59,7 @@ random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-override.
|
||||
random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-override-inherit.html spellcheck-textarea-ref.html
|
||||
needs-focus == caret_on_focus.html caret_on_focus-ref.html
|
||||
needs-focus != caret_on_textarea_lastline.html caret_on_textarea_lastline-ref.html
|
||||
needs-focus == input-text-onfocus-reframe.html input-text-onfocus-reframe-ref.html
|
||||
fuzzy-if(Android,1,1) needs-focus == input-text-onfocus-reframe.html input-text-onfocus-reframe-ref.html
|
||||
fuzzy-if(skiaContent,3,1) needs-focus == input-text-notheme-onfocus-reframe.html input-text-notheme-onfocus-reframe-ref.html
|
||||
needs-focus == caret_after_reframe.html caret_after_reframe-ref.html
|
||||
== nobogusnode-1.html nobogusnode-ref.html
|
||||
|
@ -7,10 +7,10 @@
|
||||
<script src="data:application/javascript,"></script>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
charset="UTF-16BE"
|
||||
href="data:text/css,%00b%00o%00d%00y%00{%00c%00o%00l%00o%00r%00:%00g%00r%00e%00e%00n}">
|
||||
href="data:text/css,%00b%00o%00d%00y%00{%00c%00o%00l%00o%00r%00:%00g%00r%00e%00e%00n%00}">
|
||||
</head>
|
||||
<body>
|
||||
This should be green
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
@ -1330,7 +1330,7 @@ fuzzy-if(skiaContent,1,5) == 482659-1d.html 482659-1-ref.html
|
||||
== 483565.xul 483565-ref.xul
|
||||
== 484256-1.html 484256-1-ref.html
|
||||
== 484256-2.html 484256-1-ref.html
|
||||
fails-if(stylo||styloVsGecko) == 485012-1.html 485012-1-ref.html # bug 1396093
|
||||
== 485012-1.html 485012-1-ref.html
|
||||
== 485275-1.html 485275-1-ref.html
|
||||
== 485275-1.svg 485275-1-ref.html
|
||||
== 486052-1.html 486052-1-ref.html
|
||||
|
@ -74,6 +74,11 @@ SERVO_BINDING_FUNC(Servo_StyleSet_RebuildCachedData, void,
|
||||
// they wrap the value in a struct.
|
||||
SERVO_BINDING_FUNC(Servo_StyleSet_MediumFeaturesChanged, uint8_t,
|
||||
RawServoStyleSetBorrowed set, bool* viewport_units_used)
|
||||
// We'd like to return `OriginFlags` here, but bindgen bitfield enums don't
|
||||
// work as return values with the Linux 32-bit ABI at the moment because
|
||||
// they wrap the value in a struct.
|
||||
SERVO_BINDING_FUNC(Servo_StyleSet_SetDevice, uint8_t,
|
||||
RawServoStyleSetBorrowed set, RawGeckoPresContextOwned pres_context)
|
||||
SERVO_BINDING_FUNC(Servo_StyleSet_Drop, void, RawServoStyleSetOwned set)
|
||||
SERVO_BINDING_FUNC(Servo_StyleSet_CompatModeChanged, void,
|
||||
RawServoStyleSetBorrowed raw_data)
|
||||
|
@ -105,10 +105,37 @@ ServoStyleSet::~ServoStyleSet()
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<ServoStyleSet>
|
||||
ServoStyleSet::CreateXBLServoStyleSet(
|
||||
nsPresContext* aPresContext,
|
||||
const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets)
|
||||
{
|
||||
auto set = MakeUnique<ServoStyleSet>(Kind::ForXBL);
|
||||
set->Init(aPresContext, nullptr);
|
||||
|
||||
// The XBL style sheets aren't document level sheets, but we need to
|
||||
// decide a particular SheetType to add them to style set. This type
|
||||
// doesn't affect the place where we pull those rules from
|
||||
// stylist::push_applicable_declarations_as_xbl_only_stylist().
|
||||
set->ReplaceSheets(SheetType::Doc, aNewSheets);
|
||||
|
||||
// Update stylist immediately.
|
||||
set->UpdateStylist();
|
||||
|
||||
// The PresContext of the bound document could be destroyed anytime later,
|
||||
// which shouldn't be used for XBL styleset, so we clear it here to avoid
|
||||
// dangling pointer.
|
||||
set->mPresContext = nullptr;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
void
|
||||
ServoStyleSet::Init(nsPresContext* aPresContext, nsBindingManager* aBindingManager)
|
||||
{
|
||||
mPresContext = aPresContext;
|
||||
mLastPresContextUsesXBLStyleSet = aPresContext;
|
||||
|
||||
mRawSet.reset(Servo_StyleSet_Init(aPresContext));
|
||||
mBindingManager = aBindingManager;
|
||||
|
||||
@ -164,15 +191,37 @@ ServoStyleSet::InvalidateStyleForCSSRuleChanges()
|
||||
mPresContext->RestyleManager()->AsServo()->PostRestyleEventForCSSRuleChanges();
|
||||
}
|
||||
|
||||
bool
|
||||
ServoStyleSet::SetPresContext(nsPresContext* aPresContext)
|
||||
{
|
||||
MOZ_ASSERT(IsForXBL(), "Only XBL styleset can set PresContext!");
|
||||
|
||||
mLastPresContextUsesXBLStyleSet = aPresContext;
|
||||
|
||||
const OriginFlags rulesChanged = static_cast<OriginFlags>(
|
||||
Servo_StyleSet_SetDevice(mRawSet.get(), aPresContext));
|
||||
|
||||
if (rulesChanged != OriginFlags(0)) {
|
||||
MarkOriginsDirty(rulesChanged);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
nsRestyleHint
|
||||
ServoStyleSet::MediumFeaturesChanged(bool aViewportChanged)
|
||||
{
|
||||
bool viewportUnitsUsed = false;
|
||||
const OriginFlags rulesChanged = static_cast<OriginFlags>(
|
||||
Servo_StyleSet_MediumFeaturesChanged(mRawSet.get(), &viewportUnitsUsed));
|
||||
bool rulesChanged = MediumFeaturesChangedRules(&viewportUnitsUsed);
|
||||
|
||||
if (rulesChanged != OriginFlags(0)) {
|
||||
MarkOriginsDirty(rulesChanged);
|
||||
if (mBindingManager &&
|
||||
mBindingManager->MediumFeaturesChanged(mPresContext)) {
|
||||
SetStylistXBLStyleSheetsDirty();
|
||||
rulesChanged = true;
|
||||
}
|
||||
|
||||
if (rulesChanged) {
|
||||
return eRestyle_Subtree;
|
||||
}
|
||||
|
||||
@ -183,6 +232,22 @@ ServoStyleSet::MediumFeaturesChanged(bool aViewportChanged)
|
||||
return nsRestyleHint(0);
|
||||
}
|
||||
|
||||
bool
|
||||
ServoStyleSet::MediumFeaturesChangedRules(bool* aViewportUnitsUsed)
|
||||
{
|
||||
MOZ_ASSERT(aViewportUnitsUsed);
|
||||
|
||||
const OriginFlags rulesChanged = static_cast<OriginFlags>(
|
||||
Servo_StyleSet_MediumFeaturesChanged(mRawSet.get(), aViewportUnitsUsed));
|
||||
|
||||
if (rulesChanged != OriginFlags(0)) {
|
||||
MarkOriginsDirty(rulesChanged);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_DEFINE_MALLOC_SIZE_OF(ServoStyleSetMallocSizeOf)
|
||||
|
||||
void
|
||||
@ -731,13 +796,6 @@ ServoStyleSet::InsertStyleSheetBefore(SheetType aType,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
ServoStyleSet::UpdateStyleSheet(ServoStyleSheet* aSheet)
|
||||
{
|
||||
MOZ_ASSERT(aSheet);
|
||||
// TODO(emilio): Get rid of this.
|
||||
}
|
||||
|
||||
int32_t
|
||||
ServoStyleSet::SheetCount(SheetType aType) const
|
||||
{
|
||||
@ -1372,13 +1430,21 @@ ServoStyleSet::UpdateStylist()
|
||||
{
|
||||
MOZ_ASSERT(StylistNeedsUpdate());
|
||||
|
||||
// There's no need to compute invalidations and such for an XBL styleset,
|
||||
// since they are loaded and unloaded synchronously, and they don't have to
|
||||
// deal with dynamic content changes.
|
||||
Element* root =
|
||||
IsMaster() ? mPresContext->Document()->GetDocumentElement() : nullptr;
|
||||
if (mStylistState & StylistState::StyleSheetsDirty) {
|
||||
// There's no need to compute invalidations and such for an XBL styleset,
|
||||
// since they are loaded and unloaded synchronously, and they don't have to
|
||||
// deal with dynamic content changes.
|
||||
Element* root =
|
||||
IsMaster() ? mPresContext->Document()->GetDocumentElement() : nullptr;
|
||||
|
||||
Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root);
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(mStylistState & StylistState::XBLStyleSheetsDirty)) {
|
||||
MOZ_ASSERT(IsMaster(), "Only master styleset can mark XBL stylesets dirty!");
|
||||
mBindingManager->UpdateBoundContentBindingsForServo(mPresContext);
|
||||
}
|
||||
|
||||
Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root);
|
||||
mStylistState = StylistState::NotDirty;
|
||||
}
|
||||
|
||||
|
@ -46,18 +46,22 @@ struct TreeMatchContext;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* A few flags used to track which kind of stylist state we may need to
|
||||
* update.
|
||||
*/
|
||||
// A few flags used to track which kind of stylist state we may need to
|
||||
// update.
|
||||
enum class StylistState : uint8_t {
|
||||
/** The stylist is not dirty, we should do nothing */
|
||||
// The stylist is not dirty, we should do nothing.
|
||||
NotDirty = 0,
|
||||
|
||||
/** The style sheets have changed, so we need to update the style data. */
|
||||
StyleSheetsDirty,
|
||||
// The style sheets have changed, so we need to update the style data.
|
||||
StyleSheetsDirty = 1 << 0,
|
||||
|
||||
// Some of the style sheets of the bound elements in binding manager have
|
||||
// changed, so we need to tell the binding manager to update style data.
|
||||
XBLStyleSheetsDirty = 1 << 1,
|
||||
};
|
||||
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StylistState)
|
||||
|
||||
// Bitfield type to represent Servo stylesheet origins.
|
||||
enum class OriginFlags : uint8_t {
|
||||
UserAgent = 0x01,
|
||||
@ -114,6 +118,10 @@ public:
|
||||
explicit ServoStyleSet(Kind aKind);
|
||||
~ServoStyleSet();
|
||||
|
||||
static UniquePtr<ServoStyleSet>
|
||||
CreateXBLServoStyleSet(nsPresContext* aPresContext,
|
||||
const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets);
|
||||
|
||||
void Init(nsPresContext* aPresContext, nsBindingManager* aBindingManager);
|
||||
void BeginShutdown();
|
||||
void Shutdown();
|
||||
@ -133,6 +141,9 @@ public:
|
||||
|
||||
nsRestyleHint MediumFeaturesChanged(bool aViewportChanged);
|
||||
|
||||
// aViewportChanged outputs whether any viewport units is used.
|
||||
bool MediumFeaturesChangedRules(bool* aViewportUnitsUsed);
|
||||
|
||||
void InvalidateStyleForCSSRuleChanges();
|
||||
|
||||
void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const;
|
||||
@ -231,11 +242,6 @@ public:
|
||||
ServoStyleSheet* aNewSheet,
|
||||
ServoStyleSheet* aReferenceSheet);
|
||||
|
||||
// Notify servo that the underlying raw sheet has changed, through cloning.
|
||||
// This should only be called on a just-cloned sheet, because it does not
|
||||
// mark the stylesheets as dirty either here or in servo.
|
||||
void UpdateStyleSheet(ServoStyleSheet* aSheet);
|
||||
|
||||
int32_t SheetCount(SheetType aType) const;
|
||||
ServoStyleSheet* StyleSheetAt(SheetType aType, int32_t aIndex) const;
|
||||
|
||||
@ -428,11 +434,15 @@ public:
|
||||
// Returns the style rule map.
|
||||
ServoStyleRuleMap* StyleRuleMap();
|
||||
|
||||
// Clear mPresContext. This is needed after XBL ServoStyleSet is created.
|
||||
void ClearPresContext() {
|
||||
mPresContext = nullptr;
|
||||
// Return whether this is the last PresContext which uses this XBL styleset.
|
||||
bool IsPresContextChanged(nsPresContext* aPresContext) const {
|
||||
return aPresContext != mLastPresContextUsesXBLStyleSet;
|
||||
}
|
||||
|
||||
// Set PresContext (i.e. Device) for mRawSet. This should be called only
|
||||
// by XBL stylesets. Returns true if there is any rule changing.
|
||||
bool SetPresContext(nsPresContext* aPresContext);
|
||||
|
||||
/**
|
||||
* Returns true if a modification to an an attribute with the specified
|
||||
* local name might require us to restyle the element.
|
||||
@ -525,7 +535,12 @@ private:
|
||||
*/
|
||||
void SetStylistStyleSheetsDirty()
|
||||
{
|
||||
mStylistState = StylistState::StyleSheetsDirty;
|
||||
mStylistState |= StylistState::StyleSheetsDirty;
|
||||
}
|
||||
|
||||
void SetStylistXBLStyleSheetsDirty()
|
||||
{
|
||||
mStylistState |= StylistState::XBLStyleSheetsDirty;
|
||||
}
|
||||
|
||||
bool StylistNeedsUpdate() const
|
||||
@ -565,7 +580,15 @@ private:
|
||||
ServoStyleSheet* aSheet);
|
||||
|
||||
const Kind mKind;
|
||||
nsPresContext* mPresContext;
|
||||
|
||||
// Nullptr if this is an XBL style set.
|
||||
nsPresContext* MOZ_NON_OWNING_REF mPresContext = nullptr;
|
||||
|
||||
// Because XBL style set could be used by multiple PresContext, we need to
|
||||
// store the last PresContext pointer which uses this XBL styleset for
|
||||
// computing medium rule changes.
|
||||
void* MOZ_NON_OWNING_REF mLastPresContextUsesXBLStyleSet = nullptr;
|
||||
|
||||
UniquePtr<RawServoStyleSet> mRawSet;
|
||||
EnumeratedArray<SheetType, SheetType::Count,
|
||||
nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
|
||||
|
@ -2686,8 +2686,8 @@ nsStyleSet::MediumFeaturesChanged(bool aViewportChanged)
|
||||
}
|
||||
|
||||
if (mBindingManager) {
|
||||
bool thisChanged = false;
|
||||
mBindingManager->MediumFeaturesChanged(presContext, &thisChanged);
|
||||
bool thisChanged =
|
||||
mBindingManager->MediumFeaturesChanged(presContext);
|
||||
stylesChanged = stylesChanged || thisChanged;
|
||||
}
|
||||
|
||||
|
@ -94,8 +94,8 @@ void nsStyleUtil::AppendEscapedCSSString(const nsAString& aString,
|
||||
const char16_t* in = aString.BeginReading();
|
||||
const char16_t* const end = aString.EndReading();
|
||||
for (; in != end; in++) {
|
||||
if (*in < 0x20 || (*in >= 0x7F && *in < 0xA0)) {
|
||||
// Escape U+0000 through U+001F and U+007F through U+009F numerically.
|
||||
if (*in < 0x20 || *in == 0x7F) {
|
||||
// Escape U+0000 through U+001F and U+007F numerically.
|
||||
aReturn.AppendPrintf("\\%x ", *in);
|
||||
} else {
|
||||
if (*in == '"' || *in == '\'' || *in == '\\') {
|
||||
@ -158,8 +158,8 @@ nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, nsAString& aReturn)
|
||||
char16_t ch = *in;
|
||||
if (ch == 0x00) {
|
||||
aReturn.Append(char16_t(0xFFFD));
|
||||
} else if (ch < 0x20 || (0x7F <= ch && ch < 0xA0)) {
|
||||
// Escape U+0000 through U+001F and U+007F through U+009F numerically.
|
||||
} else if (ch < 0x20 || 0x7F == ch) {
|
||||
// Escape U+0000 through U+001F and U+007F numerically.
|
||||
aReturn.AppendPrintf("\\%x ", *in);
|
||||
} else {
|
||||
// Escape ASCII non-identifier printables as a backslash plus
|
||||
|
@ -242,7 +242,6 @@ skip-if = !stylo
|
||||
skip-if = android_version == '18' #debug-only failure; timed out #Android 4.3 aws only; bug 1030419
|
||||
[test_media_queries_dynamic.html]
|
||||
[test_media_queries_dynamic_xbl.html]
|
||||
fail-if = stylo # bug 1382078
|
||||
[test_media_query_list.html]
|
||||
[test_media_query_serialization.html]
|
||||
[test_moz_device_pixel_ratio.html]
|
||||
|
@ -75,7 +75,7 @@ is(CSS.escape('-9a'), '-\\39 a', "escapingFailed Char: -9a");
|
||||
|
||||
is(CSS.escape('--a'), '--a', 'Should not need to escape leading "--"');
|
||||
|
||||
is(CSS.escape('\x80\x2D\x5F\xA9'), '\\80 \x2D\x5F\xA9', "escapingFailed Char: \\x80\\x2D\\x5F\\xA9");
|
||||
is(CSS.escape('\x7F\x80\x2D\x5F\xA9'), '\\7f \x80\x2D\x5F\xA9', "escapingFailed Char: \\x7F\\x80\\x2D\\x5F\\xA9");
|
||||
is(CSS.escape('\xA0\xA1\xA2'), '\xA0\xA1\xA2', "escapingFailed Char: \\xA0\\xA1\\xA2");
|
||||
is(CSS.escape('a0123456789b'), 'a0123456789b', "escapingFailed Char: a0123465789");
|
||||
is(CSS.escape('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz', "escapingFailed Char: abcdefghijklmnopqrstuvwxyz");
|
||||
|
@ -18,6 +18,8 @@
|
||||
// That's not the point of the test, though; the point is only that
|
||||
// *that text* is properly escaped.
|
||||
|
||||
const isStylo = SpecialPowers.DOMWindowUtils.isStyledByServo;
|
||||
|
||||
// There is one "pattern" for each code path through the error reporter
|
||||
// that might need to escape some kind of user-supplied text.
|
||||
// Each "pattern" is tested once with each of the "substitution"s below:
|
||||
@ -39,13 +41,14 @@ let patterns = [
|
||||
// _AtKeyword
|
||||
{ i: "x{@<t>: }", o: "declaration but found \u2018@<i>\u2019." },
|
||||
// _String
|
||||
{ i: "x{ '<t>'}" , o: "declaration but found \u2018'<s>'\u2019." },
|
||||
{ i: "x{ '<t>'}" , o: isStylo ? 'declaration but found \u2018"<s>"\u2019.'
|
||||
: "declaration but found \u2018'<s>'\u2019." },
|
||||
// _Bad_String
|
||||
{ i: "x{ '<t>\n}", o: "declaration but found \u2018'<s>\u2019." },
|
||||
// FIXME: temporarily disabled https://bugzilla.mozilla.org/show_bug.cgi?id=1396664
|
||||
{ i: "x{ '<t>\n}", o: isStylo ? "declaration but found \u2018\"<bad string>\n\u2019."
|
||||
: "declaration but found \u2018'<s>\u2019." },
|
||||
];
|
||||
|
||||
const isStylo = SpecialPowers.DOMWindowUtils.isStyledByServo;
|
||||
|
||||
// Stylo's CSS parser only reports the 'url(' token, not the actual bad URL.
|
||||
if (!isStylo) {
|
||||
patterns.push(
|
||||
@ -97,7 +100,7 @@ const substitutions = [
|
||||
// ASCII printables that must be escaped in identifiers.
|
||||
// Most of these should not be escaped in strings.
|
||||
{ t: "\\!\\\"\\#\\$", i: "\\!\\\"\\#\\$", s: "!\\\"#$" },
|
||||
{ t: "\\%\\&\\'\\(", i: "\\%\\&\\'\\(", s: "%&\\'(" },
|
||||
{ t: "\\%\\&\\'\\(", i: "\\%\\&\\'\\(", s: isStylo ? "%&'(" : "%&\\'(" },
|
||||
{ t: "\\)\\*\\+\\,", i: "\\)\\*\\+\\,", s: ")*+," },
|
||||
{ t: "\\.\\/\\:\\;", i: "\\.\\/\\:\\;", s: "./:;" },
|
||||
{ t: "\\<\\=\\>\\?", i: "\\<\\=\\>\\?", s: "<=>?", },
|
||||
@ -127,26 +130,26 @@ const substitutions = [
|
||||
s: "\\1c \\1d \\1e \\1f " },
|
||||
|
||||
// U+007F (DELETE) and U+0080 - U+009F (C1 controls)
|
||||
{ t: "\\\x7f\\\x80\\\x81\\\x82", i: "\\7f \\80 \\81 \\82 ",
|
||||
s: "\\7f \\80 \\81 \\82 " },
|
||||
{ t: "\\\x83\\\x84\\\x85\\\x86", i: "\\83 \\84 \\85 \\86 ",
|
||||
s: "\\83 \\84 \\85 \\86 " },
|
||||
{ t: "\\\x87\\\x88\\\x89\\\x8A", i: "\\87 \\88 \\89 \\8a ",
|
||||
s: "\\87 \\88 \\89 \\8a " },
|
||||
{ t: "\\\x8B\\\x8C\\\x8D\\\x8E", i: "\\8b \\8c \\8d \\8e ",
|
||||
s: "\\8b \\8c \\8d \\8e " },
|
||||
{ t: "\\\x8F\\\x90\\\x91\\\x92", i: "\\8f \\90 \\91 \\92 ",
|
||||
s: "\\8f \\90 \\91 \\92 " },
|
||||
{ t: "\\\x93\\\x94\\\x95\\\x96", i: "\\93 \\94 \\95 \\96 ",
|
||||
s: "\\93 \\94 \\95 \\96 " },
|
||||
{ t: "\\\x97\\\x98\\\x99\\\x9A", i: "\\97 \\98 \\99 \\9a ",
|
||||
s: "\\97 \\98 \\99 \\9a " },
|
||||
{ t: "\\\x9B\\\x9C\\\x9D\\\x9E\\\x9F", i: "\\9b \\9c \\9d \\9e \\9f ",
|
||||
s: "\\9b \\9c \\9d \\9e \\9f " },
|
||||
{ t: "\\\x7f\\\x80\\\x81\\\x82", i: "\\7f \x80\x81\x82",
|
||||
s: "\\7f \x80\x81\x82" },
|
||||
{ t: "\\\x83\\\x84\\\x85\\\x86", i: "\x83\x84\x85\x86",
|
||||
s: "\x83\x84\x85\x86" },
|
||||
{ t: "\\\x87\\\x88\\\x89\\\x8A", i: "\x87\x88\x89\x8A",
|
||||
s: "\x87\x88\x89\x8A" },
|
||||
{ t: "\\\x8B\\\x8C\\\x8D\\\x8E", i: "\x8B\x8C\x8D\x8E",
|
||||
s: "\x8B\x8C\x8D\x8E" },
|
||||
{ t: "\\\x8F\\\x90\\\x91\\\x92", i: "\x8F\x90\x91\x92",
|
||||
s: "\x8F\x90\x91\x92" },
|
||||
{ t: "\\\x93\\\x94\\\x95\\\x96", i: "\x93\x94\x95\x96",
|
||||
s: "\x93\x94\x95\x96" },
|
||||
{ t: "\\\x97\\\x98\\\x99\\\x9A", i: "\x97\x98\x99\x9A",
|
||||
s: "\x97\x98\x99\x9A" },
|
||||
{ t: "\\\x9B\\\x9C\\\x9D\\\x9E\\\x9F", i: "\x9B\x9C\x9D\x9E\x9F",
|
||||
s: "\x9B\x9C\x9D\x9E\x9F" },
|
||||
|
||||
// CSS doesn't bother with the full Unicode rules for identifiers,
|
||||
// instead declaring that any code point greater than or equal to
|
||||
// U+00A0 is a valid identifier character. Test a small handful
|
||||
// U+0080 is a valid identifier character. Test a small handful
|
||||
// of both basic and astral plane characters.
|
||||
|
||||
// Arabic (caution to editors: there is a possibly-invisible U+200E
|
||||
|
@ -35,9 +35,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct LinkedList_s LinkedList;
|
||||
|
||||
struct LinkedList_s {
|
||||
struct LinkedList {
|
||||
LinkedList *next;
|
||||
LinkedList *prev;
|
||||
};
|
||||
|
@ -420,12 +420,12 @@ static pthread_key_t tlsIndex;
|
||||
#define malloc_mutex_t CRITICAL_SECTION
|
||||
#define malloc_spinlock_t CRITICAL_SECTION
|
||||
#elif defined(XP_DARWIN)
|
||||
typedef struct {
|
||||
struct malloc_mutex_t {
|
||||
OSSpinLock lock;
|
||||
} malloc_mutex_t;
|
||||
typedef struct {
|
||||
};
|
||||
struct malloc_spinlock_t {
|
||||
OSSpinLock lock;
|
||||
} malloc_spinlock_t;
|
||||
};
|
||||
#else
|
||||
typedef pthread_mutex_t malloc_mutex_t;
|
||||
typedef pthread_mutex_t malloc_spinlock_t;
|
||||
@ -449,14 +449,12 @@ static malloc_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
* Statistics data structures.
|
||||
*/
|
||||
|
||||
typedef struct malloc_bin_stats_s malloc_bin_stats_t;
|
||||
struct malloc_bin_stats_s {
|
||||
struct malloc_bin_stats_t {
|
||||
/* Current number of runs in this bin. */
|
||||
unsigned long curruns;
|
||||
};
|
||||
|
||||
typedef struct arena_stats_s arena_stats_t;
|
||||
struct arena_stats_s {
|
||||
struct arena_stats_t {
|
||||
/* Number of bytes currently mapped. */
|
||||
size_t mapped;
|
||||
|
||||
@ -483,8 +481,7 @@ enum ChunkType {
|
||||
};
|
||||
|
||||
/* Tree of extents. */
|
||||
typedef struct extent_node_s extent_node_t;
|
||||
struct extent_node_s {
|
||||
struct extent_node_t {
|
||||
/* Linkage for the size/address-ordered tree. */
|
||||
rb_node(extent_node_t) link_szad;
|
||||
|
||||
@ -517,8 +514,7 @@ typedef rb_tree(extent_node_t) extent_tree_t;
|
||||
# define MALLOC_RTREE_NODESIZE CACHELINE
|
||||
#endif
|
||||
|
||||
typedef struct malloc_rtree_s malloc_rtree_t;
|
||||
struct malloc_rtree_s {
|
||||
struct malloc_rtree_t {
|
||||
malloc_spinlock_t lock;
|
||||
void **root;
|
||||
unsigned height;
|
||||
@ -530,12 +526,11 @@ struct malloc_rtree_s {
|
||||
* Arena data structures.
|
||||
*/
|
||||
|
||||
typedef struct arena_s arena_t;
|
||||
typedef struct arena_bin_s arena_bin_t;
|
||||
struct arena_t;
|
||||
struct arena_bin_t;
|
||||
|
||||
/* Each element of the chunk map corresponds to one page within the chunk. */
|
||||
typedef struct arena_chunk_map_s arena_chunk_map_t;
|
||||
struct arena_chunk_map_s {
|
||||
struct arena_chunk_map_t {
|
||||
/*
|
||||
* Linkage for run trees. There are two disjoint uses:
|
||||
*
|
||||
@ -618,8 +613,7 @@ typedef rb_tree(arena_chunk_map_t) arena_avail_tree_t;
|
||||
typedef rb_tree(arena_chunk_map_t) arena_run_tree_t;
|
||||
|
||||
/* Arena chunk header. */
|
||||
typedef struct arena_chunk_s arena_chunk_t;
|
||||
struct arena_chunk_s {
|
||||
struct arena_chunk_t {
|
||||
/* Arena that owns the chunk. */
|
||||
arena_t *arena;
|
||||
|
||||
@ -644,8 +638,7 @@ struct arena_chunk_s {
|
||||
};
|
||||
typedef rb_tree(arena_chunk_t) arena_chunk_tree_t;
|
||||
|
||||
typedef struct arena_run_s arena_run_t;
|
||||
struct arena_run_s {
|
||||
struct arena_run_t {
|
||||
#if defined(MOZ_DEBUG) || defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
|
||||
uint32_t magic;
|
||||
# define ARENA_RUN_MAGIC 0x384adf93
|
||||
@ -664,7 +657,7 @@ struct arena_run_s {
|
||||
unsigned regs_mask[1]; /* Dynamically sized. */
|
||||
};
|
||||
|
||||
struct arena_bin_s {
|
||||
struct arena_bin_t {
|
||||
/*
|
||||
* Current run being used to service allocations of this bin's size
|
||||
* class.
|
||||
@ -699,7 +692,7 @@ struct arena_bin_s {
|
||||
malloc_bin_stats_t stats;
|
||||
};
|
||||
|
||||
struct arena_s {
|
||||
struct arena_t {
|
||||
#if defined(MOZ_DEBUG) || defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
|
||||
uint32_t magic;
|
||||
# define ARENA_MAGIC 0x947d3d24
|
||||
|
@ -158,6 +158,41 @@ IsNotWin7PreRTM()
|
||||
return IsWin7SP1OrLater() || IsWindowsBuildOrLater(7600);
|
||||
}
|
||||
|
||||
inline bool
|
||||
IsWin7AndPre2000Compatible() {
|
||||
/*
|
||||
* See Bug 1279171.
|
||||
* We'd like to avoid using WMF on specific OS version when compatibility
|
||||
* mode is in effect. The purpose of this function is to check if FF runs on
|
||||
* Win7 OS with application compatibility mode being set to 95/98/ME.
|
||||
* Those compatibility mode options (95/98/ME) can only display and
|
||||
* be selected for 32-bit application.
|
||||
* If the compatibility mode is in effect, the GetVersionEx function will
|
||||
* report the OS as it identifies itself, which may not be the OS that is
|
||||
* installed.
|
||||
* Note : 1) We only target for Win7 build number greater than 7600.
|
||||
* 2) GetVersionEx may be altered or unavailable for release after
|
||||
* Win8.1. Set pragma to avoid build warning as error.
|
||||
*/
|
||||
bool isWin7 = IsNotWin7PreRTM() && !IsWin8OrLater();
|
||||
if (!isWin7) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OSVERSIONINFOEX info;
|
||||
ZeroMemory(&info, sizeof(OSVERSIONINFOEX));
|
||||
|
||||
info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4996)
|
||||
bool success = GetVersionEx((LPOSVERSIONINFO) &info);
|
||||
#pragma warning(pop)
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
return info.dwMajorVersion < 5;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* mozilla_WindowsVersion_h */
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 230 B |
Binary file not shown.
Before Width: | Height: | Size: 622 B After Width: | Height: | Size: 238 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user