Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2017-06-08 15:16:36 +02:00
commit 247a47fac9
1327 changed files with 20934 additions and 11671 deletions

View File

@ -2,4 +2,3 @@
# See http://pep8.readthedocs.io/en/latest/intro.html#configuration
ignore = E121, E123, E126, E129, E133, E226, E241, E242, E704, W503, E402
max-line-length = 99
filename = *.py, +.lint

View File

@ -1,5 +1,5 @@
python
import sys
sys.path.append('python/gdbpp/')
sys.path.append('third_party/python/gdbpp/')
import gdbpp
end

6
.gitignore vendored
View File

@ -70,9 +70,9 @@ parser/html/java/javaparser/
/local.properties
# Python virtualenv artifacts.
python/psutil/**/*.so
python/psutil/**/*.pyd
python/psutil/build/
third_party/python/psutil/**/*.so
third_party/python/psutil/**/*.pyd
third_party/python/psutil/build/
# Ignore chrome.manifest files from the devtools loader
devtools/client/chrome.manifest

View File

@ -72,9 +72,9 @@ _OPT\.OBJ/
^local.properties$
# Python stuff installed at build time.
^python/psutil/.*\.so
^python/psutil/.*\.pyd
^python/psutil/build/
^third_party/python/psutil/.*\.so
^third_party/python/psutil/.*\.pyd
^third_party/python/psutil/build/
# Git repositories
.git/

View File

@ -1,15 +1,15 @@
# .lldbinit file for debugging Mozilla
# -----------------------------------------------------------------------------
# For documentation on all of the commands and type summaries defined here
# and in the accompanying Python scripts, see python/lldbutils/README.txt.
# For documentation on all of the commands and type summaries defined here and
# in the accompanying Python scripts, see third_party/python/lldbutils/README.txt.
# -----------------------------------------------------------------------------
# Import the module that defines complex Gecko debugging commands. This assumes
# you are either running lldb from the top level source directory, the objdir,
# or the dist/bin directory. (.lldbinit files in the objdir and dist/bin set
# topsrcdir appropriately.)
script topsrcdir = topsrcdir if locals().has_key("topsrcdir") else os.getcwd(); sys.path.append(os.path.join(topsrcdir, "python/lldbutils")); import lldbutils; lldbutils.init()
script topsrcdir = topsrcdir if locals().has_key("topsrcdir") else os.getcwd(); sys.path.append(os.path.join(topsrcdir, "third_party/python/lldbutils")); import lldbutils; lldbutils.init()
# Mozilla's use of UNIFIED_SOURCES to include multiple source files into a
# single compiled file breaks lldb breakpoint setting. This works around that.

View File

@ -22,5 +22,5 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1361661 - Update Telemetry build and headers.
Bug 1346025 - Move vendored python modules to /third_party/python (need to clobber virtualenv)

View File

@ -997,10 +997,6 @@ pref("browser.flash-protected-mode-flip.done", false);
pref("dom.ipc.shims.enabledWarnings", false);
// Start the browser in e10s mode
pref("browser.tabs.remote.autostart", false);
pref("browser.tabs.remote.desktopbehavior", true);
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
// Controls whether and how the Windows NPAPI plugin process is sandboxed.
// To get a different setting for a particular plugin replace "default", with
@ -1517,7 +1513,11 @@ pref("privacy.usercontext.about_newtab_segregation.enabled", false);
pref("privacy.userContext.longPressBehavior", 0);
#endif
#ifndef RELEASE_OR_BETA
// Start the browser in e10s mode
pref("browser.tabs.remote.autostart", false);
pref("browser.tabs.remote.desktopbehavior", true);
#if !defined(RELEASE_OR_BETA) || defined(MOZ_DEV_EDITION)
// At the moment, autostart.2 is used, while autostart.1 is unused.
// We leave it here set to false to reset users' defaults and allow
// us to change everybody to true in the future, when desired.
@ -1663,7 +1663,7 @@ pref("extensions.formautofill.experimental", true);
pref("extensions.formautofill.experimental", false);
#endif
pref("extensions.formautofill.addresses.enabled", true);
pref("extensions.formautofill.heuristics.enabled", false);
pref("extensions.formautofill.heuristics.enabled", true);
pref("extensions.formautofill.loglevel", "Warn");
// Whether or not to restore a session with lazy-browser tabs.

View File

@ -138,16 +138,6 @@ tabbrowser {
visibility: hidden; /* temporary space to keep a tab's close button under the cursor */
}
.tabs-newtab-button > .toolbarbutton-menu-dropmarker,
#new-tab-button > .toolbarbutton-menu-dropmarker {
display: none;
}
/* override drop marker image padding */
.tabs-newtab-button > .toolbarbutton-icon {
margin-inline-end: 0;
}
.tabbrowser-tab {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
}

View File

@ -169,7 +169,6 @@
consumeoutsideclicks="false"
level="parent"
tabspecific="true">
<iframe id="dateTimePopupFrame"/>
</panel>
<!-- for select dropdowns. The menupopup is what shows the list of options,

View File

@ -28,6 +28,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
var {Cc: classes, Ci: interfaces} = Components;
/**
* A number of iterations after which to yield time back
* to the system.

View File

@ -2876,7 +2876,7 @@
}
// Mute audio immediately to improve perceived speed of tab closure.
if (aTab.hasAttribute("soundplaying")) {
if (!aAdoptedByTab && aTab.hasAttribute("soundplaying")) {
// Don't persist the muted state as this wasn't a user action.
// This lets undo-close-tab return it to an unmuted state.
aTab.linkedBrowser.mute(true);

View File

@ -63,7 +63,9 @@ add_task(async function test_setup_html() {
let video = videoIframe.contentDocument.querySelector("video");
audio.loop = true;
audio.src = "audio.ogg";
video.loop = true;
video.src = "video.ogg";
let awaitPause = ContentTaskUtils.waitForEvent(audio, "pause");
await ContentTaskUtils.waitForCondition(() => !audio.paused, "Making sure audio is playing before calling pause");

View File

@ -3,8 +3,17 @@
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
const PREF_NEWTAB_ACTIVITY_STREAM = "browser.newtabpage.activity-stream.enabled";
Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
Services.prefs.setBoolPref(PREF_NEWTAB_ACTIVITY_STREAM, false);
// Opens and closes a new tab to clear any existing preloaded ones. This is
// necessary to prevent any left-over activity-stream preloaded new tabs from
// affecting these tests.
BrowserOpenTab();
const initialTab = gBrowser.selectedTab;
gBrowser.removeTab(initialTab);
var tmp = {};
Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
@ -78,6 +87,7 @@ registerCleanupFunction(function() {
});
Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
Services.prefs.clearUserPref(PREF_NEWTAB_ACTIVITY_STREAM);
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
return watchLinksChangeOnce();

View File

@ -1012,22 +1012,27 @@ this.PanelMultiView = class {
// Take the label for toolbarbuttons; it only exists on those elements.
element = element.labelElement || element;
let bounds = this._dwu.getBoundsWithoutFlushing(element);
let bounds = element.getBoundingClientRect();
let previous = this._multiLineElementsMap.get(element);
// Only remove the 'height' property, which will cause a layout flush, when
// absolutely necessary.
// We don't need to (re-)apply the workaround for invisible elements or
// on elements we've seen before and haven't changed in the meantime.
if (!bounds.width || !bounds.height ||
(previous && element.textContent == previous.textContent &&
bounds.width == previous.bounds.width)) {
continue;
}
element.style.removeProperty("height");
items.push({ element });
}
// Removing the 'height' property will only cause a layout flush in the next
// loop below if it was set.
for (let item of items) {
item.element.style.removeProperty("height");
}
// We now read the computed style to store the height of any element that
// may contain wrapping text, which will be zero if the element is hidden.
// may contain wrapping text.
for (let item of items) {
item.bounds = item.element.getBoundingClientRect();
}

View File

@ -60,7 +60,33 @@ const PanelUI = {
Services.obs.addObserver(this, "fullscreen-nav-toolbox");
Services.obs.addObserver(this, "appMenu-notifications");
window.addEventListener("fullscreen", this);
XPCOMUtils.defineLazyPreferenceGetter(this, "autoHideToolbarInFullScreen",
"browser.fullscreen.autohide", false, (pref, previousValue, newValue) => {
// On OSX, or with autohide preffed off, MozDOMFullscreen is the only
// event we care about, since fullscreen should behave just like non
// fullscreen. Otherwise, we don't want to listen to these because
// we'd just be spamming ourselves with both of them whenever a user
// opened a video.
if (newValue) {
window.removeEventListener("MozDOMFullscreen:Entered", this);
window.removeEventListener("MozDOMFullscreen:Exited", this);
window.addEventListener("fullscreen", this);
} else {
window.addEventListener("MozDOMFullscreen:Entered", this);
window.addEventListener("MozDOMFullscreen:Exited", this);
window.removeEventListener("fullscreen", this);
}
this._updateNotifications(false);
}, autoHidePref => autoHidePref && Services.appinfo.OS !== "Darwin");
if (this.autoHideToolbarInFullScreen) {
window.addEventListener("fullscreen", this);
} else {
window.addEventListener("MozDOMFullscreen:Entered", this);
window.addEventListener("MozDOMFullscreen:Exited", this);
}
window.addEventListener("activate", this);
window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
CustomizableUI.addListener(this);
@ -175,6 +201,8 @@ const PanelUI = {
Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
Services.obs.removeObserver(this, "appMenu-notifications");
window.removeEventListener("MozDOMFullscreen:Entered", this);
window.removeEventListener("MozDOMFullscreen:Exited", this);
window.removeEventListener("fullscreen", this);
window.removeEventListener("activate", this);
this.menuButton.removeEventListener("mousedown", this);
@ -326,6 +354,8 @@ const PanelUI = {
case "keypress":
this.toggle(aEvent);
break;
case "MozDOMFullscreen:Entered":
case "MozDOMFullscreen:Exited":
case "fullscreen":
case "activate":
this._updateNotifications();
@ -744,10 +774,8 @@ const PanelUI = {
this._showBannerItem(notifications[0]);
}
} else if (doorhangers.length > 0) {
let autoHideFullScreen = Services.prefs.getBoolPref("browser.fullscreen.autohide", false) &&
Services.appinfo.OS !== "Darwin";
// Only show the doorhanger if the window is focused and not fullscreen
if ((window.fullScreen && autoHideFullScreen) || Services.focus.activeWindow !== window) {
if ((window.fullScreen && this.autoHideToolbarInFullScreen) || Services.focus.activeWindow !== window) {
this._hidePopup();
this._showBadge(doorhangers[0]);
this._showBannerItem(doorhangers[0]);

View File

@ -156,6 +156,8 @@ skip-if = os == "mac"
[browser_panelUINotifications_fullscreen.js]
tags = fullscreen
skip-if = os == "mac"
[browser_panelUINotifications_fullscreen_noAutoHideToolbar.js]
tags = fullscreen
[browser_panelUINotifications_multiWindow.js]
[browser_switch_to_customize_mode.js]
[browser_synced_tabs_menu.js]

View File

@ -0,0 +1,57 @@
"use strict";
Cu.import("resource://gre/modules/AppMenuNotifications.jsm");
add_task(async function testFullscreen() {
if (Services.appinfo.OS !== "Darwin") {
await SpecialPowers.pushPrefEnv({
set: [
["browser.fullscreen.autohide", false],
]});
}
is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
let mainActionCalled = false;
let mainAction = {
callback: () => { mainActionCalled = true; }
};
AppMenuNotifications.showNotification("update-manual", mainAction);
isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
let doorhanger = notifications[0];
is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
let fullscreenPromise = BrowserTestUtils.waitForEvent(window, "fullscreen");
EventUtils.synthesizeKey("VK_F11", {});
await fullscreenPromise;
isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is still showing after entering fullscreen.");
let popuphiddenPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popuphidden");
await ContentTask.spawn(gBrowser.selectedBrowser, {}, async () => {
content.document.documentElement.requestFullscreen();
});
await popuphiddenPromise;
await new Promise(executeSoon);
is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is hidden after entering DOM fullscreen.");
let popupshownPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popupshown");
await ContentTask.spawn(gBrowser.selectedBrowser, {}, async () => {
content.document.exitFullscreen();
});
await popupshownPromise;
await new Promise(executeSoon);
isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is shown after exiting DOM fullscreen.");
isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is not displaying on PanelUI button.");
let mainActionButton = document.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
mainActionButton.click();
ok(mainActionCalled, "Main action callback was called");
is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
fullscreenPromise = BrowserTestUtils.waitForEvent(window, "fullscreen");
EventUtils.synthesizeKey("VK_F11", {});
await fullscreenPromise;
});

View File

@ -25,17 +25,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
// Load mocking/stubbing library, sinon
// docs: http://sinonjs.org/docs/
/* global sinon:false */
Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
registerCleanupFunction(function() {
gTestTargetFile.remove(false);
delete window.sinon;
delete window.setImmediate;
delete window.clearImmediate;
});
// Asynchronous support subroutines

View File

@ -4,13 +4,16 @@ let Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
const PREF_NEWTAB_ACTIVITY_STREAM = "browser.newtabpage.activity-stream.enabled";
Services.prefs.setBoolPref(PREF_NEWTAB_ACTIVITY_STREAM, false);
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
registerCleanupFunction(function() {
Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", false);
Services.prefs.clearUserPref(PREF_NEWTAB_ACTIVITY_STREAM);
aboutNewTabService.resetNewTabURL();
});

View File

@ -95,6 +95,7 @@ var gSyncPane = {
"weave:service:logout:finish",
FxAccountsCommon.ONVERIFIED_NOTIFICATION,
FxAccountsCommon.ONLOGIN_NOTIFICATION,
FxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
];
// Add the observers now and remove them on unload

View File

@ -51,6 +51,7 @@ skip-if = true || !healthreport # Bug 1185403 for the "true"
[browser_security.js]
[browser_siteData.js]
[browser_siteData2.js]
[browser_site_login_exceptions.js]
[browser_subdialogs.js]
support-files =
subdialog.xul

View File

@ -6,8 +6,8 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/* import-globals-from ../../../../../testing/modules/sinon-1.16.1.js */
Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
/* global sinon */
Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
const TEST_QUOTA_USAGE_HOST = "example.com";
const TEST_QUOTA_USAGE_ORIGIN = "https://" + TEST_QUOTA_USAGE_HOST;
@ -89,8 +89,6 @@ function promiseCookiesCleared() {
registerCleanupFunction(function() {
delete window.sinon;
delete window.setImmediate;
delete window.clearImmediate;
mockOfflineAppCacheHelper.unregister();
});

View File

@ -0,0 +1,76 @@
"use strict";
const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
var exceptionsDialog;
add_task(async function openLoginExceptionsSubDialog() {
// Undo the save password change.
registerCleanupFunction(async function() {
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
let doc = content.document;
let savePasswordCheckBox = doc.getElementById("savePasswords");
if (savePasswordCheckBox.checked) {
savePasswordCheckBox.click();
}
});
gBrowser.removeCurrentTab();
});
await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
let doc = content.document;
let savePasswordCheckBox = doc.getElementById("savePasswords");
Assert.ok(!savePasswordCheckBox.checked,
"Save Password CheckBox should be unchecked by default");
savePasswordCheckBox.click();
let loginExceptionsButton = doc.getElementById("passwordExceptions");
loginExceptionsButton.click();
});
exceptionsDialog = await dialogOpened;
});
add_task(async function addALoginException() {
let doc = exceptionsDialog.document;
let tree = doc.getElementById("permissionsTree");
Assert.equal(tree.view.rowCount, 0, "Row count should initially be 0");
let inputBox = doc.getElementById("url");
inputBox.focus();
EventUtils.sendString("www.example.com", exceptionsDialog);
let btnBlock = doc.getElementById("btnBlock");
btnBlock.click();
await waitForCondition(() => tree.view.rowCount == 1);
Assert.equal(tree.view.getCellText(0, tree.treeBoxObject.columns.getColumnAt(0)),
"http://www.example.com");
});
add_task(async function deleteALoginException() {
let doc = exceptionsDialog.document;
let tree = doc.getElementById("permissionsTree");
Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
tree.focus();
tree.view.selection.select(0);
if (AppConstants.platform == "macosx") {
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
} else {
EventUtils.synthesizeKey("VK_DELETE", {});
}
await waitForCondition(() => tree.view.rowCount == 0);
is_element_visible(content.gSubDialog._dialogs[0]._box,
"Subdialog is visible after deleting an element");
});

View File

@ -95,6 +95,7 @@ var gSyncPane = {
"weave:service:logout:finish",
FxAccountsCommon.ONVERIFIED_NOTIFICATION,
FxAccountsCommon.ONLOGIN_NOTIFICATION,
FxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
];
// Add the observers now and remove them on unload

View File

@ -41,6 +41,7 @@ skip-if = true || !healthreport # Bug 1185403 for the "true"
[browser_sanitizeOnShutdown_prefLocked.js]
[browser_searchsuggestions.js]
[browser_security.js]
[browser_site_login_exceptions.js]
[browser_subdialogs.js]
support-files =
subdialog.xul

View File

@ -6,8 +6,8 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/* import-globals-from ../../../../../testing/modules/sinon-1.16.1.js */
Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
/* global sinon */
Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
const TEST_QUOTA_USAGE_HOST = "example.com";
const TEST_QUOTA_USAGE_ORIGIN = "https://" + TEST_QUOTA_USAGE_HOST;
@ -182,8 +182,6 @@ function assertSitesListed(doc, hosts) {
registerCleanupFunction(function() {
delete window.sinon;
delete window.setImmediate;
delete window.clearImmediate;
mockOfflineAppCacheHelper.unregister();
});

View File

@ -0,0 +1,76 @@
"use strict";
const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
var exceptionsDialog;
add_task(async function openLoginExceptionsSubDialog() {
// Undo the save password change.
registerCleanupFunction(async function() {
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
let doc = content.document;
let savePasswordCheckBox = doc.getElementById("savePasswords");
if (savePasswordCheckBox.checked) {
savePasswordCheckBox.click();
}
});
gBrowser.removeCurrentTab();
});
await openPreferencesViaOpenPreferencesAPI("security", null, {leaveOpen: true});
let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
let doc = content.document;
let savePasswordCheckBox = doc.getElementById("savePasswords");
Assert.ok(!savePasswordCheckBox.checked,
"Save Password CheckBox should be unchecked by default");
savePasswordCheckBox.click();
let loginExceptionsButton = doc.getElementById("passwordExceptions");
loginExceptionsButton.click();
});
exceptionsDialog = await dialogOpened;
});
add_task(async function addALoginException() {
let doc = exceptionsDialog.document;
let tree = doc.getElementById("permissionsTree");
Assert.equal(tree.view.rowCount, 0, "Row count should initially be 0");
let inputBox = doc.getElementById("url");
inputBox.focus();
EventUtils.sendString("www.example.com", exceptionsDialog);
let btnBlock = doc.getElementById("btnBlock");
btnBlock.click();
await waitForCondition(() => tree.view.rowCount == 1);
Assert.equal(tree.view.getCellText(0, tree.treeBoxObject.columns.getColumnAt(0)),
"http://www.example.com");
});
add_task(async function deleteALoginException() {
let doc = exceptionsDialog.document;
let tree = doc.getElementById("permissionsTree");
Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
tree.focus();
tree.view.selection.select(0);
if (AppConstants.platform == "macosx") {
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
} else {
EventUtils.synthesizeKey("VK_DELETE", {});
}
await waitForCondition(() => tree.view.rowCount == 0);
is_element_visible(content.gSubDialog._dialogs[0]._box,
"Subdialog is visible after deleting an element");
});

View File

@ -9,11 +9,9 @@ Cu.import("resource://gre/modules/Promise.jsm");
// docs: http://sinonjs.org/docs/
/* global sinon */
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
loader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
registerCleanupFunction(function*() {
// Cleanup window or the test runner will throw an error
delete window.sinon;
delete window.setImmediate;
delete window.clearImmediate;
});

View File

@ -7,23 +7,24 @@ XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
});
Cu.import("resource://gre/modules/Timer.jsm");
do_get_profile(); // fxa needs a profile directory for storage.
// Create a window polyfill so sinon can load
let window = {
document: {},
location: {},
setTimeout,
setInterval,
clearTimeout,
clearInterval,
};
let self = window;
// ================================================
// Load mocking/stubbing library, sinon
// docs: http://sinonjs.org/docs/
/* global sinon */
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
// docs: http://sinonjs.org/releases/v2.3.2/
Cu.import("resource://gre/modules/Timer.jsm");
const {Loader} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
const loader = new Loader.Loader({
paths: {
"": "resource://testing-common/",
},
globals: {
setTimeout,
setInterval,
clearTimeout,
clearInterval,
},
});
const require = Loader.Require(loader, {id: ""});
const sinon = require("sinon-2.3.2");
// ================================================

View File

@ -12,6 +12,7 @@ let { DeckView } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckView.
add_task(async function testInitUninit() {
let deckStore = new SyncedTabsDeckStore();
let listComponent = {};
let mockWindow = {};
let ViewMock = sinon.stub();
let view = {render: sinon.spy(), destroy: sinon.spy(), container: {}};
@ -23,7 +24,7 @@ add_task(async function testInitUninit() {
sinon.stub(deckStore, "setPanels");
let component = new SyncedTabsDeckComponent({
window,
window: mockWindow,
deckStore,
listComponent,
SyncedTabs,
@ -38,7 +39,7 @@ add_task(async function testInitUninit() {
SyncedTabs.syncTabs.restore();
Assert.ok(ViewMock.calledWithNew(), "view is instantiated");
Assert.equal(ViewMock.args[0][0], window);
Assert.equal(ViewMock.args[0][0], mockWindow);
Assert.equal(ViewMock.args[0][1], listComponent);
Assert.ok(ViewMock.args[0][2].onAndroidClick,
"view is passed onAndroidClick prop");
@ -81,6 +82,7 @@ add_task(async function testObserver() {
let deckStore = new SyncedTabsDeckStore();
let listStore = new SyncedTabsListStore(SyncedTabs);
let listComponent = {};
let mockWindow = {};
let ViewMock = sinon.stub();
let view = {render: sinon.spy(), destroy: sinon.spy(), container: {}};
@ -94,7 +96,7 @@ add_task(async function testObserver() {
sinon.stub(listStore, "getData");
let component = new SyncedTabsDeckComponent({
window,
mockWindow,
deckStore,
listStore,
listComponent,

View File

@ -25,6 +25,7 @@ add_task(function* testInitUninit() {
let store = new SyncedTabsListStore();
let ViewMock = sinon.stub();
let view = {render() {}, destroy() {}};
let mockWindow = {};
ViewMock.returns(view);
@ -35,7 +36,7 @@ add_task(function* testInitUninit() {
sinon.stub(store, "getData");
sinon.stub(store, "focusInput");
let component = new TabListComponent({window, store, View: ViewMock, SyncedTabs});
let component = new TabListComponent({window: mockWindow, store, View: ViewMock, SyncedTabs});
for (let action of ACTION_METHODS) {
sinon.stub(component, action);

View File

@ -23,11 +23,25 @@ XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
do_get_profile();
// Setup the environment for sinon.
// ================================================
// Load mocking/stubbing library, sinon
// docs: http://sinonjs.org/releases/v2.3.2/
Cu.import("resource://gre/modules/Timer.jsm");
let self = {}; // eslint-disable-line no-unused-vars
var sinon;
Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
const {Loader} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
const loader = new Loader.Loader({
paths: {
"": "resource://testing-common/",
},
globals: {
setTimeout,
setInterval,
clearTimeout,
clearInterval,
},
});
const require = Loader.Require(loader, {id: ""});
const sinon = require("sinon-2.3.2");
// ================================================
// Load our bootstrap extension manifest so we can access our chrome/resource URIs.
const EXTENSION_ID = "formautofill@mozilla.org";

View File

@ -102,14 +102,20 @@ class Onboarding {
}
}
addEventListener("load", function(evt) {
addEventListener("load", function onLoad(evt) {
if (!content || evt.target != content.document) {
return;
}
removeEventListener("load", onLoad);
let window = evt.target.defaultView;
// Load onboarding module only when we enable it.
if ((content.location.href == ABOUT_NEWTAB_URL ||
content.location.href == ABOUT_HOME_URL) &&
if ((window.location.href == ABOUT_NEWTAB_URL ||
window.location.href == ABOUT_HOME_URL) &&
Services.prefs.getBoolPref("browser.onboarding.enabled", false)) {
content.requestIdleCallback(() => {
new Onboarding(content);
window.requestIdleCallback(() => {
new Onboarding(window);
});
}
}, true);

View File

@ -11,7 +11,7 @@ Cu.import("resource://shield-recipe-client/lib/Utils.jsm", this);
// docs: http://sinonjs.org/docs/
const loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
/* global sinon */
loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
loader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
// Make sinon assertions fail in a way that mochitest understands
sinon.assert.fail = function(message) {
@ -21,8 +21,6 @@ sinon.assert.fail = function(message) {
registerCleanupFunction(async function() {
// Cleanup window or the test runner will throw an error
delete window.sinon;
delete window.setImmediate;
delete window.clearImmediate;
});

View File

@ -20,13 +20,22 @@ if (!extensionDir.exists()) {
}
Components.manager.addBootstrappedManifestLocation(extensionDir);
// Load Sinon for mocking/stubbing during tests.
// Sinon assumes that setTimeout and friends are available, and looks for a
// global object named self during initialization.
// ================================================
// Load mocking/stubbing library, sinon
// docs: http://sinonjs.org/releases/v2.3.2/
Cu.import("resource://gre/modules/Timer.jsm");
const self = {}; // eslint-disable-line no-unused-vars
/* global sinon */
/* exported sinon */
const loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
const {Loader} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
const loader = new Loader.Loader({
paths: {
"": "resource://testing-common/",
},
globals: {
setTimeout,
setInterval,
clearTimeout,
clearInterval,
},
});
const require = Loader.Require(loader, {id: ""});
const sinon = require("sinon-2.3.2");
// ================================================

View File

@ -3,15 +3,10 @@
* 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/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
/**
* Constants
*/
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// Stop updating jumplists after some idle time.
const IDLE_TIMEOUT_SECONDS = 5 * 60;
@ -52,28 +47,18 @@ XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
.createBundle("chrome://browser/locale/taskbar.properties");
});
XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
return PlacesUtils;
});
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
Components.utils.import("resource://gre/modules/NetUtil.jsm");
return NetUtil;
});
XPCOMUtils.defineLazyServiceGetter(this, "_idle",
"@mozilla.org/widget/idleservice;1",
"nsIIdleService");
XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
"@mozilla.org/windows-taskbar;1",
"nsIWinTaskbar");
XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
"@mozilla.org/browser/shell-service;1",
"nsIWindowsShellService");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
@ -437,7 +422,7 @@ this.WinTaskbarJumpList =
}
},
handleError(aError) {
Components.utils.reportError(
Cu.reportError(
"Async execution error (" + aError.result + "): " + aError.message);
},
handleCompletion(aReason) {
@ -456,12 +441,12 @@ this.WinTaskbarJumpList =
if (oldItem) {
try { // in case we get a bad uri
let uriSpec = oldItem.app.getParameter(0);
URIsToRemove.push(NetUtil.newURI(uriSpec));
URIsToRemove.push(Services.io.newURI(uriSpec));
} catch (err) { }
}
}
if (URIsToRemove.length > 0) {
PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
PlacesUtils.history.remove(URIsToRemove).catch(Cu.reportError);
}
},

View File

@ -1055,10 +1055,6 @@ html|span.ac-emphasize-text-url {
list-style-image: url("chrome://browser/skin/tabbrowser/alltabs-inverted.png");
}
#alltabs-button > .toolbarbutton-menu-dropmarker {
display: none;
}
/* All tabs menupopup */
.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");

View File

@ -1786,10 +1786,6 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
#alltabs-button > .toolbarbutton-menu-dropmarker {
display: none;
}
/* All Tabs Menupopup */
.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");

View File

@ -526,6 +526,15 @@
width: calc(36px + var(--tab-curve-width));
}
.tabs-newtab-button > .toolbarbutton-menu-dropmarker {
display: none;
}
.tabs-newtab-button > .toolbarbutton-icon {
/* override drop marker image padding */
margin-inline-end: 0;
}
@media (min-resolution: 1.1dppx) {
/* image preloading hack from like lowdpi styles */
#tabbrowser-tabs::before {

View File

@ -61,19 +61,16 @@ toolbar[brighttext] {
#nav-bar-overflow-button[disabled=true] > .toolbarbutton-icon,
#PanelUI-menu-button[disabled=true] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-icon,
#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menu-dropmarker,
#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-dropmarker,
#main-window:not([customizing]) .toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled=true] > .toolbarbutton-icon,
#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
opacity: 0.4;
}
.toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
}
toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
}
@ -209,6 +206,8 @@ toolbarbutton.bookmark-item:not(.subviewbutton),
border-inline-end-style: none;
}
.bookmark-item > .toolbarbutton-menu-dropmarker,
#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
#nav-bar .toolbarbutton-1 > .toolbarbutton-menu-dropmarker {
display: none;
}
@ -464,6 +463,3 @@ toolbarbutton.bookmark-item:not(.subviewbutton) {
margin-inline-end: 5px;
}
.bookmark-item > .toolbarbutton-menu-dropmarker {
display: none;
}

View File

@ -1505,10 +1505,6 @@ treechildren.searchbar-treebody::-moz-tree-row(selected) {
list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
}
#alltabs-button > .toolbarbutton-menu-dropmarker {
display: none;
}
/* All tabs menupopup */
.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");

View File

@ -1,6 +1,6 @@
#filter substitution
python
import sys
sys.path.append('@topsrcdir@/python/gdbpp')
sys.path.append('@topsrcdir@/third_party/python/gdbpp')
import gdbpp
end

View File

@ -43,7 +43,6 @@ MACH_MODULES = [
'python/mach_commands.py',
'python/mach/mach/commands/commandinfo.py',
'python/mach/mach/commands/settings.py',
'python/compare-locales/mach_commands.py',
'python/mozboot/mozboot/mach_commands.py',
'python/mozbuild/mozbuild/mach_commands.py',
'python/mozbuild/mozbuild/backend/mach_commands.py',
@ -60,6 +59,7 @@ MACH_MODULES = [
'testing/talos/mach_commands.py',
'testing/web-platform/mach_commands.py',
'testing/xpcshell/mach_commands.py',
'tools/compare-locales/mach_commands.py',
'tools/docs/mach_commands.py',
'tools/lint/mach_commands.py',
'tools/mach_commands.py',

View File

@ -193,7 +193,7 @@ def virtualenv_python(env_python, build_env, mozconfig, help):
# If we're not in the virtualenv, we need the which module for
# find_program.
if normsep(sys.executable) != normsep(manager.python_path):
sys.path.append(os.path.join(topsrcdir, 'python', 'which'))
sys.path.append(os.path.join(topsrcdir, 'third_party', 'python', 'which'))
found_python = find_program(python)
if not found_python:
die('The PYTHON environment variable does not contain '

View File

@ -9,7 +9,7 @@ import sys
import time
HERE = os.path.abspath(os.path.dirname(__file__))
sys.path.append(os.path.join(HERE, '..', 'python', 'requests'))
sys.path.append(os.path.join(HERE, '..', 'third_party', 'python', 'requests'))
import requests

View File

@ -3,25 +3,25 @@ mozilla.pth:python/mozboot
mozilla.pth:python/mozbuild
mozilla.pth:python/mozlint
mozilla.pth:python/mozversioncontrol
mozilla.pth:python/blessings
mozilla.pth:python/compare-locales
mozilla.pth:python/configobj
mozilla.pth:python/dlmanager
mozilla.pth:python/futures
mozilla.pth:python/jsmin
optional:setup.py:python/psutil:build_ext:--inplace
mozilla.pth:python/psutil
mozilla.pth:python/pylru
mozilla.pth:python/which
mozilla.pth:python/pystache
mozilla.pth:python/pyyaml/lib
mozilla.pth:python/requests
mozilla.pth:python/slugid
mozilla.pth:python/py
mozilla.pth:python/pytest
mozilla.pth:python/pytoml
mozilla.pth:python/redo
mozilla.pth:python/voluptuous
mozilla.pth:third_party/python/blessings
mozilla.pth:third_party/python/compare-locales
mozilla.pth:third_party/python/configobj
mozilla.pth:third_party/python/dlmanager
mozilla.pth:third_party/python/futures
mozilla.pth:third_party/python/jsmin
optional:setup.py:third_party/python/psutil:build_ext:--inplace
mozilla.pth:third_party/python/psutil
mozilla.pth:third_party/python/pylru
mozilla.pth:third_party/python/which
mozilla.pth:third_party/python/pystache
mozilla.pth:third_party/python/pyyaml/lib
mozilla.pth:third_party/python/requests
mozilla.pth:third_party/python/slugid
mozilla.pth:third_party/python/py
mozilla.pth:third_party/python/pytest
mozilla.pth:third_party/python/pytoml
mozilla.pth:third_party/python/redo
mozilla.pth:third_party/python/voluptuous
mozilla.pth:build
objdir:build
mozilla.pth:build/pymake
@ -46,11 +46,11 @@ mozilla.pth:testing/web-platform/tests/tools/wptrunner
mozilla.pth:testing/web-platform/tests/tools/wptserve
mozilla.pth:testing/web-platform/tests/tools/six
mozilla.pth:testing/xpcshell
mozilla.pth:python/mock-1.0.0
mozilla.pth:third_party/python/mock-1.0.0
mozilla.pth:xpcom/typelib/xpt/tools
mozilla.pth:tools/docs
mozilla.pth:media/webrtc/trunk/tools/gyp/pylib
mozilla.pth:python/pyasn1
mozilla.pth:python/pyasn1-modules
mozilla.pth:python/rsa
mozilla.pth:python/PyECC
mozilla.pth:third_party/python/pyasn1
mozilla.pth:third_party/python/pyasn1-modules
mozilla.pth:third_party/python/rsa
mozilla.pth:third_party/python/PyECC

View File

@ -47,3 +47,14 @@ if CONFIG['CLANG_CL']:
'-Wno-macro-redefined',
'-Wno-microsoft-include',
]
if CONFIG['_MSC_VER'] and not CONFIG['CLANG_CL']:
CFLAGS += [
'-wd4005', # 'WIN32_LEAN_AND_MEAN' : macro redefinition
'-wd4996', # The compiler encountered a deprecated declaration.
]
CXXFLAGS += [
'-wd4005', # 'WIN32_LEAN_AND_MEAN' : macro redefinition
'-wd4333', # '>>' : right shift by too large amount, data loss
'-wd4996', # The compiler encountered a deprecated declaration.
]

View File

@ -7,7 +7,7 @@
ifndef NO_DIST_INSTALL
ifneq (,$(strip $(PROGRAM)$(SIMPLE_PROGRAMS)))
ifneq (,$(strip $(PROGRAM)$(SIMPLE_PROGRAMS)$(RUST_PROGRAMS)))
PROGRAMS_EXECUTABLES = $(SIMPLE_PROGRAMS) $(PROGRAM) $(RUST_PROGRAMS)
PROGRAMS_DEST ?= $(FINAL_TARGET)
PROGRAMS_TARGET := target

47
devtools/bootstrap.js vendored
View File

@ -11,6 +11,7 @@ const Cu = Components.utils;
const Ci = Components.interfaces;
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
// MultiWindowKeyListener instance for Ctrl+Alt+R key
let listener;
@ -40,10 +41,56 @@ function readURI(uri) {
return data;
}
/**
* Interpret the processing instructions contained in a preferences file, based on a
* limited set of supported #if statements. After we ship as an addon, we don't want to
* introduce anymore processing instructions, so all unrecognized preprocessing
* instructions will be treated as an error.
*
* This function is mostly copied from devtools/client/inspector/webpack/prefs-loader.js
*
* @param {String} content
* The string content of a preferences file.
* @return {String} the content stripped of preprocessing instructions.
*/
function interpretPreprocessingInstructions(content) {
const ifMap = {
"#if MOZ_UPDATE_CHANNEL == beta": AppConstants.MOZ_UPDATE_CHANNEL === "beta",
"#if defined(NIGHTLY_BUILD)": AppConstants.NIGHTLY_BUILD,
"#ifdef MOZ_DEV_EDITION": AppConstants.MOZ_DEV_EDITION,
"#ifdef RELEASE_OR_BETA": AppConstants.RELEASE_OR_BETA,
};
let lines = content.split("\n");
let ignoring = false;
let newLines = [];
let continuation = false;
for (let line of lines) {
if (line.startsWith("#if")) {
if (!(line in ifMap)) {
throw new Error("missing line in ifMap: " + line);
}
ignoring = !ifMap[line];
} else if (line.startsWith("#else")) {
ignoring = !ignoring;
}
let isPrefLine = /^ *pref\("([^"]+)"/.test(line);
if (continuation || (!ignoring && isPrefLine)) {
newLines.push(line);
// The call to pref(...); might span more than one line.
continuation = !/\);/.test(line);
}
}
return newLines.join("\n");
}
// Read a preference file and set all of its defined pref as default values
// (This replicates the behavior of preferences files from mozilla-central)
function processPrefFile(url) {
let content = readURI(url);
content = interpretPreprocessingInstructions(content);
content.match(/pref\("[^"]+",\s*.+\s*\)/g).forEach(item => {
let m = item.match(/pref\("([^"]+)",\s*(.+)\s*\)/);
let name = m[1];

View File

@ -516,9 +516,15 @@ function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded = true
}
const nextPathSegment = pathSegments[i + 1];
path += pathSegment.easing.startsWith("steps")
? createStepsPathString(pathSegment, nextPathSegment)
: createCubicBezierPathString(pathSegment, nextPathSegment);
let createPathFunction;
if (pathSegment.easing.startsWith("steps")) {
createPathFunction = createStepsPathString;
} else if (pathSegment.easing.startsWith("frames")) {
createPathFunction = createFramesPathString;
} else {
createPathFunction = createCubicBezierPathString;
}
path += createPathFunction(pathSegment, nextPathSegment);
}
path += ` L${ pathSegments[pathSegments.length - 1].x },0`;
if (isClosePathNeeded) {
@ -593,6 +599,28 @@ function createStepsPathString(currentSegment, nextSegment) {
return path;
}
/**
* Create a path string to represents a frames function.
* @param {Object} currentSegment - e.g. { x: 0, y: 0, easing: "frames(2)" }
* @param {Object} nextSegment - e.g. { x: 1, y: 1 }
* @return {String} path string - e.g. "C 0.25 0.1, 0.25 1, 1 1"
*/
function createFramesPathString(currentSegment, nextSegment) {
const matches =
currentSegment.easing.match(/^frames\((\d+)\)/);
const framesNumber = parseInt(matches[1], 10);
const oneFrameX = (nextSegment.x - currentSegment.x) / framesNumber;
const oneFrameY = (nextSegment.y - currentSegment.y) / (framesNumber - 1);
let path = "";
for (let frame = 0; frame < framesNumber; frame++) {
const sx = currentSegment.x + frame * oneFrameX;
const ex = sx + oneFrameX;
const y = currentSegment.y + frame * oneFrameY;
path += ` L${ sx },${ y } L${ ex },${ y }`;
}
return path;
}
/**
* Create a path string to represents a bezier curve.
* @param {Object} currentSegment - e.g. { x: 0, y: 0, easing: "ease" }
@ -684,9 +712,9 @@ exports.getPreferredKeyframesProgressThreshold = getPreferredKeyframesProgressTh
* @return {float} - preferred threshold.
*/
function getPreferredProgressThreshold(easing) {
const stepFunction = easing.match(/steps\((\d+)/);
return stepFunction
? 1 / (parseInt(stepFunction[1], 10) + 1)
const stepOrFramesFunction = easing.match(/(steps|frames)\((\d+)/);
return stepOrFramesFunction
? 1 / (parseInt(stepOrFramesFunction[2], 10) + 1)
: DEFAULT_MIN_PROGRESS_THRESHOLD;
}
exports.getPreferredProgressThreshold = getPreferredProgressThreshold;

View File

@ -239,7 +239,26 @@ const TEST_CASES = [
expectedClass: "opacity",
expectedValues: [
{ x: 0, y: 0 },
{ x: 250, y: 0.25 },
{ x: 500, y: 0.5 },
{ x: 750, y: 0.75 },
{ x: 1000, y: 1 },
]
}
},
{
"opacity": {
expectedClass: "opacity",
expectedValues: [
{ x: 0, y: 0 },
{ x: 199, y: 0 },
{ x: 200, y: 0.25 },
{ x: 399, y: 0.25 },
{ x: 400, y: 0.5 },
{ x: 599, y: 0.5 },
{ x: 600, y: 0.75 },
{ x: 799, y: 0.75 },
{ x: 800, y: 1 },
{ x: 1000, y: 1 },
]
}

View File

@ -30,18 +30,29 @@ const TEST_CASES = {
"effect-easing": {
expectedEffectEasingGraph: [
{ x: 0, y: 0 },
{ x: 49999, y: 0.0 },
{ x: 50000, y: 0.5 },
{ x: 99999, y: 0.5 },
{ x: 19999, y: 0.0 },
{ x: 20000, y: 0.25 },
{ x: 39999, y: 0.25 },
{ x: 40000, y: 0.5 },
{ x: 59999, y: 0.5 },
{ x: 60000, y: 0.75 },
{ x: 79999, y: 0.75 },
{ x: 80000, y: 1 },
{ x: 99999, y: 1 },
{ x: 100000, y: 0 },
],
expectedKeyframeEasingGraphs: [
[
{ x: 0, y: 0 },
{ x: 49999, y: 0.0 },
{ x: 50000, y: 0.5 },
{ x: 99999, y: 0.5 },
{ x: 100000, y: 1 },
{ x: 19999, y: 0.0 },
{ x: 20000, y: 0.25 },
{ x: 39999, y: 0.25 },
{ x: 40000, y: 0.5 },
{ x: 59999, y: 0.5 },
{ x: 60000, y: 0.75 },
{ x: 79999, y: 0.75 },
{ x: 80000, y: 1 },
{ x: 99999, y: 1 },
{ x: 100000, y: 0 },
]
]

View File

@ -25,7 +25,7 @@
id: "effect-easing",
frames: { opacity: [1, 0] },
timing: {
easing: "steps(2)",
easing: "frames(5)",
duration: DURATION
}
},

View File

@ -15,6 +15,7 @@
<div id=target3>3</div>
<div id=target4>4</div>
<div id=target5>5</div>
<div id=target6>6</div>
<script>
"use strict";
@ -90,6 +91,10 @@
timing.easing = "steps(2)";
document.querySelector("#target5").animate(
[{ opacity: 0 }, { opacity: 1 }], timing).pause();
timing.easing = "linear";
document.querySelector("#target6").animate(
[{ opacity: 0, easing: "frames(5)" }, { opacity: 1 }], timing).pause();
</script>
</body>
</html>

View File

@ -8,6 +8,14 @@
const TEST_URL = "data:text/html,<html><head><title>Test for the " +
"highlighter keybindings</title></head><body>" +
"<h1>Keybindings!</h1></body></html>"
// Use the new debugger frontend because the old one swallows the netmonitor shortcut:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1370442#c7
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
registerCleanupFunction(function* () {
Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
});
function test()
{
waitForExplicitFinish();

View File

@ -6,7 +6,7 @@
const global = require("devtools/client/performance/modules/global");
const demangle = require("devtools/client/shared/demangle");
const { assert } = require("devtools/shared/DevToolsUtils");
const { isChromeScheme, isContentScheme, parseURL } =
const { isChromeScheme, isContentScheme, isWASM, parseURL } =
require("devtools/client/shared/source-utils");
const { CATEGORY_MASK, CATEGORY_MAPPINGS } = require("devtools/client/performance/modules/categories");
@ -221,7 +221,9 @@ function computeIsContentAndCategory(frame) {
schemeStartIndex = 0;
}
if (isContentScheme(location, schemeStartIndex)) {
// We can't know if WASM frames are content or not at the time of this writing, so label
// them all as content.
if (isContentScheme(location, schemeStartIndex) || isWASM(location)) {
frame.isContent = true;
return;
}

View File

@ -10,6 +10,7 @@ const UNKNOWN_SOURCE_STRING = l10n.getStr("frame.unknownSource");
// Character codes used in various parsing helper functions.
const CHAR_CODE_A = "a".charCodeAt(0);
const CHAR_CODE_B = "b".charCodeAt(0);
const CHAR_CODE_C = "c".charCodeAt(0);
const CHAR_CODE_D = "d".charCodeAt(0);
const CHAR_CODE_E = "e".charCodeAt(0);
@ -19,13 +20,17 @@ const CHAR_CODE_I = "i".charCodeAt(0);
const CHAR_CODE_J = "j".charCodeAt(0);
const CHAR_CODE_L = "l".charCodeAt(0);
const CHAR_CODE_M = "m".charCodeAt(0);
const CHAR_CODE_N = "n".charCodeAt(0);
const CHAR_CODE_O = "o".charCodeAt(0);
const CHAR_CODE_P = "p".charCodeAt(0);
const CHAR_CODE_R = "r".charCodeAt(0);
const CHAR_CODE_S = "s".charCodeAt(0);
const CHAR_CODE_T = "t".charCodeAt(0);
const CHAR_CODE_U = "u".charCodeAt(0);
const CHAR_CODE_W = "w".charCodeAt(0);
const CHAR_CODE_COLON = ":".charCodeAt(0);
const CHAR_CODE_DASH = "-".charCodeAt(0);
const CHAR_CODE_L_SQUARE_BRACKET = "[".charCodeAt(0);
const CHAR_CODE_SLASH = "/".charCodeAt(0);
const CHAR_CODE_CAP_S = "S".charCodeAt(0);
@ -248,6 +253,18 @@ function isContentScheme(location, i = 0) {
}
return false;
// "blob:"
case CHAR_CODE_B:
if (
location.charCodeAt(++i) == CHAR_CODE_L &&
location.charCodeAt(++i) == CHAR_CODE_O &&
location.charCodeAt(++i) == CHAR_CODE_B &&
location.charCodeAt(++i) == CHAR_CODE_COLON
) {
return isContentScheme(location, i + 1);
}
return false;
default:
return false;
}
@ -299,6 +316,26 @@ function isChromeScheme(location, i = 0) {
}
}
function isWASM(location, i = 0) {
return (
// "wasm-function["
location.charCodeAt(i) === CHAR_CODE_W &&
location.charCodeAt(++i) === CHAR_CODE_A &&
location.charCodeAt(++i) === CHAR_CODE_S &&
location.charCodeAt(++i) === CHAR_CODE_M &&
location.charCodeAt(++i) === CHAR_CODE_DASH &&
location.charCodeAt(++i) === CHAR_CODE_F &&
location.charCodeAt(++i) === CHAR_CODE_U &&
location.charCodeAt(++i) === CHAR_CODE_N &&
location.charCodeAt(++i) === CHAR_CODE_C &&
location.charCodeAt(++i) === CHAR_CODE_T &&
location.charCodeAt(++i) === CHAR_CODE_I &&
location.charCodeAt(++i) === CHAR_CODE_O &&
location.charCodeAt(++i) === CHAR_CODE_N &&
location.charCodeAt(++i) === CHAR_CODE_L_SQUARE_BRACKET
);
}
/**
* A utility method to get the file name from a sourcemapped location
* The sourcemap location can be in any form. This method returns a
@ -324,5 +361,6 @@ exports.getSourceNames = getSourceNames;
exports.isScratchpadScheme = isScratchpadScheme;
exports.isChromeScheme = isChromeScheme;
exports.isContentScheme = isContentScheme;
exports.isWASM = isWASM;
exports.isDataScheme = isDataScheme;
exports.getSourceMappedFile = getSourceMappedFile;

View File

@ -19,7 +19,8 @@ const CHROME_URLS = [
];
const CONTENT_URLS = [
"http://mozilla.org", "https://mozilla.org", "file:///Users/root", "app://fxosapp"
"http://mozilla.org", "https://mozilla.org", "file:///Users/root", "app://fxosapp",
"blob:http://mozilla.org", "blob:https://mozilla.org"
];
// Test `sourceUtils.parseURL`
@ -61,6 +62,13 @@ add_task(function* () {
}
});
// Test `sourceUtils.isWASM`.
add_task(function* () {
ok(sourceUtils.isWASM("wasm-function[66240] (?:13870536)"),
"wasm function correctly identified");
ok(!sourceUtils.isWASM(CHROME_URLS[0]), `A chrome url does not identify as wasm.`);
});
// Test `sourceUtils.isDataScheme`.
add_task(function* () {
let dataURI = "data:text/html;charset=utf-8,<!DOCTYPE html></html>";

View File

@ -14,6 +14,7 @@ const { connect } = require("devtools/client/shared/vendor/react-redux");
const {
getAllMessagesUiById,
getAllMessagesTableDataById,
getAllNetworkMessagesUpdateById,
getVisibleMessages,
getAllRepeatById,
} = require("devtools/client/webconsole/new-console-output/selectors/messages");
@ -34,6 +35,7 @@ const ConsoleOutput = createClass({
timestampsVisible: PropTypes.bool,
messagesTableData: PropTypes.object.isRequired,
messagesRepeat: PropTypes.object.isRequired,
networkMessagesUpdate: PropTypes.object.isRequired,
visibleMessages: PropTypes.array.isRequired,
},
@ -78,6 +80,7 @@ const ConsoleOutput = createClass({
messagesUi,
messagesTableData,
messagesRepeat,
networkMessagesUpdate,
serviceContainer,
timestampsVisible,
} = this.props;
@ -93,7 +96,8 @@ const ConsoleOutput = createClass({
tableData: messagesTableData.get(message.id),
indent: message.indent,
timestampsVisible,
repeat: messagesRepeat[message.id]
repeat: messagesRepeat[message.id],
networkMessageUpdate: networkMessagesUpdate[message.id],
})
);
});
@ -128,6 +132,7 @@ function mapStateToProps(state, props) {
messagesUi: getAllMessagesUiById(state),
messagesTableData: getAllMessagesTableDataById(state),
messagesRepeat: getAllRepeatById(state),
networkMessagesUpdate: getAllNetworkMessagesUpdateById(state),
timestampsVisible: state.ui.timestampsVisible,
};
}

View File

@ -38,6 +38,7 @@ const MessageContainer = createClass({
tableData: PropTypes.object,
timestampsVisible: PropTypes.bool.isRequired,
repeat: PropTypes.object,
networkMessageUpdate: PropTypes.object.isRequired,
},
getDefaultProps: function () {
@ -55,13 +56,16 @@ const MessageContainer = createClass({
const totalTimeChanged = this.props.message.totalTime !== nextProps.message.totalTime;
const timestampVisibleChanged =
this.props.timestampsVisible !== nextProps.timestampsVisible;
const networkMessageUpdateChanged =
this.props.networkMessageUpdate !== nextProps.networkMessageUpdate;
return repeatChanged
|| openChanged
|| tableDataChanged
|| responseChanged
|| totalTimeChanged
|| timestampVisibleChanged;
|| timestampVisibleChanged
|| networkMessageUpdateChanged;
},
render() {

View File

@ -24,6 +24,7 @@ NetworkEventMessage.propTypes = {
}),
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
networkMessageUpdate: PropTypes.object.isRequired,
};
NetworkEventMessage.defaultProps = {
@ -35,6 +36,7 @@ function NetworkEventMessage({
message = {},
serviceContainer,
timestampsVisible,
networkMessageUpdate = {},
}) {
const {
actor,
@ -42,20 +44,25 @@ function NetworkEventMessage({
type,
level,
request,
response: {
httpVersion,
status,
statusText,
},
isXHR,
timeStamp,
totalTime,
} = message;
const {
response = {},
totalTime,
} = networkMessageUpdate;
const {
httpVersion,
status,
statusText,
} = response;
const topLevelClasses = [ "cm-s-mozilla" ];
let statusInfo;
if (httpVersion && status && statusText && totalTime !== undefined) {
if (httpVersion && status && statusText !== undefined && totalTime !== undefined) {
statusInfo = `[${httpVersion} ${status} ${statusText} ${totalTime}ms]`;
}

View File

@ -184,11 +184,10 @@ NewConsoleOutputWrapper.prototype = {
},
dispatchMessageUpdate: function (message, res) {
batchedMessageAdd(actions.networkMessageUpdate(message));
// network-message-updated will emit when eventTimings message arrives
// which is the last one of 8 updates happening on network message update.
if (res.packet.updateType === "eventTimings") {
batchedMessageAdd(actions.networkMessageUpdate(message));
this.jsterm.hud.emit("network-message-updated", res);
}
},

View File

@ -36,7 +36,10 @@ const MessageState = Immutable.Record({
// This array is not supposed to be consumed by any UI component.
removedMessages: [],
// Map of the form {messageId : numberOfRepeat}
repeatById: {}
repeatById: {},
// Map of the form {messageId : networkInformation}
// `networkInformation` holds request, response, totalTime, ...
networkMessagesUpdateById: {},
});
function messages(state = new MessageState(), action, filtersState, prefsState) {
@ -44,6 +47,7 @@ function messages(state = new MessageState(), action, filtersState, prefsState)
messagesById,
messagesUiById,
messagesTableDataById,
networkMessagesUpdateById,
groupsById,
currentGroup,
repeatById,
@ -189,11 +193,12 @@ function messages(state = new MessageState(), action, filtersState, prefsState)
const {id, data} = action;
return state.set("messagesTableDataById", messagesTableDataById.set(id, data));
case constants.NETWORK_MESSAGE_UPDATE:
let updateMessage = action.message;
return state.set("messagesById", messagesById.set(
updateMessage.id,
updateMessage
));
return state.set(
"networkMessagesUpdateById",
Object.assign({}, networkMessagesUpdateById, {
[action.message.id]: action.message
})
);
case constants.REMOVED_MESSAGES_CLEAR:
return state.set("removedMessages", []);
@ -312,6 +317,13 @@ function limitTopLevelMessageCount(state, record, logLimit) {
const isInRemovedId = id => removedMessagesId.includes(id);
const mapHasRemovedIdKey = map => map.findKey((value, id) => isInRemovedId(id));
const cleanUpCollection = map => removedMessagesId.forEach(id => map.remove(id));
const cleanUpObject = object => [...Object.entries(object)]
.reduce((res, [id, value]) => {
if (!isInRemovedId(id)) {
res[id] = value;
}
return res;
}, {});
record.set("messagesById", record.messagesById.withMutations(cleanUpCollection));
@ -327,14 +339,12 @@ function limitTopLevelMessageCount(state, record, logLimit) {
}
if (Object.keys(record.repeatById).includes(removedMessagesId)) {
record.set("repeatById",
[...Object.entries(record.repeatById)].reduce((res, [id, repeat]) => {
if (!isInRemovedId(id)) {
res[id] = repeat;
}
return res;
}, {})
);
record.set("repeatById", cleanUpObject(record.repeatById));
}
if (Object.keys(record.networkMessagesUpdateById).includes(removedMessagesId)) {
record.set("networkMessagesUpdateById",
cleanUpObject(record.networkMessagesUpdateById));
}
return record;

View File

@ -37,6 +37,10 @@ function getAllRepeatById(state) {
return state.messages.repeatById;
}
function getAllNetworkMessagesUpdateById(state) {
return state.messages.networkMessagesUpdateById;
}
module.exports = {
getMessage,
getAllMessagesById,
@ -46,4 +50,5 @@ module.exports = {
getCurrentGroup,
getVisibleMessages,
getAllRepeatById,
getAllNetworkMessagesUpdateById,
};

View File

@ -14,8 +14,22 @@
<script type="text/javascript">
"use strict";
const numMessages = 4000;
const testPackets = Array.from({length: numMessages}).map((el, id) => ({
// To analyze the profile results:
// > ./mach mochitest test_render_perf.html
// Then open https://perf-html.io and drag the json file printed at the end of this test
const NUM_MESSAGES = 4000;
const NUM_STREAMING = 100;
Components.utils.import("resource://gre/modules/FileUtils.jsm");
const Services = browserRequire("Services");
Services.prefs.setIntPref("devtools.hud.loglimit", NUM_MESSAGES);
const NewConsoleOutputWrapper = browserRequire(
"devtools/client/webconsole/new-console-output/new-console-output-wrapper");
const actions =
browserRequire("devtools/client/webconsole/new-console-output/actions/index");
const EventEmitter = browserRequire("devtools/shared/event-emitter");
const testPackets = Array.from({length: NUM_MESSAGES}).map((el, id) => ({
"from": "server1.conn4.child1/consoleActor2",
"type": "consoleAPICall",
"message": {
@ -39,6 +53,7 @@ const testPackets = Array.from({length: numMessages}).map((el, id) => ({
"category": "webdev"
}
}));
const lastPacket = testPackets.pop();
async function timeit(cb) {
// Return a Promise that resolves the number of seconds cb takes.
@ -48,59 +63,126 @@ async function timeit(cb) {
return elapsed;
}
async function addAllMessages(wrapper) {
let time = await timeit(async () => {
testPackets.forEach((packet) => wrapper.dispatchMessageAdd(packet));
// Only wait for the last packet to minimize work.
await wrapper.dispatchMessageAdd(lastPacket, true);
await new Promise(resolve => requestAnimationFrame(resolve));
});
return time;
}
async function addMessage(wrapper, message) {
return timeit(async () => {
await wrapper.dispatchMessageAdd(message, true);
});
}
function getTimes(times) {
times = times.sort();
let totalTime = times.reduce((sum, t) => sum + t);
let avg = totalTime / times.length;
let median = times.length % 2 !== 0
? times[Math.floor(times.length / 2)]
: (times[(times.length / 2) - 1] + times[times.length / 2]) / 2;
return {avg, median};
}
async function clearMessages(wrapper) {
wrapper.dispatchMessagesClear();
await new Promise(resolve => requestAnimationFrame(resolve));
}
async function testStreamLogging(wrapper) {
await clearMessages(wrapper);
let streamTimes = [];
for (let i = 0; i < NUM_STREAMING; i++) {
streamTimes.push(addMessage(wrapper, testPackets[i]));
await new Promise(resolve => setTimeout(resolve, 100));
}
let {avg, median} = getTimes(await Promise.all(streamTimes));
info(`STREAMING: On average, it took ${avg} ms (median ${median} ms) ` +
`for each message`);
}
async function testBulkLogging(wrapper) {
await clearMessages(wrapper);
let bulkTimes = [];
const iterations = 5;
for (let i = 0; i < iterations; i++) {
let time = await addAllMessages(wrapper);
info(`took ${time} ms to render bulk messages (iteration ${i})`);
bulkTimes.push(time);
await clearMessages(wrapper);
}
let {avg, median} = getTimes(bulkTimes);
info(`BULK: On average, it took ${avg} ms (median ${median} ms) ` +
`to render ${NUM_MESSAGES} messages`);
}
async function testFiltering(wrapper) {
await clearMessages(wrapper);
await addAllMessages(wrapper);
let filterToggleTimeOff = await timeit(() => {
wrapper.getStore().dispatch(actions.filterToggle("log"));
});
info(`Filter toggle time (off): ${filterToggleTimeOff}`);
let filterToggleTimeOn = await timeit(() => {
wrapper.getStore().dispatch(actions.filterToggle("log"));
});
info(`Filter toggle time (on): ${filterToggleTimeOn}`);
}
window.onload = async function () {
// This test does costly work multiple times to have better performance data.
// It doesn't run in automation
SimpleTest.requestLongerTimeout(3);
try {
const Services = browserRequire("Services");
Services.prefs.setIntPref("devtools.hud.loglimit", numMessages);
const NewConsoleOutputWrapper = browserRequire(
"devtools/client/webconsole/new-console-output/new-console-output-wrapper");
const EventEmitter = browserRequire("devtools/shared/event-emitter");
const wrapper = new NewConsoleOutputWrapper(
document.getElementById("output"),
{hud: EventEmitter.decorate({proxy: {}})},
{},
null,
document,
);
wrapper.init();
const wrapper = new NewConsoleOutputWrapper(
document.getElementById("output"),
{hud: EventEmitter.decorate({proxy: {}})},
{},
null,
document,
);
wrapper.init();
// From https://github.com/devtools-html/perf.html/blob/b73eb73df04c7df51464fa50eeadef3dc7f5d4e2/docs/gecko-profile-format.md#L21
const settings = {
entries: 100000000,
interval: 1,
features: ["js"],
threads: ["GeckoMain"]
};
Services.profiler.StartProfiler(
settings.entries,
settings.interval,
settings.features,
settings.features.length,
settings.threads,
settings.threads.length
);
info("Profiler has started");
let times = [];
const iterations = 25;
const lastPacket = testPackets.pop();
for (let i = 0; i < iterations; i++) {
let time = await timeit(async () => {
testPackets.forEach((packet) => wrapper.dispatchMessageAdd(packet));
// Only wait for the last packet to minimize work.
await wrapper.dispatchMessageAdd(lastPacket, true);
await new Promise(resolve => requestAnimationFrame(resolve));
});
info(`took ${time} ms to render messages`);
times.push(time);
Services.profiler.AddMarker("Stream Logging");
await testStreamLogging(wrapper);
// Clear the console
wrapper.dispatchMessagesClear();
await new Promise(resolve => requestAnimationFrame(resolve));
}
Services.profiler.AddMarker("Bulk Logging");
await testBulkLogging(wrapper);
times.sort();
let totalTime = times.reduce((sum, t) => sum + t);
let avg = totalTime / times.length;
let median = times.length % 2 !== 0
? times[Math.floor(times.length / 2)]
: (times[(times.length / 2) - 1] + times[times.length / 2]) / 2;
info(`On average, it took ${avg} ms (median ${median} ms) ` +
`to render ${numMessages} messages`);
Services.profiler.AddMarker("Filtering Logging");
await testFiltering(wrapper);
ok(true, "Yay, it didn't time out!");
} catch (e) {
ok(false, `Error : ${e.message}
${e.stack}
`);
}
ok(true, "Tests finished");
let file = FileUtils.getFile("TmpD", [`test_render_perf_${Date.now()}.json`]);
info("PROFILE:\n\n\n\n\nSaving profile " + file.path + "\n\n\n\n\n");
Services.profiler.dumpProfileToFile(file.path);
Services.profiler.StopProfiler();
SimpleTest.finish();
};

View File

@ -23,11 +23,13 @@ const EXPECTED_STATUS = /\[HTTP\/\d\.\d \d+ [A-Za-z ]+ \d+ms\]/;
describe("NetworkEventMessage component:", () => {
describe("GET request", () => {
it("renders as expected", () => {
const message = stubPreparedMessages.get("GET request eventTimings");
const message = stubPreparedMessages.get("GET request");
const update = stubPreparedMessages.get("GET request eventTimings");
const wrapper = render(NetworkEventMessage({
message,
serviceContainer,
timestampsVisible: true,
networkMessageUpdate: update,
}));
const { timestampString } = require("devtools/client/webconsole/webconsole-l10n");
@ -66,8 +68,13 @@ describe("NetworkEventMessage component:", () => {
describe("XHR GET request", () => {
it("renders as expected", () => {
const message = stubPreparedMessages.get("XHR GET request eventTimings");
const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
const message = stubPreparedMessages.get("XHR GET request");
const update = stubPreparedMessages.get("XHR GET request eventTimings");
const wrapper = render(NetworkEventMessage({
message,
serviceContainer,
networkMessageUpdate: update,
}));
expect(wrapper.find(".message-body .method").text()).toBe("GET");
expect(wrapper.find(".message-body .xhr").length).toBe(1);
@ -79,8 +86,13 @@ describe("NetworkEventMessage component:", () => {
describe("XHR POST request", () => {
it("renders as expected", () => {
const message = stubPreparedMessages.get("XHR POST request eventTimings");
const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
const message = stubPreparedMessages.get("XHR POST request");
const update = stubPreparedMessages.get("XHR POST request eventTimings");
const wrapper = render(NetworkEventMessage({
message,
serviceContainer,
networkMessageUpdate: update,
}));
expect(wrapper.find(".message-body .method").text()).toBe("POST");
expect(wrapper.find(".message-body .xhr").length).toBe(1);

View File

@ -7,6 +7,7 @@ const {
getAllMessagesById,
getAllMessagesTableDataById,
getAllMessagesUiById,
getAllNetworkMessagesUpdateById,
getAllRepeatById,
getCurrentGroup,
getVisibleMessages,
@ -477,4 +478,45 @@ describe("Message reducer:", () => {
expect(groupsById.size).toBe(0);
});
});
describe("networkMessagesUpdateById", () => {
it("adds the network update message when network update action is called", () => {
const { dispatch, getState } = setupStore([
"GET request",
"XHR GET request"
]);
let networkUpdates = getAllNetworkMessagesUpdateById(getState());
expect(Object.keys(networkUpdates).length).toBe(0);
let updatePacket = stubPackets.get("GET request eventTimings");
dispatch(actions.networkMessageUpdate(updatePacket));
networkUpdates = getAllNetworkMessagesUpdateById(getState());
expect(Object.keys(networkUpdates).length).toBe(1);
let xhrUpdatePacket = stubPackets.get("XHR GET request eventTimings");
dispatch(actions.networkMessageUpdate(xhrUpdatePacket));
networkUpdates = getAllNetworkMessagesUpdateById(getState());
expect(Object.keys(networkUpdates).length).toBe(2);
});
it("resets networkMessagesUpdateById in response to MESSAGES_CLEAR action", () => {
const { dispatch, getState } = setupStore([
"XHR GET request"
]);
const updatePacket = stubPackets.get("XHR GET request eventTimings");
dispatch(actions.networkMessageUpdate(updatePacket));
let networkUpdates = getAllNetworkMessagesUpdateById(getState());
expect(Object.keys(networkUpdates).length).toBe(1);
dispatch(actions.messagesClear());
networkUpdates = getAllNetworkMessagesUpdateById(getState());
expect(Object.keys(networkUpdates).length).toBe(0);
});
});
});

View File

@ -1382,6 +1382,20 @@ var DebuggerServer = {
}
},
/**
* Called when DevTools are unloaded to remove the contend process server script for the
* list of scripts loaded for each new content process. Will also remove message
* listeners from already loaded scripts.
*/
removeContentServerScript() {
Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_DBG_SERVER_SCRIPT);
try {
Services.ppmm.broadcastAsyncMessage("debug:close-content-server");
} catch (e) {
// Nothing to do
}
},
/**
* Searches all active connections for an actor matching an ID.
*

View File

@ -47,59 +47,67 @@ const filenameParam = {
};
/**
* Both commands have the same set of standard optional parameters
* Both commands have almost the same set of standard optional parameters, except for the
* type of the --selector option, which can be a node only on the server.
*/
const standardParams = {
group: l10n.lookup("screenshotGroupOptions"),
params: [
{
name: "clipboard",
type: "boolean",
description: l10n.lookup("screenshotClipboardDesc"),
manual: l10n.lookup("screenshotClipboardManual")
},
{
name: "imgur",
type: "boolean",
description: l10n.lookup("screenshotImgurDesc"),
manual: l10n.lookup("screenshotImgurManual")
},
{
name: "delay",
type: { name: "number", min: 0 },
defaultValue: 0,
description: l10n.lookup("screenshotDelayDesc"),
manual: l10n.lookup("screenshotDelayManual")
},
{
name: "dpr",
type: { name: "number", min: 0, allowFloat: true },
defaultValue: 0,
description: l10n.lookup("screenshotDPRDesc"),
manual: l10n.lookup("screenshotDPRManual")
},
{
name: "fullpage",
type: "boolean",
description: l10n.lookup("screenshotFullPageDesc"),
manual: l10n.lookup("screenshotFullPageManual")
},
{
name: "selector",
type: "node",
defaultValue: null,
description: l10n.lookup("inspectNodeDesc"),
manual: l10n.lookup("inspectNodeManual")
},
{
name: "file",
type: "boolean",
description: l10n.lookup("screenshotFileDesc"),
manual: l10n.lookup("screenshotFileManual"),
},
]
const getScreenshotCommandParams = function (isClient) {
return {
group: l10n.lookup("screenshotGroupOptions"),
params: [
{
name: "clipboard",
type: "boolean",
description: l10n.lookup("screenshotClipboardDesc"),
manual: l10n.lookup("screenshotClipboardManual")
},
{
name: "imgur",
type: "boolean",
description: l10n.lookup("screenshotImgurDesc"),
manual: l10n.lookup("screenshotImgurManual")
},
{
name: "delay",
type: { name: "number", min: 0 },
defaultValue: 0,
description: l10n.lookup("screenshotDelayDesc"),
manual: l10n.lookup("screenshotDelayManual")
},
{
name: "dpr",
type: { name: "number", min: 0, allowFloat: true },
defaultValue: 0,
description: l10n.lookup("screenshotDPRDesc"),
manual: l10n.lookup("screenshotDPRManual")
},
{
name: "fullpage",
type: "boolean",
description: l10n.lookup("screenshotFullPageDesc"),
manual: l10n.lookup("screenshotFullPageManual")
},
{
name: "selector",
// On the client side, don't try to parse the selector as a node as it will
// trigger an unsafe CPOW.
type: isClient ? "string" : "node",
defaultValue: null,
description: l10n.lookup("inspectNodeDesc"),
manual: l10n.lookup("inspectNodeManual")
},
{
name: "file",
type: "boolean",
description: l10n.lookup("screenshotFileDesc"),
manual: l10n.lookup("screenshotFileManual"),
},
]
};
};
const clientScreenshotParams = getScreenshotCommandParams(true);
const serverScreenshotParams = getScreenshotCommandParams(false);
exports.items = [
{
/**
@ -180,7 +188,7 @@ exports.items = [
tooltipText: l10n.lookup("screenshotTooltipPage"),
params: [
filenameParam,
standardParams,
clientScreenshotParams,
],
exec: function (args, context) {
// Re-execute the command on the server
@ -199,7 +207,10 @@ exports.items = [
name: "screenshot_server",
hidden: true,
returnType: "imageSummary",
params: [ filenameParam, standardParams ],
params: [
filenameParam,
serverScreenshotParams,
],
exec: function (args, context) {
return captureScreenshot(args, context.environment.document);
},

View File

@ -1042,6 +1042,7 @@ EffectCompositor::PreTraverseInSubtree(Element* aRoot,
// middle of the servo traversal.
mPresContext->RestyleManager()->AsServo()->
PostRestyleEventForAnimations(target.mElement,
target.mPseudoType,
cascadeLevel == CascadeLevel::Transitions
? eRestyle_CSSTransitions
: eRestyle_CSSAnimations);
@ -1108,6 +1109,7 @@ EffectCompositor::PreTraverse(dom::Element* aElement,
mPresContext->RestyleManager()->AsServo()->
PostRestyleEventForAnimations(aElement,
aPseudoType,
cascadeLevel == CascadeLevel::Transitions
? eRestyle_CSSTransitions
: eRestyle_CSSAnimations);

View File

@ -249,6 +249,15 @@ public:
bool PreTraverseInSubtree(dom::Element* aElement,
AnimationRestyleType aRestyleType);
// Returns the target element for restyling.
//
// If |aPseudoType| is ::after or ::before, returns the generated content
// element of which |aElement| is the parent. If |aPseudoType| is any other
// pseudo type (other thant CSSPseudoElementType::NotPseudo) returns nullptr.
// Otherwise, returns |aElement|.
static dom::Element* GetElementToRestyle(dom::Element* aElement,
CSSPseudoElementType aPseudoType);
private:
~EffectCompositor() = default;
@ -258,10 +267,6 @@ private:
CSSPseudoElementType aPseudoType,
CascadeLevel aCascadeLevel);
static dom::Element* GetElementToRestyle(dom::Element* aElement,
CSSPseudoElementType
aPseudoType);
// Get the properties in |aEffectSet| that we are able to animate on the
// compositor but which are also specified at a higher level in the cascade
// than the animations level.

View File

@ -0,0 +1,28 @@
<!doctype html>
<html class="reftest-wait">
<head>
<title>
Bug 1335998 - Handle {Interpolate, Accumulate}Matrix of mismatched transform lists
</title>
<style>
#target {
width: 100px; height: 100px;
background: blue;
transform: rotate(45deg);
}
</style>
</head>
<body>
<div id="target"></div>
</body>
<script>
var div = document.getElementById("target");
var animation = div.animate([ { transform: 'translateX(200px) scale(2.0)',
composite: 'accumulate' },
{ transform: 'rotate(-45deg)' } ],
2000);
animation.finished.then(function() {
document.documentElement.className = "";
});
</script>
</html>

View File

@ -26,5 +26,6 @@ pref(dom.animations-api.core.enabled,true) load 1333539-1.html
pref(dom.animations-api.core.enabled,true) load 1333539-2.html
pref(dom.animations-api.core.enabled,true) load 1333418-1.html
pref(dom.animations-api.core.enabled,true) load 1334583-1.html
pref(dom.animations-api.core.enabled,true) load 1335998-1.html
pref(dom.animations-api.core.enabled,true) load 1343589-1.html
pref(dom.animations-api.core.enabled,true) load 1359658-1.html

View File

@ -14,6 +14,12 @@
@keyframes animRight {
to { right: 100px }
}
::before {
content: ''
}
::after {
content: ''
}
</style>
<body>
<script>

View File

@ -3,6 +3,12 @@
<script src="../testcommon.js"></script>
<style>
@keyframes anim { }
::before {
content: ''
}
::after {
content: ''
}
</style>
<body>
<script>

View File

@ -11,6 +11,12 @@
@keyframes multiPropAnim {
to { background: green, opacity: 0.5, left: 100px, top: 100px }
}
::before {
content: ''
}
::after {
content: ''
}
@keyframes empty { }
</style>
<body>

View File

@ -6,6 +6,7 @@
@keyframes anim2 { }
.before::before {
animation: anim1 10s;
content: '';
}
.after-with-mix-anims-trans::after {
content: '';
@ -17,6 +18,7 @@
.after-change::after {
width: 100px;
height: 100px;
content: '';
}
</style>
<body>

View File

@ -921,6 +921,10 @@ public:
*/
mozilla::dom::Element* GetEditingHost();
bool SupportsLangAttr() const {
return IsHTMLElement() || IsSVGElement() || IsXULElement();
}
/**
* Determining language. Look at the nearest ancestor element that has a lang
* attribute in the XML namespace or is an HTML/SVG element and has a lang in
@ -933,8 +937,7 @@ public:
// XHTML1 section C.7).
bool hasAttr = content->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang,
aResult);
if (!hasAttr && (content->IsHTMLElement() || content->IsSVGElement() ||
content->IsXULElement())) {
if (!hasAttr && content->SupportsLangAttr()) {
hasAttr = content->GetAttr(kNameSpaceID_None, nsGkAtoms::lang,
aResult);
}

View File

@ -121,6 +121,10 @@ public:
NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver)
// Note that we don't need to add mFirstAddedNodeContainer nor
// mLastAddedNodeContainer to cycle collection because they are non-null only
// during short time and shouldn't be touched while they are non-null.
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
nsAutoScriptBlocker scriptBlocker;
@ -132,6 +136,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditableNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditor)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)
@ -147,6 +152,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditor)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
mStartOfRemovingTextRangeCache.mContainerNode)
@ -166,7 +172,9 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)
IMEContentObserver::IMEContentObserver()
: mESM(nullptr)
: mFirstAddedNodeOffset(0)
, mLastAddedNodeOffset(0)
, mESM(nullptr)
, mIMENotificationRequests(nullptr)
, mSuppressNotifications(0)
, mPreCharacterDataChangeLength(-1)
@ -201,7 +209,6 @@ IMEContentObserver::Init(nsIWidget* aWidget,
// If this is now trying to initialize with new contents, all observers
// should be registered again for simpler implementation.
UnregisterObservers();
// Clear members which may not be initialized again.
Clear();
}
@ -352,6 +359,8 @@ IMEContentObserver::InitWithEditor(nsPresContext* aPresContext,
return false;
}
mDocumentObserver = new DocumentObserver(*this);
MOZ_ASSERT(!WasInitializedWithPlugin());
return true;
@ -383,6 +392,11 @@ IMEContentObserver::InitWithPlugin(nsPresContext* aPresContext,
mEditor = nullptr;
mEditableNode = aContent;
mRootContent = aContent;
// Should be safe to clear mDocumentObserver here even though it *might*
// grab this instance because this is called by Init() and the callers of
// it and MaybeReinitialize() grabs this instance with local RefPtr.
// So, this won't cause refcount of this instance become 0.
mDocumentObserver = nullptr;
mDocShell = aPresContext->GetDocShell();
if (NS_WARN_IF(!mDocShell)) {
@ -408,6 +422,12 @@ IMEContentObserver::Clear()
mEditableNode = nullptr;
mRootContent = nullptr;
mDocShell = nullptr;
// Should be safe to clear mDocumentObserver here even though it grabs
// this instance in most cases because this is called by Init() or Destroy().
// The callers of Init() grab this instance with local RefPtr.
// The caller of Destroy() also grabs this instance with local RefPtr.
// So, this won't cause refcount of this instance become 0.
mDocumentObserver = nullptr;
}
void
@ -446,6 +466,13 @@ IMEContentObserver::ObserveEditableNode()
// non-plugin content since we cannot detect text changes in
// plugins.
mRootContent->AddMutationObserver(this);
// If it's in a document (should be so), we can use document observer to
// reduce redundant computation of text change offsets.
nsIDocument* doc = mRootContent->GetComposedDoc();
if (doc) {
RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
documentObserver->Observe(doc);
}
}
if (mDocShell) {
@ -519,6 +546,11 @@ IMEContentObserver::UnregisterObservers()
mRootContent->RemoveMutationObserver(this);
}
if (mDocumentObserver) {
RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
documentObserver->StopObserving();
}
if (mDocShell) {
mDocShell->RemoveWeakScrollObserver(this);
mDocShell->RemoveWeakReflowObserver(this);
@ -907,6 +939,13 @@ IMEContentObserver::CharacterDataWillChange(nsIDocument* aDocument,
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
// Although we don't assume this change occurs while this is storing
// the range of added consecutive nodes, if it actually happens, we need to
// flush them since this change may occur before or in the range. So, it's
// safe to flush pending computation of mTextChangeData before handling this.
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
mPreCharacterDataChangeLength =
ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart,
aInfo->mChangeEnd);
@ -929,6 +968,8 @@ IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument,
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
MOZ_ASSERT(!HasAddedNodesDuringDocumentChange(),
"The stored range should be flushed before actually the data is changed");
int64_t removedLength = mPreCharacterDataChangeLength;
mPreCharacterDataChangeLength = -1;
@ -973,6 +1014,42 @@ IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
mStartOfRemovingTextRangeCache.Clear();
// If it's in a document change, nodes are added consecutively. Therefore,
// if we cache the first node and the last node, we need to compute the
// range once.
// FYI: This is not true if the change caused by an operation in the editor.
if (IsInDocumentChange()) {
// Now, mEndOfAddedTextCache may be invalid if node is added before
// the last node in mEndOfAddedTextCache. Clear it.
mEndOfAddedTextCache.Clear();
if (!HasAddedNodesDuringDocumentChange()) {
mFirstAddedNodeContainer = mLastAddedNodeContainer = aContainer;
mFirstAddedNodeOffset = aStartIndex;
mLastAddedNodeOffset = aEndIndex;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::NotifyContentAdded(), starts to store "
"consecutive added nodes", this));
return;
}
// If first node being added is not next node of the last node,
// notify IME of the previous range first, then, restart to cache the
// range.
if (NS_WARN_IF(!IsNextNodeOfLastAddedNode(aContainer, aStartIndex))) {
// Flush the old range first.
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
mFirstAddedNodeContainer = aContainer;
mFirstAddedNodeOffset = aStartIndex;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::NotifyContentAdded(), starts to store "
"consecutive added nodes", this));
}
mLastAddedNodeContainer = aContainer;
mLastAddedNodeOffset = aEndIndex;
return;
}
MOZ_ASSERT(!HasAddedNodesDuringDocumentChange(),
"The cache should be cleared when document change finished");
uint32_t offset = 0;
nsresult rv = NS_OK;
if (!mEndOfAddedTextCache.Match(aContainer, aStartIndex)) {
@ -1048,6 +1125,7 @@ IMEContentObserver::ContentRemoved(nsIDocument* aDocument,
}
mEndOfAddedTextCache.Clear();
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
nsINode* containerNode = NODE_FROM(aContainer, aDocument);
@ -1133,6 +1211,9 @@ IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
if (postAttrChangeLength == mPreAttrChangeLength) {
return;
}
// First, compute text range which were added during a document change.
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
// Then, compute the new text changed caused by this attribute change.
uint32_t start;
nsresult rv =
ContentEventHandler::GetFlatTextLengthInRange(
@ -1150,6 +1231,169 @@ IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
MaybeNotifyIMEOfTextChange(data);
}
void
IMEContentObserver::ClearAddedNodesDuringDocumentChange()
{
mFirstAddedNodeContainer = mLastAddedNodeContainer = nullptr;
mFirstAddedNodeOffset = mLastAddedNodeOffset = 0;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::ClearAddedNodesDuringDocumentChange()"
", finished storing consecutive nodes", this));
}
// static
nsIContent*
IMEContentObserver::GetChildNode(nsINode* aParent, int32_t aOffset)
{
if (!aParent->HasChildren() || aOffset < 0 ||
aOffset >= static_cast<int32_t>(aParent->Length())) {
return nullptr;
}
if (!aOffset) {
return aParent->GetFirstChild();
}
if (aOffset == static_cast<int32_t>(aParent->Length() - 1)) {
return aParent->GetLastChild();
}
return aParent->GetChildAt(aOffset);
}
bool
IMEContentObserver::IsNextNodeOfLastAddedNode(nsINode* aParent,
int32_t aOffset) const
{
MOZ_ASSERT(aParent);
MOZ_ASSERT(aOffset >= 0 &&
aOffset <= static_cast<int32_t>(aParent->Length()));
MOZ_ASSERT(mRootContent);
MOZ_ASSERT(HasAddedNodesDuringDocumentChange());
// If the parent node isn't changed, we can check it only with offset.
if (aParent == mLastAddedNodeContainer) {
if (NS_WARN_IF(mLastAddedNodeOffset != aOffset)) {
return false;
}
return true;
}
// If the parent node is changed, that means that given offset should be the
// last added node not having next sibling.
if (NS_WARN_IF(mLastAddedNodeOffset !=
static_cast<int32_t>(mLastAddedNodeContainer->Length()))) {
return false;
}
// If the node is aParent is a descendant of mLastAddedNodeContainer,
// aOffset should be 0.
if (mLastAddedNodeContainer == aParent->GetParent()) {
if (NS_WARN_IF(aOffset)) {
return false;
}
return true;
}
// Otherwise, we need to check it even with slow path.
nsIContent* lastAddedContent =
GetChildNode(mLastAddedNodeContainer, mLastAddedNodeOffset - 1);
if (NS_WARN_IF(!lastAddedContent)) {
return false;
}
nsIContent* nextContentOfLastAddedContent =
lastAddedContent->GetNextNode(mRootContent->GetParentNode());
if (NS_WARN_IF(!nextContentOfLastAddedContent)) {
return false;
}
nsIContent* startContent = GetChildNode(aParent, aOffset);
if (NS_WARN_IF(!startContent) ||
NS_WARN_IF(nextContentOfLastAddedContent != startContent)) {
return false;
}
#ifdef DEBUG
NS_WARNING_ASSERTION(
!aOffset || aOffset == static_cast<int32_t>(aParent->Length() - 1),
"Used slow path for aParent");
NS_WARNING_ASSERTION(
!(mLastAddedNodeOffset - 1) ||
mLastAddedNodeOffset ==
static_cast<int32_t>(mLastAddedNodeContainer->Length()),
"Used slow path for mLastAddedNodeContainer");
#endif // #ifdef DEBUG
return true;
}
void
IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange()
{
if (!HasAddedNodesDuringDocumentChange()) {
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange()"
", flushing stored consecutive nodes", this));
// Notify IME of text change which is caused by added nodes now.
// First, compute offset of start of first added node from start of the
// editor.
uint32_t offset;
nsresult rv =
ContentEventHandler::GetFlatTextLengthInRange(
NodePosition(mRootContent, 0),
NodePosition(mFirstAddedNodeContainer,
mFirstAddedNodeOffset),
mRootContent, &offset, LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED(rv))) {
ClearAddedNodesDuringDocumentChange();
return;
}
// Next, compute the text length of added nodes.
uint32_t length;
rv =
ContentEventHandler::GetFlatTextLengthInRange(
NodePosition(mFirstAddedNodeContainer,
mFirstAddedNodeOffset),
NodePosition(mLastAddedNodeContainer,
mLastAddedNodeOffset),
mRootContent, &length, LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED(rv))) {
ClearAddedNodesDuringDocumentChange();
return;
}
// Finally, try to notify IME of the range.
TextChangeData data(offset, offset, offset + length,
IsEditorHandlingEventForComposition(),
IsEditorComposing());
MaybeNotifyIMEOfTextChange(data);
ClearAddedNodesDuringDocumentChange();
}
void
IMEContentObserver::BeginDocumentUpdate()
{
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::BeginDocumentUpdate(), "
"HasAddedNodesDuringDocumentChange()=%s",
this, ToChar(HasAddedNodesDuringDocumentChange())));
MOZ_ASSERT(!HasAddedNodesDuringDocumentChange());
}
void
IMEContentObserver::EndDocumentUpdate()
{
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMEContentObserver::EndDocumentUpdate(), "
"HasAddedNodesDuringDocumentChange()=%s",
this, ToChar(HasAddedNodesDuringDocumentChange())));
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
}
void
IMEContentObserver::SuppressNotifyingIME()
{
@ -1975,4 +2219,105 @@ IMEContentObserver::IMENotificationSender::SendCompositionEventHandled()
"NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED", this));
}
/******************************************************************************
* mozilla::IMEContentObserver::DocumentObservingHelper
******************************************************************************/
NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver::DocumentObserver)
// StopObserving() releases mIMEContentObserver and mDocument.
tmp->StopObserving();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver::DocumentObserver)
NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver::DocumentObserver)
void
IMEContentObserver::DocumentObserver::Observe(nsIDocument* aDocument)
{
MOZ_ASSERT(aDocument);
// Guarantee that aDocument won't be destroyed during a call of
// StopObserving().
RefPtr<nsIDocument> newDocument = aDocument;
StopObserving();
mDocument = newDocument.forget();
mDocument->AddObserver(this);
}
void
IMEContentObserver::DocumentObserver::StopObserving()
{
if (!IsObserving()) {
return;
}
// Grab IMEContentObserver which could be destroyed during method calls.
RefPtr<IMEContentObserver> observer = mIMEContentObserver.forget();
// Stop observing the document first.
RefPtr<nsIDocument> document = mDocument.forget();
document->RemoveObserver(this);
// Notify IMEContentObserver of ending of document updates if this already
// notified it of beginning of document updates.
for (; IsUpdating(); --mDocumentUpdating) {
// FYI: IsUpdating() returns true until mDocumentUpdating becomes 0.
// However, IsObserving() returns false now because mDocument was
// already cleared above. Therefore, this method won't be called
// recursively.
observer->EndDocumentUpdate();
}
}
void
IMEContentObserver::DocumentObserver::Destroy()
{
StopObserving();
mIMEContentObserver = nullptr;
}
void
IMEContentObserver::DocumentObserver::BeginUpdate(nsIDocument* aDocument,
nsUpdateType aUpdateType)
{
if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving())) {
return;
}
if (!(aUpdateType & UPDATE_CONTENT_MODEL)) {
return;
}
mDocumentUpdating++;
mIMEContentObserver->BeginDocumentUpdate();
}
void
IMEContentObserver::DocumentObserver::EndUpdate(nsIDocument* aDocument,
nsUpdateType aUpdateType)
{
if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving()) ||
NS_WARN_IF(!IsUpdating())) {
return;
}
if (!(aUpdateType & UPDATE_CONTENT_MODEL)) {
return;
}
mDocumentUpdating--;
mIMEContentObserver->EndDocumentUpdate();
}
} // namespace mozilla

View File

@ -17,6 +17,7 @@
#include "nsISelectionListener.h"
#include "nsIScrollObserver.h"
#include "nsIWidget.h"
#include "nsStubDocumentObserver.h"
#include "nsStubMutationObserver.h"
#include "nsThreadUtils.h"
#include "nsWeakReference.h"
@ -73,9 +74,39 @@ public:
nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
/**
* Init() initializes the instance, i.e., retrieving necessary objects and
* starts to observe something.
* Be aware, callers of this method need to guarantee that the instance
* won't be released during calling this.
*
* @param aWidget The widget which can access native IME.
* @param aPresContext The PresContext which has aContent.
* @param aContent An editable element or a plugin host element which
* user may use IME in.
* Or nullptr if this will observe design mode
* document.
* @param aEditor When aContent is an editable element or nullptr,
* non-nullptr referring an editor instance which
* manages aContent.
* Otherwise, i.e., this will observe a plugin content,
* should be nullptr.
*/
void Init(nsIWidget* aWidget, nsPresContext* aPresContext,
nsIContent* aContent, nsIEditor* aEditor);
/**
* Destroy() finalizes the instance, i.e., stops observing contents and
* clearing the members.
* Be aware, callers of this method need to guarantee that the instance
* won't be released during calling this.
*/
void Destroy();
/**
* Returns false if the instance refers some objects and observing them.
* Otherwise, true.
*/
bool Destroyed() const;
/**
@ -84,10 +115,14 @@ public:
* storing the instance.
*/
void DisconnectFromEventStateManager();
/**
* MaybeReinitialize() tries to restart to observe the editor's root node.
* This is useful when the editor is reframed and all children are replaced
* with new node instances.
* Be aware, callers of this method need to guarantee that the instance
* won't be released during calling this.
*
* @return Returns true if the instance is managing the content.
* Otherwise, false.
*/
@ -95,6 +130,7 @@ public:
nsPresContext* aPresContext,
nsIContent* aContent,
nsIEditor* aEditor);
bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent) const;
bool IsManaging(const TextComposition* aTextComposition) const;
bool WasInitializedWithPlugin() const;
@ -146,6 +182,61 @@ private:
bool IsSafeToNotifyIME() const;
bool IsEditorComposing() const;
/**
* nsINode::GetChildAt() is slow. So, this avoids to use it if it's
* first child or last child of aParent.
*/
static nsIContent* GetChildNode(nsINode* aParent, int32_t aOffset);
// Following methods are called by DocumentObserver when
// beginning to update the contents and ending updating the contents.
void BeginDocumentUpdate();
void EndDocumentUpdate();
// Following methods manages added nodes during a document change.
/**
* MaybeNotifyIMEOfAddedTextDuringDocumentChange() may send text change
* notification caused by the nodes added between mFirstAddedNodeOffset in
* mFirstAddedNodeContainer and mLastAddedNodeOffset in
* mLastAddedNodeContainer and forgets the range.
*/
void MaybeNotifyIMEOfAddedTextDuringDocumentChange();
/**
* IsInDocumentChange() returns true while the DOM tree is being modified
* with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or
* insertAdjacentHTML(). This returns false when user types something in
* the focused editor editor.
*/
bool IsInDocumentChange() const
{
return mDocumentObserver && mDocumentObserver->IsUpdating();
}
/**
* Forget the range of added nodes during a document change.
*/
void ClearAddedNodesDuringDocumentChange();
/**
* HasAddedNodesDuringDocumentChange() returns true when this stores range
* of nodes which were added into the DOM tree during a document change but
* have not been sent to IME. Note that this should always return false when
* IsInDocumentChange() returns false.
*/
bool HasAddedNodesDuringDocumentChange() const
{
return mFirstAddedNodeContainer && mLastAddedNodeContainer;
}
/**
* Returns true if the node at aOffset in aParent is next node of the node at
* mLastAddedNodeOffset in mLastAddedNodeContainer in pre-order tree
* traversal of the DOM.
*/
bool IsNextNodeOfLastAddedNode(nsINode* aParent, int32_t aOffset) const;
void PostFocusSetNotification();
void MaybeNotifyIMEOfFocusSet();
void PostTextChangeNotification();
@ -278,6 +369,47 @@ private:
// mQueuedSender is, it was put into the event queue but not run yet.
RefPtr<IMENotificationSender> mQueuedSender;
/**
* IMEContentObserver is a mutation observer of mRootContent. However,
* it needs to know the beginning of content changes and end of it too for
* reducing redundant computation of text offset with ContentEventHandler.
* Therefore, it needs helper class to listen only them since if
* both mutations were observed by IMEContentObserver directly, each
* methods need to check if the changing node is in mRootContent but it's
* too expensive.
*/
class DocumentObserver final : public nsStubDocumentObserver
{
public:
explicit DocumentObserver(IMEContentObserver& aIMEContentObserver)
: mIMEContentObserver(&aIMEContentObserver)
, mDocumentUpdating(0)
{
}
NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
void Observe(nsIDocument* aDocument);
void StopObserving();
void Destroy();
bool Destroyed() const { return !mIMEContentObserver; }
bool IsObserving() const { return mDocument != nullptr; }
bool IsUpdating() const { return mDocumentUpdating != 0; }
private:
DocumentObserver() = delete;
virtual ~DocumentObserver() { Destroy(); }
RefPtr<IMEContentObserver> mIMEContentObserver;
nsCOMPtr<nsIDocument> mDocument;
uint32_t mDocumentUpdating;
};
RefPtr<DocumentObserver> mDocumentObserver;
/**
* FlatTextCache stores flat text length from start of the content to
* mNodeOffset of mContainerNode.
@ -332,6 +464,27 @@ private:
// handled by the editor and no other mutation (e.g., adding node) occur.
FlatTextCache mStartOfRemovingTextRangeCache;
// mFirstAddedNodeContainer is parent node of first added node in current
// document change. So, this is not nullptr only when a node was added
// during a document change and the change has not been included into
// mTextChangeData yet.
// Note that this shouldn't be in cycle collection since this is not nullptr
// only during a document change.
nsCOMPtr<nsINode> mFirstAddedNodeContainer;
// mLastAddedNodeContainer is parent node of last added node in current
// document change. So, this is not nullptr only when a node was added
// during a document change and the change has not been included into
// mTextChangeData yet.
// Note that this shouldn't be in cycle collection since this is not nullptr
// only during a document change.
nsCOMPtr<nsINode> mLastAddedNodeContainer;
// mFirstAddedNodeOffset is offset of first added node in
// mFirstAddedNodeContainer.
int32_t mFirstAddedNodeOffset;
// mLastAddedNodeOffset is offset of *after* last added node in
// mLastAddedNodeContainer. I.e., the index of last added node + 1.
int32_t mLastAddedNodeOffset;
TextChangeData mTextChangeData;
// mSelectionData is the last selection data which was notified. The

View File

@ -863,8 +863,9 @@ IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
MOZ_LOG(sISMLog, LogLevel::Debug,
(" UpdateIMEState(), try to reinitialize the "
"active IMEContentObserver"));
if (!sActiveIMEContentObserver->MaybeReinitialize(widget, sPresContext,
aContent, &aEditorBase)) {
RefPtr<IMEContentObserver> contentObserver = sActiveIMEContentObserver;
if (!contentObserver->MaybeReinitialize(widget, sPresContext,
aContent, &aEditorBase)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), failed to reinitialize the "
"active IMEContentObserver"));

View File

@ -1287,10 +1287,11 @@ private:
if (aReject.mError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
SLOG("OnSeekRejected reason=WAITING_FOR_DATA type=%d", aReject.mType);
MOZ_ASSERT(!mMaster->IsRequestingAudioData());
MOZ_ASSERT(!mMaster->IsRequestingVideoData());
MOZ_ASSERT(!mMaster->IsWaitingAudioData());
MOZ_ASSERT(!mMaster->IsWaitingVideoData());
MOZ_ASSERT_IF(aReject.mType == MediaData::AUDIO_DATA, !mMaster->IsRequestingAudioData());
MOZ_ASSERT_IF(aReject.mType == MediaData::VIDEO_DATA, !mMaster->IsRequestingVideoData());
MOZ_ASSERT_IF(aReject.mType == MediaData::AUDIO_DATA, !mMaster->IsWaitingAudioData());
MOZ_ASSERT_IF(aReject.mType == MediaData::VIDEO_DATA, !mMaster->IsWaitingVideoData());
// Fire 'waiting' to notify the player that we are waiting for data.
mMaster->UpdateNextFrameStatus(
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);

View File

@ -112,6 +112,9 @@ public:
int32_t offset;
ok &= NS_SUCCEEDED(info->Offset(&offset));
int32_t size;
ok &= NS_SUCCEEDED(info->Size(&size));
int64_t presentationTimeUs;
ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
@ -130,7 +133,7 @@ public:
return;
}
if (ok && presentationTimeUs >= 0) {
if (ok && (size > 0 || presentationTimeUs >= 0)) {
RefPtr<layers::Image> img = new SurfaceTextureImage(
mDecoder->mSurfaceHandle, inputInfo.mImageSize, false /* NOT continuous */,
gl::OriginPos::BottomLeft);

View File

@ -385,11 +385,12 @@ H264Converter::CheckForSPSChange(MediaRawData* aSample)
// We now check if the out of band one has changed.
// This scenario can only occur on Android with devices that can recycle a
// decoder.
if (mp4_demuxer::AnnexB::HasSPS(aSample->mExtraData) &&
!mp4_demuxer::AnnexB::CompareExtraData(aSample->mExtraData,
mOriginalExtraData)) {
extra_data = mOriginalExtraData = aSample->mExtraData;
if (!mp4_demuxer::AnnexB::HasSPS(aSample->mExtraData) ||
mp4_demuxer::AnnexB::CompareExtraData(aSample->mExtraData,
mOriginalExtraData)) {
return NS_OK;
}
extra_data = mOriginalExtraData = aSample->mExtraData;
}
if (mp4_demuxer::AnnexB::CompareExtraData(extra_data,
mCurrentConfig.mExtraData)) {

View File

@ -13,10 +13,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1323324
/** Test for Bug 1323324 **/
SimpleTest.waitForExplicitFinish();
var globalWrapper;
function verifyPromiseGlobal(p, global, msg) {
// SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
// the actual global. We want to grab the underlying object.
var globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
is(SpecialPowers.unwrap(globalWrapper), global,
msg + " should come from " + global.label);
}

View File

@ -13,18 +13,20 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1323324
/** Test for Bug 1323324 **/
SimpleTest.waitForExplicitFinish();
var globalWrapper;
function verifyPromiseGlobal(p, global, msg) {
// SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
// the actual global. We want to grab the underlying object.
var globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
is(SpecialPowers.unwrap(globalWrapper), global,
msg + " should come from " + global.label);
}
const isXrayArgumentTest = false;
var func;
function getPromise(global, arg) {
var func = new global.Function("x", "return x").bind(undefined, arg);
func = new global.Function("x", "return x").bind(undefined, arg);
return TestFunctions.passThroughCallbackPromise(func);
}

View File

@ -767,6 +767,7 @@ nsSMILAnimationController::PreTraverseInSubtree(Element* aRoot)
context->RestyleManager()->AsServo()->
PostRestyleEventForAnimations(key.mElement,
CSSPseudoElementType::NotPseudo,
eRestyle_StyleAttribute_Animations);
foundElementsNeedingRestyle = true;

View File

@ -617,6 +617,17 @@ EditorBase::GetSelectionController(nsISelectionController** aSel)
{
NS_ENSURE_TRUE(aSel, NS_ERROR_NULL_POINTER);
*aSel = nullptr; // init out param
nsCOMPtr<nsISelectionController> selCon = GetSelectionController();
if (NS_WARN_IF(!selCon)) {
return NS_ERROR_NOT_INITIALIZED;
}
selCon.forget(aSel);
return NS_OK;
}
already_AddRefed<nsISelectionController>
EditorBase::GetSelectionController()
{
nsCOMPtr<nsISelectionController> selCon;
if (mSelConWeak) {
selCon = do_QueryReferent(mSelConWeak);
@ -624,11 +635,7 @@ EditorBase::GetSelectionController(nsISelectionController** aSel)
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
selCon = do_QueryInterface(presShell);
}
if (!selCon) {
return NS_ERROR_NOT_INITIALIZED;
}
NS_ADDREF(*aSel = selCon);
return NS_OK;
return selCon.forget();
}
NS_IMETHODIMP
@ -651,8 +658,7 @@ EditorBase::GetSelection(SelectionType aSelectionType,
{
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
*aSelection = nullptr;
nsCOMPtr<nsISelectionController> selcon;
GetSelectionController(getter_AddRefs(selcon));
nsCOMPtr<nsISelectionController> selcon = GetSelectionController();
if (!selcon) {
return NS_ERROR_NOT_INITIALIZED;
}

View File

@ -493,6 +493,7 @@ protected:
*/
bool EnsureComposition(WidgetCompositionEvent* aCompositionEvent);
already_AddRefed<nsISelectionController> GetSelectionController();
nsresult GetSelection(SelectionType aSelectionType,
nsISelection** aSelection);

View File

@ -525,7 +525,7 @@ nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChanne
//
unsigned int len = strlen(challenge);
void *inToken, *outToken;
void *inToken = nullptr, *outToken;
uint32_t inTokenLen, outTokenLen;
if (len > kNegotiateLen) {
@ -545,6 +545,7 @@ nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChanne
Base64Decode(challenge, len, (char**)&inToken, &inTokenLen);
if (NS_FAILED(rv)) {
free(inToken);
return rv;
}
}
@ -552,7 +553,6 @@ nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChanne
//
// Initializing, don't use an input token.
//
inToken = nullptr;
inTokenLen = 0;
}

View File

@ -12,7 +12,8 @@ function* test(testDriver) {
// scroll over the scrollbar, and make sure the subframe scrolls
var scrollPos = subframe.scrollTop;
var scrollbarX = (200 + subframe.clientWidth) / 2;
yield moveMouseAndScrollWheelOver(subframe, scrollbarX, 100, testDriver);
yield synthesizeNativeWheelAndWaitForScrollEvent(subframe, scrollbarX, 100,
0, -10, testDriver);
ok(subframe.scrollTop > scrollPos, "subframe scrolled after wheeling over scrollbar");
}

View File

@ -460,7 +460,7 @@ private:
DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms", CollectScrollTransforms, bool, false);
DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count", CompositorUnobserveCount, int32_t, 10);
DECL_OVERRIDE_PREF(Live, "gfx.webrender.omta.enabled", WebRenderOMTAEnabled, gfxPrefs::OverrideBase_WebRender());
DECL_GFX_PREF(Live, "gfx.webrender.profiler.enable", WebRenderProfilerEnabled, bool, false);
DECL_GFX_PREF(Live, "gfx.webrender.profiler.enabled", WebRenderProfilerEnabled, bool, false);
DECL_GFX_PREF(Live, "gfx.webrendest.enabled", WebRendestEnabled, bool, false);
// Use vsync events generated by hardware
DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs", WorkAroundDriverBugs, bool, true);
@ -501,7 +501,7 @@ private:
DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-outer-layers", LayersAllowOuterBoxShadow, gfxPrefs::OverrideBase_WebRender());
DECL_OVERRIDE_PREF(Live, "layers.advanced.bullet-layers", LayersAllowBulletLayers, gfxPrefs::OverrideBase_WebRender());
DECL_OVERRIDE_PREF(Live, "layers.advanced.button-foreground-layers", LayersAllowButtonForegroundLayers, gfxPrefs::OverrideBase_WebRender());
DECL_OVERRIDE_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, gfxPrefs::OverrideBase_WebRendest());
DECL_OVERRIDE_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, gfxPrefs::OverrideBase_WebRender());
DECL_OVERRIDE_PREF(Live, "layers.advanced.caret-layers", LayersAllowCaretLayers, gfxPrefs::OverrideBase_WebRender());
DECL_OVERRIDE_PREF(Live, "layers.advanced.columnRule-layers", LayersAllowColumnRuleLayers, gfxPrefs::OverrideBase_WebRender());
DECL_OVERRIDE_PREF(Live, "layers.advanced.displaybuttonborder-layers", LayersAllowDisplayButtonBorder, gfxPrefs::OverrideBase_WebRender());

View File

@ -129,6 +129,7 @@ NS_IMETHODIMP nsTextToSubURI::UnEscapeAndConvert(
nsDependentCString label(charset);
nsAutoCString encoding;
if (!EncodingUtils::FindEncodingForLabelNoReplacement(label, encoding)) {
free(unescaped);
return NS_ERROR_UCONV_NOCONV;
}
nsCOMPtr<nsIUnicodeDecoder> decoder =

View File

@ -217,6 +217,7 @@ function treatAsSafeArgument(entry, varName, csuName)
["Gecko_AddPropertyToSet", "aPropertySet", null],
["Gecko_CalcStyleDifference", "aAnyStyleChanged", null],
["Gecko_nsStyleSVG_CopyContextProperties", "aDst", null],
["Gecko_nsStyleFont_PrefillDefaultForGeneric", "aFont", null],
];
for (var [entryMatch, varMatch, csuMatch] of whitelist) {
assert(entryMatch || varMatch || csuMatch);
@ -331,8 +332,7 @@ function ignoreCallEdge(entry, callee)
// We manually lock here
if (name == "Gecko_nsFont_InitSystem" ||
name == "Gecko_GetFontMetrics" ||
name == "Gecko_nsStyleFont_FixupNoneGeneric" ||
name == "Gecko_nsStyleFont_FixupMinFontSize")
name == "ThreadSafeGetDefaultFontHelper")
{
return true;
}
@ -390,6 +390,7 @@ function ignoreContents(entry)
/CSSValueSerializeCalcOps::Append/,
"Gecko_CSSValue_SetFunction",
"Gecko_CSSValue_SetArray",
"Gecko_CSSValue_InitSharedList",
"Gecko_EnsureMozBorderColors",
"Gecko_ClearMozBorderColors",
"Gecko_AppendMozBorderColors",

View File

@ -10665,33 +10665,110 @@ BytecodeEmitter::emitClass(ParseNode* pn)
return false;
}
// Pseudocode for class declarations:
//
// class extends BaseExpression {
// constructor() { ... }
// ...
// }
//
//
// if defined <BaseExpression> {
// let heritage = BaseExpression;
//
// if (heritage !== null) {
// funProto = heritage;
// objProto = heritage.prototype;
// } else {
// funProto = %FunctionPrototype%;
// objProto = null;
// }
// } else {
// objProto = %ObjectPrototype%;
// }
//
// let homeObject = ObjectCreate(objProto);
//
// if defined <constructor> {
// if defined <BaseExpression> {
// cons = DefineMethod(<constructor>, proto=homeObject, funProto=funProto);
// } else {
// cons = DefineMethod(<constructor>, proto=homeObject);
// }
// } else {
// if defined <BaseExpression> {
// cons = DefaultDerivedConstructor(proto=homeObject, funProto=funProto);
// } else {
// cons = DefaultConstructor(proto=homeObject);
// }
// }
//
// cons.prototype = homeObject;
// homeObject.constructor = cons;
//
// EmitPropertyList(...)
// This is kind of silly. In order to the get the home object defined on
// the constructor, we have to make it second, but we want the prototype
// on top for EmitPropertyList, because we expect static properties to be
// rarer. The result is a few more swaps than we would like. Such is life.
if (heritageExpression) {
if (!emitTree(heritageExpression))
return false;
if (!emit1(JSOP_CLASSHERITAGE))
return false;
if (!emit1(JSOP_OBJWITHPROTO))
IfThenElseEmitter ifThenElse(this);
if (!emitTree(heritageExpression)) // ... HERITAGE
return false;
// JSOP_CLASSHERITAGE leaves both protos on the stack. After
// creating the prototype, swap it to the bottom to make the
// constructor.
if (!emit1(JSOP_SWAP))
// Heritage must be null or a non-generator constructor
if (!emit1(JSOP_CHECKCLASSHERITAGE)) // ... HERITAGE
return false;
// [IF] (heritage !== null)
if (!emit1(JSOP_DUP)) // ... HERITAGE HERITAGE
return false;
if (!emit1(JSOP_NULL)) // ... HERITAGE HERITAGE NULL
return false;
if (!emit1(JSOP_STRICTNE)) // ... HERITAGE NE
return false;
// [THEN] funProto = heritage, objProto = heritage.prototype
if (!ifThenElse.emitIfElse())
return false;
if (!emit1(JSOP_DUP)) // ... HERITAGE HERITAGE
return false;
if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) // ... HERITAGE PROTO
return false;
// [ELSE] funProto = %FunctionPrototype%, objProto = null
if (!ifThenElse.emitElse())
return false;
if (!emit1(JSOP_POP)) // ...
return false;
if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) // ... PROTO
return false;
if (!emit1(JSOP_NULL)) // ... PROTO NULL
return false;
// [ENDIF]
if (!ifThenElse.emitEnd())
return false;
if (!emit1(JSOP_OBJWITHPROTO)) // ... HERITAGE HOMEOBJ
return false;
if (!emit1(JSOP_SWAP)) // ... HOMEOBJ HERITAGE
return false;
} else {
if (!emitNewInit(JSProto_Object))
if (!emitNewInit(JSProto_Object)) // ... HOMEOBJ
return false;
}
// Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE
// is not used, an implicit value of %FunctionPrototype% is implied.
if (constructor) {
if (!emitFunction(constructor, !!heritageExpression))
if (!emitFunction(constructor, !!heritageExpression)) // ... HOMEOBJ CONSTRUCTOR
return false;
if (constructor->pn_funbox->needsHomeObject()) {
if (!emit2(JSOP_INITHOMEOBJECT, 0))
if (!emit2(JSOP_INITHOMEOBJECT, 0)) // ... HOMEOBJ CONSTRUCTOR
return false;
}
} else {
@ -10712,34 +10789,34 @@ BytecodeEmitter::emitClass(ParseNode* pn)
JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty;
if (heritageExpression) {
if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR))
if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) // ... HOMEOBJ CONSTRUCTOR
return false;
} else {
if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR))
if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) // ... HOMEOBJ CONSTRUCTOR
return false;
}
}
if (!emit1(JSOP_SWAP))
if (!emit1(JSOP_SWAP)) // ... CONSTRUCTOR HOMEOBJ
return false;
if (!emit1(JSOP_DUP2))
if (!emit1(JSOP_DUP2)) // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ
return false;
if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP))
if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR
return false;
if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP))
if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) // ... CONSTRUCTOR HOMEOBJ
return false;
RootedPlainObject obj(cx);
if (!emitPropertyList(classMethods, &obj, ClassBody))
if (!emitPropertyList(classMethods, &obj, ClassBody)) // ... CONSTRUCTOR HOMEOBJ
return false;
if (!emit1(JSOP_POP))
if (!emit1(JSOP_POP)) // ... CONSTRUCTOR
return false;
if (names) {
ParseNode* innerName = names->innerBinding();
if (!emitLexicalInitialization(innerName))
if (!emitLexicalInitialization(innerName)) // ... CONSTRUCTOR
return false;
// Pop the inner scope.
@ -10749,15 +10826,17 @@ BytecodeEmitter::emitClass(ParseNode* pn)
ParseNode* outerName = names->outerBinding();
if (outerName) {
if (!emitLexicalInitialization(outerName))
if (!emitLexicalInitialization(outerName)) // ... CONSTRUCTOR
return false;
// Only class statements make outer bindings, and they do not leave
// themselves on the stack.
if (!emit1(JSOP_POP))
if (!emit1(JSOP_POP)) // ...
return false;
}
}
// The CONSTRUCTOR is left on stack if this is an expression.
MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
return true;

View File

@ -285,3 +285,9 @@ runTest(`(module
end $outer
)
)`);
// Import as a start function.
runTest(`(module
(import "env" "test" (func))
(start 0)
)`);

View File

@ -4695,3 +4695,82 @@ BaselineCompiler::emit_JSOP_JUMPTARGET()
masm.inc64(AbsoluteAddress(counterAddr));
return true;
}
typedef bool (*CheckClassHeritageOperationFn)(JSContext*, HandleValue);
static const VMFunction CheckClassHeritageOperationInfo =
FunctionInfo<CheckClassHeritageOperationFn>(js::CheckClassHeritageOperation,
"CheckClassHeritageOperation");
bool
BaselineCompiler::emit_JSOP_CHECKCLASSHERITAGE()
{
frame.syncStack(0);
// Leave the heritage value on the stack.
masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
prepareVMCall();
pushArg(R0);
return callVM(CheckClassHeritageOperationInfo);
}
bool
BaselineCompiler::emit_JSOP_BUILTINPROTO()
{
// The builtin prototype is a constant for a given global.
RootedObject builtin(cx);
JSProtoKey key = static_cast<JSProtoKey>(GET_UINT8(pc));
MOZ_ASSERT(key < JSProto_LIMIT);
if (!GetBuiltinPrototype(cx, key, &builtin))
return false;
frame.push(ObjectValue(*builtin));
return true;
}
typedef JSObject* (*ObjectWithProtoOperationFn)(JSContext*, HandleValue);
static const VMFunction ObjectWithProtoOperationInfo =
FunctionInfo<ObjectWithProtoOperationFn>(js::ObjectWithProtoOperation,
"ObjectWithProtoOperationInfo");
bool
BaselineCompiler::emit_JSOP_OBJWITHPROTO()
{
frame.syncStack(0);
// Leave the proto value on the stack for the decompiler
masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
prepareVMCall();
pushArg(R0);
if (!callVM(ObjectWithProtoOperationInfo))
return false;
masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
frame.pop();
frame.push(R0);
return true;
}
typedef JSObject* (*FunWithProtoFn)(JSContext*, HandleFunction, HandleObject, HandleObject);
static const VMFunction FunWithProtoInfo =
FunctionInfo<FunWithProtoFn>(js::FunWithProtoOperation, "FunWithProtoOperation");
bool
BaselineCompiler::emit_JSOP_FUNWITHPROTO()
{
frame.popRegsAndSync(1);
masm.unboxObject(R0, R0.scratchReg());
masm.loadPtr(frame.addressOfEnvironmentChain(), R1.scratchReg());
prepareVMCall();
pushArg(R0.scratchReg());
pushArg(R1.scratchReg());
pushArg(ImmGCPtr(script->getFunction(GET_UINT32_INDEX(pc))));
if (!callVM(FunWithProtoInfo))
return false;
masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
frame.push(R0);
return true;
}

View File

@ -241,7 +241,11 @@ namespace jit {
_(JSOP_DEBUGCHECKSELFHOSTED) \
_(JSOP_JUMPTARGET) \
_(JSOP_IS_CONSTRUCTING) \
_(JSOP_TRY_DESTRUCTURING_ITERCLOSE)
_(JSOP_TRY_DESTRUCTURING_ITERCLOSE) \
_(JSOP_CHECKCLASSHERITAGE) \
_(JSOP_BUILTINPROTO) \
_(JSOP_OBJWITHPROTO) \
_(JSOP_FUNWITHPROTO)
class BaselineCompiler : public BaselineCompilerSpecific
{

View File

@ -102,11 +102,12 @@ MSG_DEF(JSMSG_CANT_SET_PROTO_OF, 1, JSEXN_TYPEERR, "can't set prototype of
MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE, 0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle")
MSG_DEF(JSMSG_INVALID_ARG_TYPE, 3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}")
MSG_DEF(JSMSG_TERMINATED, 1, JSEXN_ERR, "Script terminated by timeout at:\n{0}")
MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL, 1, JSEXN_TYPEERR, "{0}.prototype is not an object or null")
MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|")
MSG_DEF(JSMSG_UNINITIALIZED_THIS, 1, JSEXN_REFERENCEERR, "|this| used uninitialized in {0} class constructor")
MSG_DEF(JSMSG_UNINITIALIZED_THIS_ARROW, 0, JSEXN_REFERENCEERR, "|this| used uninitialized in arrow function in class constructor")
MSG_DEF(JSMSG_BAD_DERIVED_RETURN, 1, JSEXN_TYPEERR, "derived class constructor returned invalid value {0}")
MSG_DEF(JSMSG_BAD_HERITAGE, 2, JSEXN_TYPEERR, "class heritage {0} is {1}")
MSG_DEF(JSMSG_NOT_OBJORNULL, 1, JSEXN_TYPEERR, "{0} is not an object or null")
// JSON
MSG_DEF(JSMSG_JSON_BAD_PARSE, 3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data")

View File

@ -729,6 +729,7 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetSt
case JSOP_CHECKOBJCOERCIBLE:
case JSOP_CHECKTHIS:
case JSOP_CHECKTHISREINIT:
case JSOP_CHECKCLASSHERITAGE:
case JSOP_DEBUGCHECKSELFHOSTED:
case JSOP_INITGLEXICAL:
case JSOP_INITLEXICAL:
@ -1970,12 +1971,6 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex)
case JSOP_DERIVEDCONSTRUCTOR:
return write("CONSTRUCTOR");
case JSOP_CLASSHERITAGE:
if (defIndex == 0)
return write("FUNCPROTO");
MOZ_ASSERT(defIndex == 1);
return write("OBJPROTO");
case JSOP_DOUBLE:
return sprinter.printf("%lf", script->getConst(GET_UINT32_INDEX(pc)).toDouble());

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