merge autoland to mozilla-central. r=merge a=merge

MozReview-Commit-ID: EdYDXWoGZXA
This commit is contained in:
Sebastian Hengst 2017-07-21 11:12:24 +02:00
commit 4218b5b991
216 changed files with 8042 additions and 7367 deletions

View File

@ -1498,9 +1498,6 @@ var gBrowserInit = {
}
}
// Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
setTimeout(function() { SafeBrowsing.init(); }, 2000);
Services.obs.addObserver(gIdentityHandler, "perm-changed");
Services.obs.addObserver(gRemoteControl, "remote-active");
Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history");
@ -4991,6 +4988,16 @@ var CombinedStopReload = {
});
},
/* This function is necessary to correctly vertically center the animation
within the toolbar, which uses -moz-pack-align:stretch; and thus a height
which is dependant on the font-size. */
setAnimationImageHeightRelativeToToolbarButtonHeight() {
let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
let toolbarItem = this.stopReloadContainer.closest(".customization-target > toolbaritem");
let bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
toolbarItem.style.setProperty("--toolbarbutton-height", bounds.height + "px");
},
switchToStop(aRequest, aWebProgress) {
if (!this._initialized)
return;
@ -5002,10 +5009,12 @@ var CombinedStopReload = {
this.animate;
this._cancelTransition();
if (shouldAnimate)
if (shouldAnimate) {
this.setAnimationImageHeightRelativeToToolbarButtonHeight();
this.stopReloadContainer.setAttribute("animate", "true");
else
} else {
this.stopReloadContainer.removeAttribute("animate");
}
this.reload.setAttribute("displaystop", "true");
},
@ -5019,10 +5028,12 @@ var CombinedStopReload = {
!aWebProgress.isLoadingDocument &&
this.animate;
if (shouldAnimate)
if (shouldAnimate) {
this.setAnimationImageHeightRelativeToToolbarButtonHeight();
this.stopReloadContainer.setAttribute("animate", "true");
else
} else {
this.stopReloadContainer.removeAttribute("animate");
}
this.reload.removeAttribute("displaystop");

View File

@ -8,7 +8,7 @@ add_task(async function() {
await pushPrefs(["accessibility.tabfocus", 7]);
// When the onboarding component is enabled, it would inject extra tour notification into
// the newtab page so there would be 2 more notification close button and action button
// the newtab page so there would be 3 more overlay button, notification close button and action button
let onbardingEnabled = AppConstants.NIGHTLY_BUILD && Services.prefs.getBoolPref("browser.onboarding.enabled");
// Focus count in new tab page.
@ -26,7 +26,7 @@ add_task(async function() {
}
let tab = await addNewTabPageTab();
if (onbardingEnabled) {
FOCUS_COUNT += 2;
FOCUS_COUNT += 3;
await promiseTourNotificationOpened(tab.linkedBrowser);
}
gURLBar.focus();
@ -37,7 +37,7 @@ add_task(async function() {
let expectedCount = 4;
if (onbardingEnabled) {
expectedCount += 2;
expectedCount += 3;
}
countFocus(expectedCount);

View File

@ -20,6 +20,9 @@ XPCOMUtils.defineLazyGetter(this, "WeaveService", () =>
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
"resource://gre/modules/SafeBrowsing.jsm");
// lazy module getters
/* global AboutHome:false, AboutNewTab:false, AddonManager:false, AppMenuNotifications:false,
@ -1185,6 +1188,10 @@ BrowserGlue.prototype = {
ContextualIdentityService.load();
});
Services.tm.idleDispatchToMainThread(() => {
SafeBrowsing.init();
}, 5000);
this._sanitizer.onStartup();
E10SAccessibilityCheck.onWindowsRestored();
},

View File

@ -3279,8 +3279,8 @@ var SessionStoreInternal = {
}
}
let restoreTabsLazily = this._prefBranch.getBoolPref("sessionstore.restore_tabs_lazily") &&
this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
let restoreOnDemand = this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
let restoreTabsLazily = this._prefBranch.getBoolPref("sessionstore.restore_tabs_lazily") && restoreOnDemand;
for (var t = 0; t < newTabCount; t++) {
let tabData = winData.tabs[t];
@ -3333,6 +3333,13 @@ var SessionStoreInternal = {
tabbrowser.selectedTab = tab;
tabbrowser.removeTab(leftoverTab);
}
// Prepare connection to the host when users hover mouse over this
// tab. If we're not restoring on demand, we'll prepare connection
// when we're restoring next tab.
if (!tabData.pinned && restoreOnDemand) {
this.speculativeConnectOnTabHover(tab, url);
}
}
tabs.push(tab);
@ -3417,6 +3424,42 @@ var SessionStoreInternal = {
this._sendRestoreCompletedNotifications();
},
/**
* Prepare connection to host beforehand.
*
* @param url
* URL of a host.
* @returns a flag indicates whether a connection has been made
*/
prepareConnectionToHost(url) {
if (!url.startsWith("about:")) {
let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
let uri = Services.io.newURI(url);
sc.speculativeConnect(uri, null, null);
return true;
}
return false;
},
/**
* Make a connection to a host when users hover mouse on a tab.
*
* @param tab
* A tab to set up a hover listener.
* @param url
* URL of a host.
*/
speculativeConnectOnTabHover(tab, url) {
tab.addEventListener("mouseover", () => {
let prepared = this.prepareConnectionToHost(url);
// This is used to test if a connection has been made beforehand.
if (gDebuggingEnabled) {
tab.__test_connection_prepared = prepared;
tab.__test_connection_url = url;
}
}, {once: true});
},
/**
* Restore multiple windows using the provided state.
* @param aWindow
@ -3683,6 +3726,19 @@ var SessionStoreInternal = {
this.restoreTabContent(tab, options);
} else if (!forceOnDemand) {
TabRestoreQueue.add(tab);
// Check if a tab is in queue and will be restored
// after the currently loading tabs. If so, prepare
// a connection to host to speed up page loading.
if (TabRestoreQueue.willRestoreSoon(tab)) {
if (activeIndex in tabData.entries) {
let url = tabData.entries[activeIndex].url;
let prepared = this.prepareConnectionToHost(url);
if (gDebuggingEnabled) {
tab.__test_connection_prepared = prepared;
tab.__test_connection_url = url;
}
}
}
this.restoreNextTab();
}
} else {

View File

@ -35,6 +35,7 @@ support-files =
browser_scrollPositions_sample_frameset.html
browser_scrollPositions_readerModeArticle.html
browser_sessionStorage.html
browser_speculative_connect.html
browser_248970_b_sample.html
browser_339445_sample.html
browser_423132_sample.html
@ -263,3 +264,5 @@ skip-if = !e10s # Tabs can't crash without e10s
[browser_cookies.js]
[browser_cookies_legacy.js]
[browser_cookies_privacy.js]
[browser_speculative_connect.js]

View File

@ -0,0 +1,9 @@
<html>
<header>
<title>Dummy html page to test speculative connect</title>
</header>
<body>
Hello Speculative Connect
</body>
</html>

View File

@ -0,0 +1,96 @@
const TEST_URLS = [
"about:buildconfig",
"http://mochi.test:8888/browser/browser/components/sessionstore/test/browser_speculative_connect.html",
""
];
/**
* This will open tabs in browser. This will also make the last tab
* inserted to be the selected tab.
*/
async function openTabs(win) {
for (let i = 0; i < TEST_URLS.length; ++i) {
await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URLS[i]);
}
}
add_task(async function speculative_connect_restore_on_demand() {
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
is(Services.prefs.getBoolPref("browser.sessionstore.restore_on_demand"), true, "We're restoring on demand");
forgetClosedWindows();
// Open a new window and populate with tabs.
let win = await promiseNewWindowLoaded();
await openTabs(win);
// Close the window.
await BrowserTestUtils.closeWindow(win);
// Reopen a window.
let newWin = undoCloseWindow(0);
// Make sure we wait until this window is restored.
await BrowserTestUtils.waitForEvent(newWin, "load");
await BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer, "SSTabRestored");
let tabs = newWin.gBrowser.tabs;
is(tabs.length, TEST_URLS.length + 1, "Restored right number of tabs");
let e = new MouseEvent("mouseover");
// First tab should be ignore, since it's the default blank tab when we open a new window.
// Trigger a mouse enter on second tab.
tabs[1].dispatchEvent(e);
is(tabs[1].__test_connection_prepared, false, "Second tab doesn't have a connection prepared");
is(tabs[1].__test_connection_url, TEST_URLS[0], "Second tab has correct url");
// Trigger a mouse enter on third tab.
tabs[2].dispatchEvent(e);
is(tabs[2].__test_connection_prepared, true, "Third tab has a connection prepared");
is(tabs[2].__test_connection_url, TEST_URLS[1], "Third tab has correct url");
// Last tab is the previously selected tab.
tabs[3].dispatchEvent(e);
is(tabs[3].__test_connection_prepared, undefined, "Previous selected tab should not have a connection prepared");
is(tabs[3].__test_connection_url, undefined, "Previous selected tab should not have a connection prepared");
await BrowserTestUtils.closeWindow(newWin);
});
add_task(async function speculative_connect_restore_automatically() {
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
is(Services.prefs.getBoolPref("browser.sessionstore.restore_on_demand"), false, "We're restoring automatically");
forgetClosedWindows();
// Open a new window and populate with tabs.
let win = await promiseNewWindowLoaded();
await openTabs(win);
// Close the window.
await BrowserTestUtils.closeWindow(win);
// Reopen a window.
let newWin = undoCloseWindow(0);
// Make sure we wait until this window is restored.
await BrowserTestUtils.waitForEvent(newWin, "load");
await BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer, "SSTabRestored");
let tabs = newWin.gBrowser.tabs;
is(tabs.length, TEST_URLS.length + 1, "Restored right number of tabs");
// First tab is ignore, since it's the default tab open when we open new window
// Second tab.
is(tabs[1].__test_connection_prepared, false, "Second tab doesn't have a connection prepared");
is(tabs[1].__test_connection_url, TEST_URLS[0], "Second tab has correct host url");
// Third tab.
is(tabs[2].__test_connection_prepared, true, "Third tab has a connection prepared");
is(tabs[2].__test_connection_url, TEST_URLS[1], "Third tab has correct host url");
// Last tab is the previously selected tab.
is(tabs[3].__test_connection_prepared, undefined, "Selected tab should not have a connection prepared");
is(tabs[3].__test_connection_url, undefined, "Selected tab should not have a connection prepared");
await BrowserTestUtils.closeWindow(newWin);
});

View File

@ -1005,7 +1005,8 @@ this.UITour = {
* Called before opening or after closing a highlight or info panel to see if
* we need to open or close the appMenu to see the annotation's anchor.
*/
_setAppMenuStateForAnnotation(aWindow, aAnnotationType, aShouldOpenForHighlight, aCallback = null) {
_setAppMenuStateForAnnotation(aWindow, aAnnotationType, aShouldOpenForHighlight, aTarget = null,
aCallback = null) {
log.debug("_setAppMenuStateForAnnotation:", aAnnotationType);
log.debug("_setAppMenuStateForAnnotation: Menu is expected to be:", aShouldOpenForHighlight ? "open" : "closed");
@ -1035,7 +1036,16 @@ this.UITour = {
// Actually show or hide the menu
if (this.appMenuOpenForAnnotation.size) {
log.debug("_setAppMenuStateForAnnotation: Opening the menu");
this.showMenu(aWindow, "appMenu", aCallback);
this.showMenu(aWindow, "appMenu", async () => {
// PanelMultiView's like the AppMenu might shuffle the DOM, which might result
// in our target being invalidated if it was anonymous content (since the XBL
// binding it belonged to got destroyed). We work around this by re-querying for
// the node and stuffing it into the old target structure.
log.debug("_setAppMenuStateForAnnotation: Refreshing target");
let refreshedTarget = await this.getTarget(aWindow, aTarget.targetName);
aTarget.node = refreshedTarget.node;
aCallback();
});
} else {
log.debug("_setAppMenuStateForAnnotation: Closing the menu");
this.hideMenu(aWindow, "appMenu");
@ -1152,6 +1162,7 @@ this.UITour = {
this._setAppMenuStateForAnnotation(aChromeWindow, "highlight",
this.targetIsInAppMenu(aTarget),
aTarget,
showHighlightPanel.bind(this));
},
@ -1281,9 +1292,17 @@ this.UITour = {
return;
}
// We need to bind the anchor argument to the showInfoPanel function call
// after _setAppMenuStateForAnnotation has finished, since
// _setAppMenuStateForAnnotation might have refreshed the anchor node.
let callShowInfoPanel = () => {
showInfoPanel.call(this, this._correctAnchor(aAnchor.node));
};
this._setAppMenuStateForAnnotation(aChromeWindow, "info",
this.targetIsInAppMenu(aAnchor),
showInfoPanel.bind(this, this._correctAnchor(aAnchor.node)));
aAnchor,
callShowInfoPanel);
},
isInfoOnTarget(aChromeWindow, aTargetName) {

View File

@ -15,22 +15,26 @@ var tests = [
function test_info_customize_auto_open_close(done) {
let popup = document.getElementById("UITourTooltip");
gContentAPI.showInfo("customize", "Customization", "Customize me please!");
UITour.getTarget(window, "customize").then((customizeTarget) => {
waitForPopupAtAnchor(popup, customizeTarget.node, function checkPanelIsOpen() {
isnot(PanelUI.panel.state, "closed", "Panel should have opened before the popup anchored");
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
// Move the info outside which should close the app menu.
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
UITour.getTarget(window, "appMenu").then((target) => {
waitForPopupAtAnchor(popup, target.node, function checkPanelIsClosed() {
isnot(PanelUI.panel.state, "open",
"Panel should have closed after the info moved elsewhere.");
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
done();
}, "Info should move to the appMenu button");
});
}, "Info panel should be anchored to the customize button");
let shownPromise = promisePanelShown(window);
shownPromise.then(() => {
UITour.getTarget(window, "customize").then((customizeTarget) => {
waitForPopupAtAnchor(popup, customizeTarget.node, function checkPanelIsOpen() {
isnot(PanelUI.panel.state, "closed", "Panel should have opened before the popup anchored");
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
// Move the info outside which should close the app menu.
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
UITour.getTarget(window, "appMenu").then((target) => {
waitForPopupAtAnchor(popup, target.node, function checkPanelIsClosed() {
isnot(PanelUI.panel.state, "open",
"Panel should have closed after the info moved elsewhere.");
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
done();
}, "Info should move to the appMenu button");
});
}, "Info panel should be anchored to the customize button");
});
});
},
function test_info_customize_manual_open_close(done) {

View File

@ -24,17 +24,19 @@ const TEST_THRESHOLD = {
const MULTI_EXPERIMENT = {
"beta": { buckets: { 1: .5, 4: 1, }, // 1 process: 50%, 4 processes: 50%
// See below for an explanation, this only allows webextensions.
get addonsDisableExperiment() { return getAddonsDisqualifyForMulti(); } },
// When on the "beta" channel, getAddonsDisqualifyForMulti
// will return true if any addon installed is not a web extension.
// Therefore, this returns true if and only if all addons
// installed are web extensions or if no addons are installed
// at all.
addonsDisableExperiment(prefix) { return getAddonsDisqualifyForMulti(); } },
"release": { buckets: { 1: .2, 4: 1 }, // 1 process: 20%, 4 processes: 80%
"release": { buckets: { 1: .99, 4: 1 }, // 1 process: 99%, 4 processes: 1%
// When on the "release" channel, getAddonsDisqualifyForMulti
// will return true if any addon installed is not a web extension.
// Therefore, this returns true if and only if all addons
// installed are web extensions or if no addons are installed
// at all.
get addonsDisableExperiment() { return getAddonsDisqualifyForMulti(); } }
// We don't want to allow users with any extension
// (webextension or otherwise in the experiment). prefix will
// be non-empty if there is any addon.
addonsDisableExperiment(prefix) { return !!prefix; } }
};
const ADDON_ROLLOUT_POLICY = {
@ -183,7 +185,7 @@ function defineCohort() {
// the default number of content processes (1 on beta) but still in the
// test cohort.
if (!(updateChannel in MULTI_EXPERIMENT) ||
MULTI_EXPERIMENT[updateChannel].addonsDisableExperiment ||
MULTI_EXPERIMENT[updateChannel].addonsDisableExperiment(cohortPrefix) ||
!eligibleForMulti ||
userOptedIn.multi ||
disqualified) {

View File

@ -10,7 +10,7 @@
<Description about="urn:mozilla:install-manifest">
<em:id>e10srollout@mozilla.org</em:id>
<em:version>1.80</em:version>
<em:version>1.85</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:multiprocessCompatible>true</em:multiprocessCompatible>

View File

@ -105,21 +105,24 @@ AutofillProfileAutoCompleteSearch.prototype = {
return;
}
this._getAddresses({info, searchString}).then((addresses) => {
let collectionName = FormAutofillUtils.isAddressField(info.fieldName) ?
"addresses" : "creditCards";
this._getRecords({collectionName, info, searchString}).then((records) => {
if (this.forceStop) {
return;
}
// Sort addresses by timeLastUsed for showing the lastest used address at top.
addresses.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
let handler = FormAutofillContent.getFormHandler(focusedInput);
let adaptedAddresses = handler.getAdaptedProfiles(addresses);
let adaptedRecords = handler.getAdaptedProfiles(records);
let allFieldNames = FormAutofillContent.getAllFieldNames(focusedInput);
let result = new ProfileAutoCompleteResult(searchString,
info.fieldName,
allFieldNames,
adaptedAddresses,
adaptedRecords,
{});
listener.onSearchResult(this, result);
@ -136,27 +139,29 @@ AutofillProfileAutoCompleteSearch.prototype = {
},
/**
* Get the address data from parent process for AutoComplete result.
* Get the records from parent process for AutoComplete result.
*
* @private
* @param {Object} data
* Parameters for querying the corresponding result.
* @param {string} data.collectionName
* The name used to specify which collection to retrieve records.
* @param {string} data.searchString
* The typed string for filtering out the matched address.
* The typed string for filtering out the matched records.
* @param {string} data.info
* The input autocomplete property's information.
* @returns {Promise}
* Promise that resolves when addresses returned from parent process.
*/
_getAddresses(data) {
this.log.debug("_getAddresses with data:", data);
_getRecords(data) {
this.log.debug("_getRecords with data:", data);
return new Promise((resolve) => {
Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
resolve(result.data);
});
Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", data);
Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
});
},
};

View File

@ -80,7 +80,7 @@ FormAutofillParent.prototype = {
async init() {
Services.obs.addObserver(this, "advanced-pane-loaded");
Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
Services.ppmm.addMessageListener("FormAutofill:GetAddresses", this);
Services.ppmm.addMessageListener("FormAutofill:GetRecords", this);
Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
Services.ppmm.addMessageListener("FormAutofill:OpenPreferences", this);
@ -181,8 +181,8 @@ FormAutofillParent.prototype = {
this.profileStorage.initialize();
break;
}
case "FormAutofill:GetAddresses": {
this._getAddresses(data, target);
case "FormAutofill:GetRecords": {
this._getRecords(data, target);
break;
}
case "FormAutofill:SaveAddress": {
@ -217,7 +217,7 @@ FormAutofillParent.prototype = {
this.profileStorage._saveImmediately();
Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
Services.ppmm.removeMessageListener("FormAutofill:GetAddresses", this);
Services.ppmm.removeMessageListener("FormAutofill:GetRecords", this);
Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
Services.obs.removeObserver(this, "advanced-pane-loaded");
@ -225,27 +225,32 @@ FormAutofillParent.prototype = {
},
/**
* Get the address data from profile store and return addresses back to content
* Get the records from profile store and return results back to content
* process.
*
* @private
* @param {string} data.collectionName
* The name used to specify which collection to retrieve records.
* @param {string} data.searchString
* The typed string for filtering out the matched address.
* The typed string for filtering out the matched records.
* @param {string} data.info
* The input autocomplete property's information.
* @param {nsIFrameMessageManager} target
* Content's message manager.
*/
_getAddresses({searchString, info}, target) {
let addresses = [];
_getRecords({collectionName, searchString, info}, target) {
let records;
let collection = this.profileStorage[collectionName];
if (info && info.fieldName) {
addresses = this.profileStorage.addresses.getByFilter({searchString, info});
if (!collection) {
records = [];
} else if (info && info.fieldName) {
records = collection.getByFilter({searchString, info});
} else {
addresses = this.profileStorage.addresses.getAll();
records = collection.getAll();
}
target.sendAsyncMessage("FormAutofill:Addresses", addresses);
target.sendAsyncMessage("FormAutofill:Records", records);
},
_updateSavedFieldNames() {
@ -256,12 +261,14 @@ FormAutofillParent.prototype = {
Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
}
this.profileStorage.addresses.getAll().forEach((address) => {
Object.keys(address).forEach((fieldName) => {
if (!address[fieldName]) {
return;
}
Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
["addresses", "creditCards"].forEach(c => {
this.profileStorage[c].getAll().forEach((record) => {
Object.keys(record).forEach((fieldName) => {
if (!record[fieldName]) {
return;
}
Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
});
});
});
@ -299,9 +306,13 @@ FormAutofillParent.prototype = {
}
changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
});
// Address should be updated
Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill_update", 1);
return;
}
this.profileStorage.addresses.notifyUsed(address.guid);
// Address is merged successfully
Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill", 1);
} else {
let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
if (!changedGUIDs.length) {
@ -320,6 +331,9 @@ FormAutofillParent.prototype = {
target.ownerGlobal.openPreferences("panePrivacy",
{origin: "autofillDoorhanger"});
});
} else {
// We want to exclude the first time form filling.
Services.telemetry.scalarAdd("formautofill.addresses.fill_type_manual", 1);
}
}
},

View File

@ -63,7 +63,7 @@ ManageProfileDialog.prototype = {
* @returns {promise}
*/
loadAddresses() {
return this.getAddresses().then(addresses => {
return this.getRecords({collectionName: "addresses"}).then(addresses => {
log.debug("addresses:", addresses);
// Sort by last modified time starting with most recent
addresses.sort((a, b) => b.timeLastModified - a.timeLastModified);
@ -73,17 +73,27 @@ ManageProfileDialog.prototype = {
},
/**
* Get addresses from storage.
* Get records from storage.
*
* @returns {promise}
* @private
* @param {Object} data
* Parameters for querying the corresponding result.
* @param {string} data.collectionName
* The name used to specify which collection to retrieve records.
* @param {string} data.searchString
* The typed string for filtering out the matched records.
* @param {string} data.info
* The input autocomplete property's information.
* @returns {Promise}
* Promise that resolves when addresses returned from parent process.
*/
getAddresses() {
getRecords(data) {
return new Promise(resolve => {
Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
resolve(result.data);
});
Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {});
Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
});
},

View File

@ -9,10 +9,10 @@ const TEST_SELECTORS = {
const DIALOG_SIZE = "width=600,height=400";
function waitForAddresses() {
function waitForRecords() {
return new Promise(resolve => {
Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
// Wait for the next tick for elements to get rendered.
SimpleTest.executeSoon(resolve.bind(null, result.data));
});
@ -54,7 +54,7 @@ add_task(async function test_removingSingleAndMultipleProfiles() {
await saveAddress(TEST_ADDRESS_3);
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL, null, DIALOG_SIZE);
await waitForAddresses();
await waitForRecords();
let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
@ -66,7 +66,7 @@ add_task(async function test_removingSingleAndMultipleProfiles() {
is(btnRemove.disabled, false, "Remove button enabled");
is(btnEdit.disabled, false, "Edit button enabled");
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
await waitForAddresses();
await waitForRecords();
is(selAddresses.length, 2, "Two addresses left");
EventUtils.synthesizeMouseAtCenter(selAddresses.children[0], {}, win);
@ -75,7 +75,7 @@ add_task(async function test_removingSingleAndMultipleProfiles() {
is(btnEdit.disabled, true, "Edit button disabled when multi-select");
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
await waitForAddresses();
await waitForRecords();
is(selAddresses.length, 0, "All addresses are removed");
win.close();
@ -83,16 +83,16 @@ add_task(async function test_removingSingleAndMultipleProfiles() {
add_task(async function test_profilesDialogWatchesStorageChanges() {
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL, null, DIALOG_SIZE);
await waitForAddresses();
await waitForRecords();
let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
await saveAddress(TEST_ADDRESS_1);
let addresses = await waitForAddresses();
let addresses = await waitForRecords();
is(selAddresses.length, 1, "One address is shown");
await removeAddresses([addresses[0].guid]);
await waitForAddresses();
await waitForRecords();
is(selAddresses.length, 0, "Address is removed");
win.close();
});

View File

@ -64,16 +64,20 @@ async function openPopupOn(browser, selector) {
await expectPopupOpen(browser);
}
function getAddresses() {
function getRecords(data) {
return new Promise(resolve => {
Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
resolve(result.data);
});
Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {});
Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
});
}
function getAddresses() {
return getRecords({collectionName: "addresses"});
}
function saveAddress(address) {
Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", {address});
return TestUtils.topicObserved("formautofill-storage-changed");

View File

@ -11,15 +11,15 @@ let {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {
var ParentUtils = {
cleanUpAddress() {
Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
let addresses = result.data;
Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses",
{guids: addresses.map(address => address.guid)});
});
Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {searchString: ""});
Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", {searchString: "", collectionName: "addresses"});
},
updateAddress(type, chromeMsg, msgData, contentMsg) {
@ -60,8 +60,8 @@ var ParentUtils = {
},
checkAddresses({expectedAddresses}) {
Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
let addresses = result.data;
if (addresses.length !== expectedAddresses.length) {
sendAsyncMessage("FormAutofillTest:areAddressesMatching", false);
@ -82,7 +82,7 @@ var ParentUtils = {
sendAsyncMessage("FormAutofillTest:areAddressesMatching", true);
});
Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {searchString: ""});
Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", {searchString: "", collectionName: "addresses"});
},
};

View File

@ -0,0 +1,50 @@
/*
* Test for make sure getRecords can retrieve right collection from storag.
*/
"use strict";
Cu.import("resource://formautofill/FormAutofillParent.jsm");
Cu.import("resource://formautofill/ProfileStorage.jsm");
add_task(async function test_getRecords() {
let formAutofillParent = new FormAutofillParent();
await formAutofillParent.init();
await formAutofillParent.profileStorage.initialize();
let fakeResult = {
addresses: [{
"given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
"organization": "World Wide Web Consortium",
}],
creditCards: [{
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
}],
};
["addresses", "creditCards", "nonExisting"].forEach(collectionName => {
let collection = profileStorage[collectionName];
let expectedResult = fakeResult[collectionName] || [];
let target = {
sendAsyncMessage: function sendAsyncMessage(msg, payload) {},
};
let mock = sinon.mock(target);
mock.expects("sendAsyncMessage").once().withExactArgs("FormAutofill:Records", expectedResult);
if (collection) {
sinon.stub(collection, "getAll");
collection.getAll.returns(expectedResult);
}
formAutofillParent._getRecords({collectionName}, target);
mock.verify();
if (collection) {
do_check_eq(collection.getAll.called, true);
}
});
});

View File

@ -27,6 +27,7 @@ support-files =
[test_getCategoriesFromFieldNames.js]
[test_getFormInputDetails.js]
[test_getInfo.js]
[test_getRecords.js]
[test_isCJKName.js]
[test_isFieldEligibleForAutofill.js]
[test_markAsAutofillField.js]

View File

@ -13,10 +13,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
const BROWSER_READY_NOTIFICATION = "final-ui-startup";
const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished";
const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored";
const PREF_WHITELIST = [
"browser.onboarding.enabled",
"browser.onboarding.hidden",
@ -27,12 +28,14 @@ const PREF_WHITELIST = [
];
[
"onboarding-tour-private-browsing",
"onboarding-tour-addons",
"onboarding-tour-customize",
"onboarding-tour-default-browser",
"onboarding-tour-library",
"onboarding-tour-performance",
"onboarding-tour-private-browsing",
"onboarding-tour-search",
"onboarding-tour-singlesearch",
"onboarding-tour-sync",
].forEach(tourId => PREF_WHITELIST.push(`browser.onboarding.tour.${tourId}.completed`));
@ -69,6 +72,54 @@ function initContentMessageListener() {
});
}
let syncTourChecker = {
registered: false,
observe() {
this.setComplete();
},
init() {
if (Services.prefs.getBoolPref("browser.onboarding.tour.onboarding-tour-sync.completed", false)) {
return;
}
// Check if we've already logged in at startup.
fxAccounts.getSignedInUser().then(user => {
if (user) {
this.setComplete();
return;
}
// Observe for login action if we haven't logged in yet.
this.register();
});
},
register() {
if (this.registered) {
return;
}
Services.obs.addObserver(this, "fxaccounts:onverified");
this.registered = true;
},
setComplete() {
Services.prefs.setBoolPref("browser.onboarding.tour.onboarding-tour-sync.completed", true);
this.unregister();
},
unregister() {
if (!this.registered) {
return;
}
Services.obs.removeObserver(this, "fxaccounts:onverified");
this.registered = false;
},
uninit() {
this.unregister();
},
}
/**
* onBrowserReady - Continues startup of the add-on after browser is ready.
*/
@ -87,8 +138,15 @@ function observe(subject, topic, data) {
switch (topic) {
case BROWSER_READY_NOTIFICATION:
Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION);
// Avoid running synchronously during this event that's used for timing
setTimeout(() => onBrowserReady());
onBrowserReady();
break;
case BROWSER_SESSION_STORE_NOTIFICATION:
Services.obs.removeObserver(observe, BROWSER_SESSION_STORE_NOTIFICATION);
// Postpone Firefox account checking until "before handling user events"
// phase to meet performance criteria. The reason we don't postpone the
// whole onBrowserReady here is because in that way we will miss onload
// events for onboarding.js.
Services.tm.idleDispatchToMainThread(() => syncTourChecker.init());
break;
}
}
@ -101,8 +159,10 @@ function startup(aData, aReason) {
// Only start Onboarding when the browser UI is ready
if (aReason === APP_STARTUP || aReason === ADDON_INSTALL) {
Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION);
Services.obs.addObserver(observe, BROWSER_SESSION_STORE_NOTIFICATION);
} else {
onBrowserReady();
syncTourChecker.init();
}
}
@ -111,4 +171,5 @@ function shutdown(aData, aReason) {
if (waitingForBrowserReady) {
Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION);
}
syncTourChecker.uninit();
}

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 92 92" xmlns="http://www.w3.org/2000/svg"><title>Tip / Icon / Performance</title><g id="Symbols" fill="none" fill-rule="evenodd"><g id="Tip-/-Icon-/-Performance" fill="#0A84FF"><path d="M47.237 53.003L47 53c-6.075 0-11 4.925-11 11s4.925 11 11 11c4.453 0 8.287-2.645 10.018-6.45 1.888-3.525 2.97-7.84 8.397-12.066 7.422-5.778 15.097-10.033 14.61-11.098-.485-1.06-8.04 2.724-16.675 5.79-7.916 2.812-14.765 1.706-16.113 1.827zm-36.467 31.3C4.05 76.316 0 66.015 0 54.768 0 29.4 20.593 8.838 46 8.838c25.404 0 46 20.563 46 45.93 0 11.247-4.05 21.548-10.77 29.535H10.77zM46 21.698c-1.016 0-1.84 1.646-1.84 3.674s.824 3.675 1.84 3.675 1.84-1.647 1.84-3.675c0-2.028-.824-3.674-1.84-3.674zm34.96 40.418c0-1.016-1.65-1.837-3.68-1.837-2.03 0-3.68.82-3.68 1.836s1.65 1.837 3.68 1.837c2.03 0 3.68-.82 3.68-1.837zm-62.364 0c0-1.016-1.65-1.837-3.68-1.837-2.032 0-3.68.82-3.68 1.836s1.648 1.837 3.68 1.837c2.03 0 3.68-.82 3.68-1.837zm7.664-23.133c.73-.706.18-2.46-1.232-3.92s-3.15-2.072-3.88-1.366c-.73.704-.178 2.458 1.234 3.92 1.41 1.457 3.148 2.07 3.878 1.366zm46-5.287c-.73-.706-2.468-.094-3.88 1.367-1.41 1.46-1.962 3.215-1.232 3.92.73.704 2.47.092 3.88-1.368 1.412-1.46 1.964-3.214 1.233-3.92z" id="Combined-Shape"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 92 92" xmlns="http://www.w3.org/2000/svg"><title>Tip / Icon / Performance</title><g id="Symbols" fill="none" fill-rule="evenodd"><g id="Tip-/-Icon-/-Performance" fill="#3E3D40"><path d="M47.237 53.003L47 53c-6.075 0-11 4.925-11 11s4.925 11 11 11c4.453 0 8.287-2.645 10.018-6.45 1.888-3.525 2.97-7.84 8.397-12.066 7.422-5.778 15.097-10.033 14.61-11.098-.485-1.06-8.04 2.724-16.675 5.79-7.916 2.812-14.765 1.706-16.113 1.827zm-36.467 31.3C4.05 76.316 0 66.015 0 54.768 0 29.4 20.593 8.838 46 8.838c25.404 0 46 20.563 46 45.93 0 11.247-4.05 21.548-10.77 29.535H10.77zM46 21.698c-1.016 0-1.84 1.646-1.84 3.674s.824 3.675 1.84 3.675 1.84-1.647 1.84-3.675c0-2.028-.824-3.674-1.84-3.674zm34.96 40.418c0-1.016-1.65-1.837-3.68-1.837-2.03 0-3.68.82-3.68 1.836s1.65 1.837 3.68 1.837c2.03 0 3.68-.82 3.68-1.837zm-62.364 0c0-1.016-1.65-1.837-3.68-1.837-2.032 0-3.68.82-3.68 1.836s1.648 1.837 3.68 1.837c2.03 0 3.68-.82 3.68-1.837zm7.664-23.133c.73-.706.18-2.46-1.232-3.92s-3.15-2.072-3.88-1.366c-.73.704-.178 2.458 1.234 3.92 1.41 1.457 3.148 2.07 3.878 1.366zm46-5.287c-.73-.706-2.468-.094-3.88 1.367-1.41 1.46-1.962 3.215-1.232 3.92.73.704 2.47.092 3.88-1.368 1.412-1.46 1.964-3.214 1.233-3.92z" id="Combined-Shape"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -38,6 +38,9 @@ document.getElementById("onboarding-overlay")
case "onboarding-tour-search-button":
Mozilla.UITour.openSearchPanel(() => {});
break;
case "onboarding-tour-singlesearch-button":
Mozilla.UITour.showMenu("urlbar");
break;
case "onboarding-tour-sync-button":
let emailInput = document.getElementById("onboarding-tour-sync-email-input");
if (emailInput.checkValidity()) {

View File

@ -23,30 +23,35 @@
display: block;
}
#onboarding-overlay-icon {
width: 36px;
height: 29px;
#onboarding-overlay-button {
position: absolute;
cursor: pointer;
top: 30px;
offset-inline-start: 30px;
background: url("img/overlay-icon.svg") no-repeat;
border: none;
/* Set to none so no grey contrast background in the high-contrast mode */
background: none;
}
#onboarding-overlay-button-icon {
width: 36px;
}
#onboarding-notification-icon::after,
#onboarding-overlay-icon::after {
#onboarding-overlay-button::after {
background: #5ce6e6;
position: absolute;
font-size: 12px;
border: 1px solid #fff;
text-align: center;
color: #10404a;
box-sizing: content-box;
}
#onboarding-overlay-icon::after {
#onboarding-overlay-button::after {
content: attr(aria-label);
top: -6px;
offset-inline-start: 32px;
offset-inline-start: 39px;
border-radius: 22px;
padding: 5px 8px;
min-width: 100px;
@ -57,29 +62,35 @@
display: none;
}
#onboarding-overlay-close-btn,
#onboarding-notification-close-btn {
position: absolute;
top: 15px;
offset-inline-end: 15px;
cursor: pointer;
width: 16px;
height: 16px;
background-image: url(chrome://browser/skin/sidebar/close.svg);
background-position: center center;
background-repeat: no-repeat;
padding: 12px;
.onboarding-close-btn {
position: absolute;
top: 15px;
offset-inline-end: 15px;
cursor: pointer;
width: 16px;
height: 16px;
padding: 12px;
border: none;
background: var(--onboarding-overlay-dialog-background-color);
}
.onboarding-close-btn::before {
content: url(chrome://browser/skin/sidebar/close.svg);
display: block;
margin-top: -8px;
margin-inline-start: -8px;
}
#onboarding-overlay-close-btn:hover,
.onboarding-close-btn:hover,
#onboarding-notification-close-btn:hover {
background-color: rgba(204, 204, 204, 0.6);
}
#onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog {
--onboarding-overlay-dialog-background-color: #f5f5f7;
width: 960px;
height: 510px;
background: #f5f5f7;
background: var(--onboarding-overlay-dialog-background-color);
border: 1px solid rgba(9, 6, 13, 0.1); /* #09060D, 0.1 opacity */
border-radius: 3px;
position: relative;
@ -135,10 +146,13 @@
}
#onboarding-tour-list > li {
--padding-inline-start: 49px;
--padding-top: 14px;
--padding-bottom: 14px;
list-style: none;
padding-inline-start: 49px;
padding-top: 14px;
padding-bottom: 14px;
padding-inline-start: var(--padding-inline-start);
padding-top: var(--padding-top);
padding-bottom: var(--padding-bottom);
margin-inline-start: 16px;
margin-bottom: 9px;
background-repeat: no-repeat;
@ -161,12 +175,19 @@
}
#onboarding-tour-list > li.onboarding-complete {
padding-inline-start: 29px;
--padding-inline-start: 29px;
}
#onboarding-tour-list > li.onboarding-active,
#onboarding-tour-list > li:hover {
color: #0A84FF;
/* With 1px transparent border, could see a border in the high-constrast mode */
border: 1px solid transparent;
/* Substract 1px for the 1px transparent or a 1px shift would happen */
padding-inline-start: calc(var(--padding-inline-start) - 1px);
padding-top: calc(var(--padding-top) - 1px);
padding-bottom: calc(var(--padding-bottom) - 1px);
background-color: #fff;
}
/* Default browser tour */
@ -283,7 +304,8 @@
font-weight: 600;
line-height: 21px;
background: #0a84ff;
border: none;
/* With 1px transparent border, could see a border in the high-constrast mode */
border: 1px solid transparent;
border-radius: 0;
color: #fff;
float: inline-end;
@ -307,16 +329,20 @@
}
/* Tour Icons */
#onboarding-tour-search {
#onboarding-tour-search,
#onboarding-tour-singlesearch {
background-image: url("img/icons_search.svg");
}
#onboarding-tour-search.onboarding-active,
#onboarding-tour-search:hover {
#onboarding-tour-search:hover,
#onboarding-tour-singlesearch.onboarding-active,
#onboarding-tour-singlesearch:hover {
background-image: url("img/icons_search-colored.svg");
}
#onboarding-notification-bar[data-target-tour-id=onboarding-tour-search] #onboarding-notification-tour-icon {
#onboarding-notification-bar[data-target-tour-id=onboarding-tour-search] #onboarding-notification-tour-icon,
#onboarding-notification-bar[data-target-tour-id=onboarding-tour-singlesearch] #onboarding-notification-tour-icon {
background-image: url("img/icons_search-notification.svg");
}
@ -396,8 +422,24 @@
background-image: url("img/icons_search-colored.svg");
}
#onboarding-tour-performance {
background-image: url("img/icons_performance.svg");
}
#onboarding-tour-performance.onboarding-active,
#onboarding-tour-performance:hover {
background-image: url("img/icons_performance-colored.svg");
}
#onboarding-notification-bar[data-target-tour-id=onboarding-tour-performance] #onboarding-notification-tour-icon {
/* TODO: Placeholder icon. It should be replaced upon assets are available.
This is tracking in Bug 1382520. */
background-image: url("img/icons_sync-notification.svg");
}
/* Tour Notifications */
#onboarding-notification-bar {
--onboarding-notification-bar-background-color: rgba(255, 255, 255, 0.97);
position: fixed;
z-index: 20998; /* We want this always under #onboarding-overlay */
left: 0;
@ -405,7 +447,7 @@
width: 100%;
height: 122px;
min-width: 640px;
background: rgba(255, 255, 255, 0.97);
background: var(--onboarding-notification-bar-background-color);
border-top: 2px solid #e9e9e9;
transition: transform 0.8s;
transform: translateY(122px);
@ -436,15 +478,14 @@
--vpadding: 3px;
content: attr(data-tooltip);
top: 0;
offset-inline-start: 68px;
offset-inline-start: 73px;
line-height: calc(var(--height) - var(--vpadding) * 2);
border-radius: calc(var(--height) / 2);
padding: var(--vpadding) 10px;
}
#onboarding-notification-close-btn {
background-color: rgba(255, 255, 255, 0.97);
border: none;
background: var(--onboarding-notification-bar-background-color);
position: absolute;
offset-block-start: 50%;
offset-inline-end: 34px;
@ -489,7 +530,8 @@
#onboarding-notification-action-btn {
background: #0a84ff;
border: none;
/* With 1px transparent border, could see a border in the high-constrast mode */
border: 1px solid transparent;
border-radius: 0;
padding: 10px 20px;
font-size: 14px;

View File

@ -250,6 +250,59 @@ var onboardingTourset = {
return div;
},
},
"singlesearch": {
id: "onboarding-tour-singlesearch",
tourNameId: "onboarding.tour-singlesearch",
getNotificationStrings(bundle) {
return {
title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-singlesearch.title"),
message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-singlesearch.message"),
button: bundle.GetStringFromName("onboarding.button.learnMore"),
};
},
getPage(win, bundle) {
let div = win.document.createElement("div");
div.innerHTML = `
<section class="onboarding-tour-description">
<h1 data-l10n-id="onboarding.tour-singlesearch.title"></h1>
<p data-l10n-id="onboarding.tour-singlesearch.description"></p>
</section>
<section class="onboarding-tour-content">
<img src="resource://onboarding/img/figure_search.svg" role="presentation"/>
</section>
<aside class="onboarding-tour-button-container">
<button id="onboarding-tour-singlesearch-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-singlesearch.button"></button>
</aside>
`;
return div;
},
},
"performance": {
id: "onboarding-tour-performance",
tourNameId: "onboarding.tour-performance",
getNotificationStrings(bundle) {
return {
title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-performance.title"),
message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-performance.message", [BRAND_SHORT_NAME], 1),
button: bundle.GetStringFromName("onboarding.button.learnMore"),
};
},
getPage(win, bundle) {
let div = win.document.createElement("div");
// TODO: The content image is a placeholder. It should be replaced upon assets are available.
// This is tracking in Bug 1382520.
div.innerHTML = `
<section class="onboarding-tour-description">
<h1 data-l10n-id="onboarding.tour-performance.title"></h1>
<p data-l10n-id="onboarding.tour-performance.description"></p>
</section>
<section class="onboarding-tour-content">
<img src="resource://onboarding/img/figure_sync.svg" role="presentation"/>
</section>
`;
return div;
},
},
};
/**
@ -264,8 +317,9 @@ class Onboarding {
async init(contentWindow) {
this._window = contentWindow;
this._tours = [];
this._tourType = Services.prefs.getStringPref("browser.onboarding.tour-type", "update");
let tourIds = this._getTourIDList(Services.prefs.getStringPref("browser.onboarding.tour-type", "update"));
let tourIds = this._getTourIDList();
tourIds.forEach(tourId => {
if (onboardingTourset[tourId]) {
this._tours.push(onboardingTourset[tourId]);
@ -311,7 +365,7 @@ class Onboarding {
this._tourItems = [];
this._tourPages = [];
this._overlayIcon = this._renderOverlayIcon();
this._overlayIcon = this._renderOverlayButton();
this._overlayIcon.addEventListener("click", this);
this._window.document.body.appendChild(this._overlayIcon);
@ -326,8 +380,8 @@ class Onboarding {
this._window.requestIdleCallback(() => this._initNotification());
}
_getTourIDList(tourType) {
let tours = Services.prefs.getStringPref(`browser.onboarding.${tourType}tour`, "");
_getTourIDList() {
let tours = Services.prefs.getStringPref(`browser.onboarding.${this._tourType}tour`, "");
return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
}
@ -400,7 +454,7 @@ class Onboarding {
}
switch (evt.target.id) {
case "onboarding-overlay-icon":
case "onboarding-overlay-button":
case "onboarding-overlay-close-btn":
// If the clicking target is directly on the outer-most overlay,
// that means clicking outside the tour content area.
@ -418,6 +472,12 @@ class Onboarding {
this.gotoPage(tourId);
this._removeTourFromNotificationQueue(tourId);
break;
// These tours are tagged completed instantly upon showing.
case "onboarding-tour-default-browser":
case "onboarding-tour-sync":
case "onboarding-tour-performance":
this.setToursCompleted([ evt.target.id ]);
break;
}
let classList = evt.target.classList;
if (classList.contains("onboarding-tour-item")) {
@ -679,9 +739,12 @@ class Onboarding {
</div>
<button id="onboarding-notification-action-btn"></button>
</section>
<button id="onboarding-notification-close-btn"></button>
<button id="onboarding-notification-close-btn" class="onboarding-close-btn"></button>
`;
let toolTip = this._bundle.formatStringFromName("onboarding.notification-icon-tool-tip", [BRAND_SHORT_NAME], 1);
let toolTip = this._bundle.formatStringFromName(
this._tourType === "new" ? "onboarding.notification-icon-tool-tip" :
"onboarding.notification-icon-tooltip-updated",
[BRAND_SHORT_NAME], 1);
div.querySelector("#onboarding-notification-icon").setAttribute("data-tooltip", toolTip);
return div;
}
@ -707,7 +770,7 @@ class Onboarding {
// The security should be fine because this is not from an external input.
div.innerHTML = `
<div id="onboarding-overlay-dialog">
<span id="onboarding-overlay-close-btn"></span>
<button id="onboarding-overlay-close-btn" class="onboarding-close-btn"></button>
<header id="onboarding-header"></header>
<nav>
<ul id="onboarding-tour-list"></ul>
@ -725,12 +788,18 @@ class Onboarding {
return div;
}
_renderOverlayIcon() {
let img = this._window.document.createElement("div");
let tooltip = this._bundle.formatStringFromName("onboarding.overlay-icon-tooltip", [BRAND_SHORT_NAME], 1);
img.setAttribute("aria-label", tooltip);
img.id = "onboarding-overlay-icon";
return img;
_renderOverlayButton() {
let button = this._window.document.createElement("button");
let tooltipStringId = this._tourType === "new" ?
"onboarding.overlay-icon-tooltip" : "onboarding.overlay-icon-tooltip-updated";
let tooltip = this._bundle.formatStringFromName(tooltipStringId, [BRAND_SHORT_NAME], 1);
button.setAttribute("aria-label", tooltip);
button.id = "onboarding-overlay-button";
let img = this._window.document.createElement("img");
img.id = "onboarding-overlay-button-icon";
img.src = "resource://onboarding/img/overlay-icon.svg";
button.appendChild(img);
return button;
}
_loadTours(tours) {

View File

@ -10,6 +10,9 @@ onboarding.button.learnMore=Learn More
onboarding.notification-icon-tool-tip=New to %S?
# LOCALIZATION NOTE(onboarding.overlay-icon-tooltip): This string will be used to show the tooltip alongside the notification icon in the overlay tour. %S is brandShortName.
onboarding.overlay-icon-tooltip=New to %S? Lets get started.
# LOCALIZATION NOTE(onboarding.overlay-icon-tooltip-updated): %S is brandShortName.
onboarding.overlay-icon-tooltip-updated=%S is all new. See what you can do!
onboarding.notification-icon-tooltip-updated=See whats new!
onboarding.tour-search2=Search
onboarding.tour-search.title2=Find it faster.
@ -95,3 +98,19 @@ onboarding.tour-library.button=Show Library in Menu
onboarding.notification.onboarding-tour-library.title=Keep it together.
# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-library.message): This string will be used in the notification message for the library tour. %S is brandShortName
onboarding.notification.onboarding-tour-library.message=The new %S library puts the great things youve discovered on the web in one convenient place.
onboarding.tour-singlesearch=Address Bar
onboarding.tour-singlesearch.title=Find it faster.
# LOCALIZATION NOTE(onboarding.tour-singlesearch.description): %S is brandShortName
onboarding.tour-singlesearch.description=The address bar might be the most powerful tool in the sleek new %S toolbar. Start typing, and see suggestions based on your browsing and search history. Go to a web address, search the whole web with your default search engine, or send your query directly to a single site with one-click search.
onboarding.tour-singlesearch.button=Show Address Bar
onboarding.notification.onboarding-tour-singlesearch.title=Find it faster.
onboarding.notification.onboarding-tour-singlesearch.message=The unified address bar is the only tool you need to find your way around the web.
onboarding.tour-performance=Performance
onboarding.tour-performance.title=Browse with the best of em.
# LOCALIZATION NOTE(onboarding.tour-performance.description): %1$S is brandShortName.
onboarding.tour-performance.description=Its a whole new %1$S, built for faster page loading, smoother scrolling, and more responsive tab switching. These performance upgrades come paired with a modern, intuitive design. Start browsing and experience it for yourself: the best %1$S yet.
onboarding.notification.onboarding-tour-performance.title=Browse with the best of em.
# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-performance.message): %S is brandShortName.
onboarding.notification.onboarding-tour-performance.message=Prepare yourself for the fastest, smoothest, most reliable %S yet.

View File

@ -7,7 +7,7 @@ function assertOnboardingDestroyed(browser) {
return ContentTask.spawn(browser, {}, function() {
let expectedRemovals = [
"#onboarding-overlay",
"#onboarding-overlay-icon"
"#onboarding-overlay-button"
];
for (let selector of expectedRemovals) {
let removal = content.document.querySelector(selector);
@ -36,7 +36,7 @@ add_task(async function test_hide_onboarding_tours() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
tabs.push(tab);
}
@ -67,7 +67,7 @@ add_task(async function test_click_action_button_to_set_tour_completed() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
tabs.push(tab);
}
@ -101,7 +101,7 @@ add_task(async function test_set_right_tour_completed_style_on_overlay() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
tabs.push(tab);
}

View File

@ -10,7 +10,7 @@ add_task(async function test_onboarding_default_new_tourset() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
tabs.push(tab);
}
@ -48,7 +48,7 @@ add_task(async function test_onboarding_custom_new_tourset() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
tabs.push(tab);
}
@ -85,7 +85,7 @@ add_task(async function test_onboarding_custom_update_tourset() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
tabs.push(tab);
}

View File

@ -17,7 +17,7 @@
:root {
%ifdef MOZ_PHOTON_THEME
--toolbarbutton-border-radius: 2px;
--toolbarbutton-border-radius: 4px;
%else
--toolbarbutton-border-radius: 1px;

View File

@ -1570,7 +1570,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
%ifdef MOZ_PHOTON_THEME
:root:-moz-any([inFullscreen], [tabsintitlebar]):not([customizing]) #TabsToolbar:not(:-moz-lwtheme) {
:root:-moz-any([inFullscreen], [tabsintitlebar]) #TabsToolbar:not(:-moz-lwtheme) {
-moz-appearance: none;
background-color: #232323;
color: hsl(240, 9%, 98%);

View File

@ -580,9 +580,7 @@ toolbarpaletteitem[place=toolbar] > toolbarspring {
#customization-panelWrapper > .panel-arrowbox {
position: relative;
height: 10px;
%ifndef XP_MACOSX
margin-bottom: 2px;
%endif
margin-bottom: -1px;
}
#customization-panelWrapper > .panel-arrowbox > .panel-arrow[side="top"] {
@ -614,6 +612,7 @@ toolbarpaletteitem[place=toolbar] > toolbarspring {
* 10px.
*/
margin-inline-end: calc(4px + 3 * var(--toolbarbutton-inner-padding));
vertical-align: top;
}
%ifdef XP_MACOSX

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -47,7 +47,7 @@ toolbar[brighttext] .toolbarbutton-1 {
transform: translateX(0);
}
to {
transform: translateX(-738px);
transform: translateX(-450px);
}
}
@ -56,7 +56,7 @@ toolbar[brighttext] .toolbarbutton-1 {
transform: scaleX(-1) translateX(0);
}
to {
transform: scaleX(-1) translateX(-738px);
transform: scaleX(-1) translateX(-450px);
}
}
@ -65,7 +65,7 @@ toolbar[brighttext] .toolbarbutton-1 {
transform: translateX(0);
}
to {
transform: translateX(-612px);
transform: translateX(-450px);
}
}
@ -74,15 +74,20 @@ toolbar[brighttext] .toolbarbutton-1 {
transform: scaleX(-1) translateX(0);
}
to {
transform: scaleX(-1) translateX(-612px);
transform: scaleX(-1) translateX(-450px);
}
}
#stop-reload-button[animate] > #reload-button,
#stop-reload-button[animate] > #stop-button {
position: relative;
}
#reload-button > .toolbarbutton-animatable-box,
#stop-button > .toolbarbutton-animatable-box {
position: fixed;
position: absolute;
overflow: hidden;
margin-top: -10px; /* Vertically center the 20px tall animatable image */
top: calc(50% - 10px); /* Vertically center the 20px tall animatable image */
/* Since .toolbarbutton-icon uses a different width than the animatable-box,
we need to set a padding relative to the difference in widths. */
margin-inline-start: calc((16px + 2 * var(--toolbarbutton-inner-padding) - 18px) / 2);
@ -98,7 +103,7 @@ toolbar[brighttext] .toolbarbutton-1 {
#reload-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image,
#stop-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
height: 20px; /* Height of each frame within the SVG sprite */
height: var(--toolbarbutton-height); /* Height must be equal to height of toolbarbutton padding-box */
animation-fill-mode: forwards;
animation-iteration-count: 1;
list-style-image: none;
@ -106,7 +111,7 @@ toolbar[brighttext] .toolbarbutton-1 {
#stop-reload-button[animate] > #reload-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
background-image: url("chrome://browser/skin/reload-to-stop.svg");
width: 756px;
width: 468px;
}
#stop-reload-button[animate] > #reload-button:not([displaystop]) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
@ -118,13 +123,13 @@ toolbar[brighttext] .toolbarbutton-1 {
}
#reload-button:not([displaystop]) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
animation-timing-function: steps(41);
animation-duration: 684ms;
animation-timing-function: steps(25);
animation-duration: 400ms;
}
#stop-reload-button[animate] > #reload-button[displaystop] + #stop-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
background-image: url("chrome://browser/skin/stop-to-reload.svg");
width: 630px;
width: 468px;
}
#stop-reload-button[animate] > #reload-button[displaystop] + #stop-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
@ -136,24 +141,24 @@ toolbar[brighttext] .toolbarbutton-1 {
}
#reload-button[displaystop] + #stop-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
animation-timing-function: steps(34);
animation-duration: 600ms;
animation-timing-function: steps(25);
animation-duration: 400ms;
}
#reload-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
transform: translateX(-738px);
transform: translateX(-450px);
}
#reload-button:-moz-locale-dir(rtl) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
transform: scaleX(-1) translateX(-738px);
transform: scaleX(-1) translateX(-450px);
}
#reload-button[displaystop] + #stop-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
transform: translateX(-612px);
transform: translateX(-450px);
}
#reload-button[displaystop] + #stop-button:-moz-locale-dir(rtl) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
transform: scaleX(-1) translateX(-612px);
transform: scaleX(-1) translateX(-450px);
}
%endif
#reload-button {

View File

@ -235,10 +235,16 @@ button {
color: gray;
}
#addons-panel h2 {
font-size: 1.5rem;
font-weight: bold;
}
.addon-target-container {
background: #fff;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.12);
list-style-type: none;
font-size: 13px;
margin: 0 0 8px;
padding: 4px 16px;
transition: box-shadow 150ms;
@ -253,7 +259,11 @@ button {
display: flex;
margin: 0;
padding: 16px 0;
font-size: 14px;
}
.addon-target-name {
font-size: 15px;
font-weight: 600;
}
.addon-target-actions {
@ -296,7 +306,7 @@ button {
background: none;
border: none;
color: #0087ff;
font-size: 14px;
font-size: 13px;
margin: 12px;
min-width: auto;
padding: 4px;

View File

@ -173,7 +173,9 @@ module.exports = createClass({
role: "presentation",
src: target.icon
}),
dom.span({ className: "target-name", title: target.name }, target.name)
dom.span(
{ className: "target-name addon-target-name", title: target.name },
target.name)
),
showMessages(target),
dom.dl(

View File

@ -78,6 +78,7 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
[browser_inspector_highlighter-cssshape_01.js]
[browser_inspector_highlighter-cssshape_02.js]
[browser_inspector_highlighter-cssshape_03.js]
[browser_inspector_highlighter-cssshape_04.js]
[browser_inspector_highlighter-csstransform_01.js]
[browser_inspector_highlighter-csstransform_02.js]
[browser_inspector_highlighter-embed.js]

View File

@ -74,7 +74,7 @@ function* ellipseHasCorrectAttrs(testActor, inspector, highlighterFront) {
is(rx, 40, "Ellipse highlighter has correct rx");
is(ry, 30, "Ellipse highlighter has correct ry");
is(cx, 25, "Ellipse highlighter has correct cx");
is(cy, 75, "Ellipse highlighter has correct cy");
is(cy, 30, "Ellipse highlighter has correct cy");
}
function* insetHasCorrectAttrs(testActor, inspector, highlighterFront) {

View File

@ -0,0 +1,232 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Test that shapes are updated correctly on mouse events.
const TEST_URL = URL_ROOT + "doc_inspector_highlighter_cssshapes.html";
const HIGHLIGHTER_TYPE = "ShapesHighlighter";
add_task(function* () {
let inspector = yield openInspectorForURL(TEST_URL);
let helper = yield getHighlighterHelperFor(HIGHLIGHTER_TYPE)(inspector);
let {testActor} = inspector;
yield testPolygonMovePoint(testActor, helper);
yield testPolygonAddPoint(testActor, helper);
yield testPolygonRemovePoint(testActor, helper);
yield testCircleMoveCenter(testActor, helper);
yield testEllipseMoveRadius(testActor, helper);
yield testInsetMoveEdges(testActor, helper);
helper.finalize();
});
function* testPolygonMovePoint(testActor, helper) {
info("Displaying polygon");
yield helper.show("#polygon", {mode: "cssClipPath"});
let { mouse, highlightedNode } = helper;
let points = yield helper.getElementAttribute("shapes-polygon", "points");
let [x, y] = points.split(" ")[0].split(",");
let quads = yield testActor.getAllAdjustedQuads("#polygon");
let { top, left, width, height } = quads.border[0].bounds;
x = left + width * x / 100;
y = top + height * y / 100;
let dx = width / 10;
let dy = height / 10;
info("Moving first polygon point");
yield mouse.down(x, y);
yield mouse.move(x + dx, y + dy);
yield mouse.up();
yield testActor.reflow();
let computedStyle = yield highlightedNode.getComputedStyle();
let definition = computedStyle["clip-path"].value;
ok(definition.includes(`${dx}px ${dy}px`), `Point moved to ${dx}px ${dy}px`);
}
function* testPolygonAddPoint(testActor, helper) {
yield helper.show("#polygon", {mode: "cssClipPath"});
let { mouse, highlightedNode } = helper;
// Move first point to have same x as second point, then double click between
// the two points to add a new one.
let points = yield helper.getElementAttribute("shapes-polygon", "points");
let pointsArray = points.split(" ");
let quads = yield testActor.getAllAdjustedQuads("#polygon");
let { top, left, width, height } = quads.border[0].bounds;
let [x1, y1] = pointsArray[0].split(",");
let [x2, y2] = pointsArray[1].split(",");
x1 = left + width * x1 / 100;
x2 = left + width * x2 / 100;
y1 = top + height * y1 / 100;
y2 = top + height * y2 / 100;
yield mouse.down(x1, y1);
yield mouse.move(x2, y1);
yield mouse.up();
yield testActor.reflow();
let newPointX = x2;
let newPointY = (y1 + y2) / 2;
let options = {
selector: ":root",
x: newPointX,
y: newPointY,
center: false,
options: {clickCount: 2}
};
info("Adding new polygon point");
yield testActor.synthesizeMouse(options);
yield testActor.reflow();
let computedStyle = yield highlightedNode.getComputedStyle();
let definition = computedStyle["clip-path"].value;
ok(definition.includes(`${newPointX * 100 / width}% ${newPointY * 100 / height}%`),
"Point successfuly added");
}
function* testPolygonRemovePoint(testActor, helper) {
yield helper.show("#polygon", {mode: "cssClipPath"});
let { highlightedNode } = helper;
let points = yield helper.getElementAttribute("shapes-polygon", "points");
let [x, y] = points.split(" ")[0].split(",");
let quads = yield testActor.getAllAdjustedQuads("#polygon");
let { top, left, width, height } = quads.border[0].bounds;
let options = {
selector: ":root",
x: left + width * x / 100,
y: top + height * y / 100,
center: false,
options: {clickCount: 2}
};
info("Removing first polygon point");
yield testActor.synthesizeMouse(options);
yield testActor.reflow();
let computedStyle = yield highlightedNode.getComputedStyle();
let definition = computedStyle["clip-path"].value;
ok(!definition.includes(`${x}% ${y}%`), "Point successfully removed");
}
function* testCircleMoveCenter(testActor, helper) {
yield helper.show("#circle", {mode: "cssClipPath"});
let { mouse, highlightedNode } = helper;
let cx = parseFloat(yield helper.getElementAttribute("shapes-ellipse", "cx"));
let cy = parseFloat(yield helper.getElementAttribute("shapes-ellipse", "cy"));
let quads = yield testActor.getAllAdjustedQuads("#circle");
let { width, height } = quads.border[0].bounds;
let cxPixel = width * cx / 100;
let cyPixel = height * cy / 100;
let dx = width / 10;
let dy = height / 10;
info("Moving circle center");
yield mouse.down(cxPixel, cyPixel, "#circle");
yield mouse.move(cxPixel + dx, cyPixel + dy, "#circle");
yield mouse.up(cxPixel + dx, cyPixel + dy, "#circle");
yield testActor.reflow();
let computedStyle = yield highlightedNode.getComputedStyle();
let definition = computedStyle["clip-path"].value;
ok(definition.includes(`at ${cx + 10}% ${cy + 10}%`),
"Circle center successfully moved");
}
function* testEllipseMoveRadius(testActor, helper) {
yield helper.show("#ellipse", {mode: "cssClipPath"});
let { mouse, highlightedNode } = helper;
let rx = parseFloat(yield helper.getElementAttribute("shapes-ellipse", "rx"));
let ry = parseFloat(yield helper.getElementAttribute("shapes-ellipse", "ry"));
let cx = parseFloat(yield helper.getElementAttribute("shapes-ellipse", "cx"));
let cy = parseFloat(yield helper.getElementAttribute("shapes-ellipse", "cy"));
let quads = yield testActor.getAllAdjustedQuads("#ellipse");
let { width, height } = quads.content[0].bounds;
let computedStyle = yield highlightedNode.getComputedStyle();
let paddingTop = parseFloat(computedStyle["padding-top"].value);
let paddingLeft = parseFloat(computedStyle["padding-left"].value);
let cxPixel = paddingLeft + width * cx / 100;
let cyPixel = paddingTop + height * cy / 100;
let rxPixel = cxPixel + width * rx / 100;
let ryPixel = cyPixel + height * ry / 100;
let dx = width / 10;
let dy = height / 10;
info("Moving ellipse rx");
yield mouse.down(rxPixel, cyPixel, "#ellipse");
yield mouse.move(rxPixel + dx, cyPixel, "#ellipse");
yield mouse.up(rxPixel + dx, cyPixel, "#ellipse");
yield testActor.reflow();
info("Moving ellipse ry");
yield mouse.down(cxPixel, ryPixel, "#ellipse");
yield mouse.move(cxPixel, ryPixel - dy, "#ellipse");
yield mouse.up(cxPixel, ryPixel - dy, "#ellipse");
yield testActor.reflow();
computedStyle = yield highlightedNode.getComputedStyle();
let definition = computedStyle["clip-path"].value;
ok(definition.includes(`${rx + 10}% ${ry - 10}%`),
"Ellipse radiuses successfully moved");
}
function* testInsetMoveEdges(testActor, helper) {
yield helper.show("#inset", {mode: "cssClipPath"});
let { mouse, highlightedNode } = helper;
let x = parseFloat(yield helper.getElementAttribute("shapes-rect", "x"));
let y = parseFloat(yield helper.getElementAttribute("shapes-rect", "y"));
let width = parseFloat(yield helper.getElementAttribute("shapes-rect", "width"));
let height = parseFloat(yield helper.getElementAttribute("shapes-rect", "height"));
let quads = yield testActor.getAllAdjustedQuads("#inset");
let { width: elemWidth, height: elemHeight } = quads.content[0].bounds;
let left = elemWidth * x / 100;
let top = elemHeight * y / 100;
let right = left + elemWidth * width / 100;
let bottom = top + elemHeight * height / 100;
let xCenter = (left + right) / 2;
let yCenter = (top + bottom) / 2;
let dx = elemWidth / 10;
let dy = elemHeight / 10;
info("Moving inset top");
yield mouse.down(xCenter, top, "#inset");
yield mouse.move(xCenter, top + dy, "#inset");
yield mouse.up(xCenter, top + dy, "#inset");
yield testActor.reflow();
info("Moving inset bottom");
yield mouse.down(xCenter, bottom, "#inset");
yield mouse.move(xCenter, bottom + dy, "#inset");
yield mouse.up(xCenter, bottom + dy, "#inset");
yield testActor.reflow();
info("Moving inset left");
yield mouse.down(left, yCenter, "#inset");
yield mouse.move(left + dx, yCenter, "#inset");
yield mouse.up(left + dx, yCenter, "#inset");
yield testActor.reflow();
info("Moving inset right");
yield mouse.down(right, yCenter, "#inset");
yield mouse.move(right + dx, yCenter, "#inset");
yield mouse.up(right + dx, yCenter, "#inset");
yield testActor.reflow();
let computedStyle = yield highlightedNode.getComputedStyle();
let definition = computedStyle["clip-path"].value;
ok(definition.includes(
`${top + dy}px ${elemWidth - right - dx}px ${100 - y - height - 10}% ${x + 10}%`),
"Inset edges successfully moved");
}

View File

@ -30,11 +30,11 @@
clip-path: circle(25% at 30% 40%);
}
#ellipse {
clip-path: ellipse(40% 30% at 25% 75%) content-box;
clip-path: ellipse(40% 30% at 25% 30%) content-box;
padding: 20px;
}
#ellipse-padding-box {
clip-path: ellipse(40% 30% at 25% 75%) padding-box;
clip-path: ellipse(40% 30% at 25% 30%) padding-box;
padding: 20px;
}
#inset {

View File

@ -503,11 +503,11 @@ const getHighlighterHelperFor = (type) => Task.async(
// mouse.up(); // synthesize "mouseup" at 20,30
mouse: new Proxy({}, {
get: (target, name) =>
function* (x = prevX, y = prevY) {
function* (x = prevX, y = prevY, selector = ":root") {
prevX = x;
prevY = y;
yield testActor.synthesizeMouse({
selector: ":root", x, y, options: {type: "mouse" + name}});
selector, x, y, options: {type: "mouse" + name}});
}
}),

View File

@ -628,6 +628,14 @@ netmonitor.toolbar.filterFreetext.label=Filter URLs
# shortcut key to focus on the toolbar url filtering textbox
netmonitor.toolbar.filterFreetext.key=CmdOrCtrl+F
# LOCALIZATION NOTE (netmonitor.toolbar.disableCache.label): This is the label
# displayed for the checkbox for disabling browser cache.
netmonitor.toolbar.disableCache.label=Disable cache
# LOCALIZATION NOTE (netmonitor.toolbar.disableCache.tooltip): This is the tooltip
# displayed for the checkbox for disabling browser cache.
netmonitor.toolbar.disableCache.tooltip=Disable HTTP cache
# LOCALIZATION NOTE (netmonitor.toolbar.clear): This is the label displayed
# in the network toolbar for the "Clear" button.
netmonitor.toolbar.clear=Clear

View File

@ -7,6 +7,7 @@
const {
ACTIVITY_TYPE,
OPEN_NETWORK_DETAILS,
DISABLE_BROWSER_CACHE,
OPEN_STATISTICS,
RESET_COLUMNS,
SELECT_DETAILS_PANEL_TAB,
@ -27,6 +28,18 @@ function openNetworkDetails(open) {
};
}
/**
* Change browser cache state.
*
* @param {boolean} disabled - expected browser cache in disable state
*/
function disableBrowserCache(disabled) {
return {
type: DISABLE_BROWSER_CACHE,
disabled,
};
}
/**
* Change performance statistics panel open state.
*
@ -94,6 +107,14 @@ function toggleNetworkDetails() {
dispatch(openNetworkDetails(!getState().ui.networkDetailsOpen));
}
/**
* Toggle browser cache status.
*/
function toggleBrowserCache() {
return (dispatch, getState) =>
dispatch(disableBrowserCache(!getState().ui.browserCacheDisabled));
}
/**
* Toggle performance statistics panel.
*/
@ -104,11 +125,13 @@ function toggleStatistics() {
module.exports = {
openNetworkDetails,
disableBrowserCache,
openStatistics,
resetColumns,
resizeWaterfall,
selectDetailsPanelTab,
toggleColumn,
toggleNetworkDetails,
toggleBrowserCache,
toggleStatistics,
};

View File

@ -795,6 +795,17 @@ body,
margin: 1px 3px;
}
.devtools-checkbox {
position: relative;
vertical-align: middle;
bottom: 1px;
}
.devtools-checkbox-label {
margin-inline-start: 10px;
margin-inline-end: 3px;
}
/* Empty notices in tab panels */
.empty-notice {

View File

@ -4,6 +4,7 @@
"use strict";
const Services = require("Services");
const {
createClass,
createFactory,
@ -25,7 +26,7 @@ const { L10N } = require("../utils/l10n");
// Components
const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
const { button, div, span } = DOM;
const { button, div, input, label, span } = DOM;
const COLLPASE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
const EXPAND_DETAILS_PANE = L10N.getStr("expandDetailsPane");
@ -33,6 +34,8 @@ const SEARCH_KEY_SHORTCUT = L10N.getStr("netmonitor.toolbar.filterFreetext.key")
const SEARCH_PLACE_HOLDER = L10N.getStr("netmonitor.toolbar.filterFreetext.label");
const TOOLBAR_CLEAR = L10N.getStr("netmonitor.toolbar.clear");
const DEVTOOLS_DISABLE_CACHE_PREF = "devtools.cache.disabled";
/*
* Network monitor toolbar component
* Toolbar contains a set of useful tools to control network requests
@ -47,6 +50,9 @@ const Toolbar = createClass({
networkDetailsToggleDisabled: PropTypes.bool.isRequired,
networkDetailsOpen: PropTypes.bool.isRequired,
toggleNetworkDetails: PropTypes.func.isRequired,
disableBrowserCache: PropTypes.func.isRequired,
toggleBrowserCache: PropTypes.func.isRequired,
browserCacheDisabled: PropTypes.bool.isRequired,
toggleRequestFilterType: PropTypes.func.isRequired,
},
@ -65,6 +71,8 @@ const Toolbar = createClass({
networkDetailsToggleDisabled,
networkDetailsOpen,
toggleNetworkDetails,
toggleBrowserCache,
browserCacheDisabled,
} = this.props;
let toggleButtonClassName = [
@ -102,6 +110,20 @@ const Toolbar = createClass({
onClick: clearRequests,
}),
div({ className: "requests-list-filter-buttons" }, buttons),
label(
{
className: "devtools-checkbox-label",
title: L10N.getStr("netmonitor.toolbar.disableCache.tooltip"),
},
input({
id: "devtools-cache-checkbox",
className: "devtools-checkbox",
type: "checkbox",
checked: browserCacheDisabled,
onClick: toggleBrowserCache,
}),
L10N.getStr("netmonitor.toolbar.disableCache.label"),
),
),
span({ className: "devtools-toolbar-group" },
SearchBox({
@ -122,6 +144,21 @@ const Toolbar = createClass({
)
)
);
},
componentDidMount() {
Services.prefs.addObserver(DEVTOOLS_DISABLE_CACHE_PREF,
this.updateBrowserCacheDisabled);
},
componentWillUnmount() {
Services.prefs.removeObserver(DEVTOOLS_DISABLE_CACHE_PREF,
this.updateBrowserCacheDisabled);
},
updateBrowserCacheDisabled() {
this.props.disableBrowserCache(
Services.prefs.getBoolPref(DEVTOOLS_DISABLE_CACHE_PREF));
}
});
@ -129,6 +166,7 @@ module.exports = connect(
(state) => ({
networkDetailsToggleDisabled: isNetworkDetailsToggleButtonDisabled(state),
networkDetailsOpen: state.ui.networkDetailsOpen,
browserCacheDisabled: state.ui.browserCacheDisabled,
requestFilterTypes: getRequestFilterTypes(state),
summary: getDisplayedRequestsSummary(state),
}),
@ -137,5 +175,7 @@ module.exports = connect(
setRequestFilterText: (text) => dispatch(Actions.setRequestFilterText(text)),
toggleRequestFilterType: (type) => dispatch(Actions.toggleRequestFilterType(type)),
toggleNetworkDetails: () => dispatch(Actions.toggleNetworkDetails()),
disableBrowserCache: (disabled) => dispatch(Actions.disableBrowserCache(disabled)),
toggleBrowserCache: () => dispatch(Actions.toggleBrowserCache()),
}),
)(Toolbar);

View File

@ -14,6 +14,7 @@ const actionTypes = {
CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST",
ENABLE_REQUEST_FILTER_TYPE_ONLY: "ENABLE_REQUEST_FILTER_TYPE_ONLY",
OPEN_NETWORK_DETAILS: "OPEN_NETWORK_DETAILS",
DISABLE_BROWSER_CACHE: "DISABLE_BROWSER_CACHE",
OPEN_STATISTICS: "OPEN_STATISTICS",
REMOVE_SELECTED_CUSTOM_REQUEST: "REMOVE_SELECTED_CUSTOM_REQUEST",
RESET_COLUMNS: "RESET_COLUMNS",

View File

@ -10,6 +10,7 @@ const {
RESET_COLUMNS,
TOGGLE_COLUMN,
TOGGLE_REQUEST_FILTER_TYPE,
DISABLE_BROWSER_CACHE,
} = require("../constants");
const { getRequestFilterTypes } = require("../selectors/index");
@ -30,6 +31,10 @@ function prefsMiddleware(store) {
Services.prefs.setCharPref(
"devtools.netmonitor.filters", JSON.stringify(filters));
break;
case DISABLE_BROWSER_CACHE:
Services.prefs.setBoolPref(
"devtools.cache.disabled", store.getState().ui.browserCacheDisabled);
break;
case TOGGLE_COLUMN:
case RESET_COLUMNS:
let visibleColumns = [...store.getState().ui.columns]

View File

@ -5,9 +5,11 @@
"use strict";
const I = require("devtools/client/shared/vendor/immutable");
const Services = require("Services");
const {
CLEAR_REQUESTS,
OPEN_NETWORK_DETAILS,
DISABLE_BROWSER_CACHE,
OPEN_STATISTICS,
REMOVE_SELECTED_CUSTOM_REQUEST,
RESET_COLUMNS,
@ -51,6 +53,7 @@ const UI = I.Record({
columns: new Columns(),
detailsPanelSelectedTab: "headers",
networkDetailsOpen: false,
browserCacheDisabled: Services.prefs.getBoolPref("devtools.cache.disabled"),
statisticsOpen: false,
waterfallWidth: null,
});
@ -67,6 +70,10 @@ function openNetworkDetails(state, action) {
return state.set("networkDetailsOpen", action.open);
}
function disableBrowserCache(state, action) {
return state.set("browserCacheDisabled", action.disabled);
}
function openStatistics(state, action) {
return state.set("statisticsOpen", action.open);
}
@ -94,6 +101,8 @@ function ui(state = new UI(), action) {
return openNetworkDetails(state, { open: false });
case OPEN_NETWORK_DETAILS:
return openNetworkDetails(state, action);
case DISABLE_BROWSER_CACHE:
return disableBrowserCache(state, action);
case OPEN_STATISTICS:
return openStatistics(state, action);
case RESET_COLUMNS:

View File

@ -70,7 +70,7 @@ const MessageContainer = createClass({
const message = this.props.getMessage();
let MessageComponent = getMessageComponent(message);
return MessageComponent(Object.assign({message, indent: message.indent}, this.props));
return MessageComponent(Object.assign({message}, this.props));
}
});

View File

@ -25,14 +25,12 @@ ConsoleApiCall.propTypes = {
message: PropTypes.object.isRequired,
open: PropTypes.bool,
serviceContainer: PropTypes.object.isRequired,
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
loadedObjectProperties: PropTypes.object,
};
ConsoleApiCall.defaultProps = {
open: false,
indent: 0,
};
function ConsoleApiCall(props) {
@ -42,13 +40,13 @@ function ConsoleApiCall(props) {
open,
tableData,
serviceContainer,
indent,
timestampsVisible,
repeat,
loadedObjectProperties,
} = props;
const {
id: messageId,
indent,
source,
type,
level,

View File

@ -17,27 +17,22 @@ ConsoleCommand.displayName = "ConsoleCommand";
ConsoleCommand.propTypes = {
message: PropTypes.object.isRequired,
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
serviceContainer: PropTypes.object,
};
ConsoleCommand.defaultProps = {
indent: 0,
};
/**
* Displays input from the console.
*/
function ConsoleCommand(props) {
const {
indent,
message,
timestampsVisible,
serviceContainer,
} = props;
const {
indent,
source,
type,
level,

View File

@ -19,22 +19,16 @@ EvaluationResult.displayName = "EvaluationResult";
EvaluationResult.propTypes = {
dispatch: PropTypes.func.isRequired,
message: PropTypes.object.isRequired,
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
serviceContainer: PropTypes.object,
loadedObjectProperties: PropTypes.object,
};
EvaluationResult.defaultProps = {
indent: 0,
};
function EvaluationResult(props) {
const {
dispatch,
message,
serviceContainer,
indent,
timestampsVisible,
loadedObjectProperties,
} = props;
@ -45,6 +39,7 @@ function EvaluationResult(props) {
helperType,
level,
id: messageId,
indent,
exceptionDocURL,
frame,
timeStamp,

View File

@ -22,17 +22,11 @@ NetworkEventMessage.propTypes = {
serviceContainer: PropTypes.shape({
openNetworkPanel: PropTypes.func.isRequired,
}),
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
networkMessageUpdate: PropTypes.object.isRequired,
};
NetworkEventMessage.defaultProps = {
indent: 0,
};
function NetworkEventMessage({
indent,
message = {},
serviceContainer,
timestampsVisible,
@ -40,6 +34,7 @@ function NetworkEventMessage({
}) {
const {
actor,
indent,
source,
type,
level,

View File

@ -18,14 +18,12 @@ PageError.displayName = "PageError";
PageError.propTypes = {
message: PropTypes.object.isRequired,
open: PropTypes.bool,
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
serviceContainer: PropTypes.object,
};
PageError.defaultProps = {
open: false,
indent: 0,
};
function PageError(props) {
@ -34,11 +32,11 @@ function PageError(props) {
message,
open,
serviceContainer,
indent,
timestampsVisible,
} = props;
const {
id: messageId,
indent,
source,
type,
level,

View File

@ -86,7 +86,10 @@ describe("ConsoleAPICall component:", () => {
const message = stubPreparedMessages.get("console.log('foobar', 'test')");
const indent = 10;
let wrapper = render(ConsoleApiCall({ message, serviceContainer, indent }));
let wrapper = render(ConsoleApiCall({
message: Object.assign({}, message, {indent}),
serviceContainer
}));
expect(wrapper.find(".indent").prop("style").width)
.toBe(`${indent * INDENT_WIDTH}px`);

View File

@ -81,7 +81,9 @@ describe("EvaluationResult component:", () => {
const message = stubPreparedMessages.get("new Date(0)");
const indent = 10;
let wrapper = render(EvaluationResult({ message, indent}));
let wrapper = render(EvaluationResult({
message: Object.assign({}, message, {indent}),
}));
expect(wrapper.find(".indent").prop("style").width)
.toBe(`${indent * INDENT_WIDTH}px`);

View File

@ -57,7 +57,10 @@ describe("NetworkEventMessage component:", () => {
const message = stubPreparedMessages.get("GET request");
const indent = 10;
let wrapper = render(NetworkEventMessage({ message, serviceContainer, indent}));
let wrapper = render(NetworkEventMessage({
message: Object.assign({}, message, {indent}),
serviceContainer
}));
expect(wrapper.find(".indent").prop("style").width)
.toBe(`${indent * INDENT_WIDTH}px`);

View File

@ -142,7 +142,10 @@ describe("PageError component:", () => {
it("has the expected indent", () => {
const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
const indent = 10;
let wrapper = render(PageError({ message, serviceContainer, indent}));
let wrapper = render(PageError({
message: Object.assign({}, message, {indent}),
serviceContainer
}));
expect(wrapper.find(".indent").prop("style").width)
.toBe(`${indent * INDENT_WIDTH}px`);

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ DevToolsModules(
'css-grid-utils.js',
'make-debugger.js',
'map-uri-to-addon-id.js',
'shapes-geometry-utils.js',
'stack.js',
'TabSources.js',
'walker-search.js',

View File

@ -0,0 +1,110 @@
"use strict";
/**
* Get the distance between two points on a plane.
* @param {Number} x1 the x coord of the first point
* @param {Number} y1 the y coord of the first point
* @param {Number} x2 the x coord of the second point
* @param {Number} y2 the y coord of the second point
* @returns {Number} the distance between the two points
*/
const getDistance = (x1, y1, x2, y2) => {
return Math.hypot(x2 - x1, y2 - y1);
};
/**
* Determine if the given x/y coords are along the edge of the given ellipse.
* We allow for a small area around the edge that still counts as being on the edge.
* @param {Number} x the x coordinate of the click
* @param {Number} y the y coordinate of the click
* @param {Number} cx the x coordinate of the center of the ellipse
* @param {Number} cy the y coordinate of the center of the ellipse
* @param {Number} rx the x radius of the ellipse
* @param {Number} ry the y radius of the ellipse
* @param {Number} clickWidthX the width of the area that counts as being on the edge
* along the x radius.
* @param {Number} clickWidthY the width of the area that counts as being on the edge
* along the y radius.
* @returns {Boolean} whether the click counts as being on the edge of the ellipse.
*/
const clickedOnEllipseEdge = (x, y, cx, cy, rx, ry, clickWidthX, clickWidthY) => {
// The formula to determine if something is inside or on the edge of an ellipse is:
// (x - cx)^2/rx^2 + (y - cy)^2/ry^2 <= 1. If > 1, it's outside.
// We make two ellipses, adjusting rx and ry with clickWidthX and clickWidthY
// to allow for an area around the edge of the ellipse that can be clicked on.
// If the click was outside the inner ellipse and inside the outer ellipse, return true.
let inner = ((x - cx) ** 2) / (rx - clickWidthX) ** 2 +
((y - cy) ** 2) / (ry - clickWidthY) ** 2;
let outer = ((x - cx) ** 2) / (rx + clickWidthX) ** 2 +
((y - cy) ** 2) / (ry + clickWidthY) ** 2;
return inner >= 1 && outer <= 1;
};
/**
* Get the distance between a point and a line defined by two other points.
* @param {Number} x1 the x coordinate of the first point in the line
* @param {Number} y1 the y coordinate of the first point in the line
* @param {Number} x2 the x coordinate of the second point in the line
* @param {Number} y2 the y coordinate of the second point in the line
* @param {Number} x3 the x coordinate of the point for which the distance is found
* @param {Number} y3 the y coordinate of the point for which the distance is found
* @returns {Number} the distance between (x3,y3) and the line defined by
* (x1,y1) and (y1,y2)
*/
const distanceToLine = (x1, y1, x2, y2, x3, y3) => {
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_two_points
let num = Math.abs((y2 - y1) * x3 - (x2 - x1) * y3 + x2 * y1 - y2 * x1);
let denom = getDistance(x1, y1, x2, y2);
return num / denom;
};
/**
* Get the point on the line defined by points a,b that is closest to point c
* @param {Number} ax the x coordinate of point a
* @param {Number} ay the y coordinate of point a
* @param {Number} bx the x coordinate of point b
* @param {Number} by the y coordinate of point b
* @param {Number} cx the x coordinate of point c
* @param {Number} cy the y coordinate of point c
* @returns {Array} a 2 element array that contains the x/y coords of the projected point
*/
const projection = (ax, ay, bx, by, cx, cy) => {
// https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
let ab = [bx - ax, by - ay];
let ac = [cx - ax, cy - ay];
let scalar = dotProduct(ab, ac) / dotProduct(ab, ab);
return [ax + scalar * ab[0], ay + scalar * ab[1]];
};
/**
* Get the dot product of two vectors, represented by arrays of numbers.
* @param {Array} a the first vector
* @param {Array} b the second vector
* @returns {Number} the dot product of a and b
*/
const dotProduct = (a, b) => {
return a.reduce((prev, curr, i) => {
return prev + curr * b[i];
}, 0);
};
/**
* Determine if the given x/y coords are above the given point.
* @param {Number} x the x coordinate of the click
* @param {Number} y the y coordinate of the click
* @param {Number} pointX the x coordinate of the center of the point
* @param {Number} pointY the y coordinate of the center of the point
* @param {Number} radiusX the x radius of the point
* @param {Number} radiusY the y radius of the point
* @returns {Boolean} whether the click was on the point
*/
const clickedOnPoint = (x, y, pointX, pointY, radiusX, radiusY) => {
return x >= pointX - radiusX && x <= pointX + radiusX &&
y >= pointY - radiusY && y <= pointY + radiusY;
};
exports.getDistance = getDistance;
exports.clickedOnEllipseEdge = clickedOnEllipseEdge;
exports.distanceToLine = distanceToLine;
exports.projection = projection;
exports.clickedOnPoint = clickedOnPoint;

View File

@ -12,7 +12,8 @@ const {
coordToPercent,
evalCalcExpression,
shapeModeToCssPropertyName,
getCirclePath
getCirclePath,
getUnit
} = require("devtools/server/actors/highlighters/shapes");
function run_test() {
@ -21,6 +22,7 @@ function run_test() {
test_eval_calc_expression();
test_shape_mode_to_css_property_name();
test_get_circle_path();
test_get_unit();
run_next_test();
}
@ -32,7 +34,7 @@ function test_split_coords() {
}, {
desc: "splitCoords for coord pair with calc()",
expr: "calc(50px + 20%) 30%",
expected: ["calc(50px+20%)", "30%"]
expected: ["calc(50px\u00a0+\u00a020%)", "30%"]
}];
for (let { desc, expr, expected } of tests) {
@ -125,3 +127,41 @@ function test_get_circle_path() {
equal(getCirclePath(cx, cy, width, height, zoom), expected, desc);
}
}
function test_get_unit() {
const tests = [{
desc: "getUnit with %",
expr: "30%", expected: "%"
}, {
desc: "getUnit with px",
expr: "400px", expected: "px"
}, {
desc: "getUnit with em",
expr: "4em", expected: "em"
}, {
desc: "getUnit with 0",
expr: "0", expected: "px"
}, {
desc: "getUnit with 0%",
expr: "0%", expected: "px"
}, {
desc: "getUnit with no unit",
expr: "30", expected: "px"
}, {
desc: "getUnit with calc",
expr: "calc(30px + 5%)", expected: "px"
}, {
desc: "getUnit with var",
expr: "var(--variable)", expected: "px"
}, {
desc: "getUnit with closest-side",
expr: "closest-side", expected: "px"
}, {
desc: "getUnit with farthest-side",
expr: "farthest-side", expected: "px"
}];
for (let { desc, expr, expected } of tests) {
equal(getUnit(expr), expected, desc);
}
}

View File

@ -4,6 +4,10 @@
"use strict";
const Cu = Components.utils;
const Ci = Components.interfaces;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
this.EXPORTED_SYMBOLS = [
"DevToolsShim",
];
@ -37,11 +41,22 @@ this.DevToolsShim = {
themes: [],
/**
* Check if DevTools are currently installed and available.
* Check if DevTools are currently installed (but not necessarily initialized).
*
* @return {Boolean} true if DevTools are installed.
*/
isInstalled: function () {
return Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler)
.hasSubstitution("devtools");
},
/**
* Check if DevTools have already been initialized.
*
* @return {Boolean} true if DevTools are initialized.
*/
isInitialized: function () {
return !!this.gDevTools;
},
@ -61,14 +76,14 @@ this.DevToolsShim = {
* shutdown.
*/
unregister: function () {
if (this.isInstalled()) {
if (this.isInitialized()) {
this.gDevTools.emit("devtools-unregistered");
this.gDevTools = null;
}
},
/**
* The following methods can be called before DevTools are installed:
* The following methods can be called before DevTools are initialized:
* - on
* - off
* - registerTool
@ -76,7 +91,7 @@ this.DevToolsShim = {
* - registerTheme
* - unregisterTheme
*
* If DevTools are not installed when calling the method, DevToolsShim will call the
* If DevTools are not initialized when calling the method, DevToolsShim will call the
* appropriate method as soon as a gDevTools instance is registered.
*/
@ -86,7 +101,7 @@ this.DevToolsShim = {
* - toolbox-destroyed
*/
on: function (event, listener) {
if (this.isInstalled()) {
if (this.isInitialized()) {
this.gDevTools.on(event, listener);
} else {
this.listeners.push([event, listener]);
@ -98,7 +113,7 @@ this.DevToolsShim = {
* with on().
*/
off: function (event, listener) {
if (this.isInstalled()) {
if (this.isInitialized()) {
this.gDevTools.off(event, listener);
} else {
removeItem(this.listeners, ([e, l]) => e === event && l === listener);
@ -110,7 +125,7 @@ this.DevToolsShim = {
* no longer supported.
*/
registerTool: function (tool) {
if (this.isInstalled()) {
if (this.isInitialized()) {
this.gDevTools.registerTool(tool);
} else {
this.tools.push(tool);
@ -122,7 +137,7 @@ this.DevToolsShim = {
* no longer supported.
*/
unregisterTool: function (tool) {
if (this.isInstalled()) {
if (this.isInitialized()) {
this.gDevTools.unregisterTool(tool);
} else {
removeItem(this.tools, t => t === tool);
@ -134,7 +149,7 @@ this.DevToolsShim = {
* no longer supported.
*/
registerTheme: function (theme) {
if (this.isInstalled()) {
if (this.isInitialized()) {
this.gDevTools.registerTheme(theme);
} else {
this.themes.push(theme);
@ -146,7 +161,7 @@ this.DevToolsShim = {
* no longer supported.
*/
unregisterTheme: function (theme) {
if (this.isInstalled()) {
if (this.isInitialized()) {
this.gDevTools.unregisterTheme(theme);
} else {
removeItem(this.themes, t => t === theme);
@ -163,6 +178,11 @@ this.DevToolsShim = {
if (!this.isInstalled()) {
return [];
}
if (!this.isInitialized()) {
this._initDevTools();
}
return this.gDevTools.getOpenedScratchpads();
},
@ -174,6 +194,11 @@ this.DevToolsShim = {
if (!this.isInstalled()) {
return;
}
if (!this.isInitialized()) {
this._initDevTools();
}
this.gDevTools.restoreScratchpadSession(scratchpads);
},
@ -194,9 +219,19 @@ this.DevToolsShim = {
if (!this.isInstalled()) {
return Promise.resolve();
}
if (!this.isInitialized()) {
this._initDevTools();
}
return this.gDevTools.inspectNode(tab, selectors);
},
_initDevTools: function () {
let { loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
loader.require("devtools/client/framework/devtools-browser");
},
_onDevToolsRegistered: function () {
// Register all pending event listeners on the real gDevTools object.
for (let [event, listener] of this.listeners) {
@ -250,6 +285,10 @@ for (let method of [...addonSdkMethods, ...webExtensionsMethods]) {
throw new Error(`Method ${method} unavailable if DevTools are not installed`);
}
if (!this.isInitialized()) {
this._initDevTools();
}
return this.gDevTools[method].apply(this.gDevTools, arguments);
};
}

View File

@ -4,9 +4,12 @@
"use strict";
const { DevToolsShim } =
const { DevToolsShim: realDevToolsShim } =
Components.utils.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
// Create a copy of the DevToolsShim for the test.
const DevToolsShim = Object.assign({}, realDevToolsShim);
// Test the DevToolsShim
/**
@ -41,6 +44,14 @@ function createMockDevTools() {
return mock;
}
function mockDevToolsInstalled() {
DevToolsShim.isInstalled = () => true;
}
function mockDevToolsUninstalled() {
DevToolsShim.isInstalled = () => false;
}
/**
* Check if a given method was called an expected number of times, and finally check the
* arguments provided to the last call, if appropriate.
@ -62,17 +73,17 @@ function checkCalls(mock, method, length, lastArgs) {
}
function test_register_unregister() {
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
DevToolsShim.register(createMockDevTools());
ok(DevToolsShim.isInstalled(), "DevTools are installed");
ok(DevToolsShim.isInitialized(), "DevTools are installed");
DevToolsShim.unregister();
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
}
function test_on_is_forwarded_to_devtools() {
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
function cb1() {}
function cb2() {}
@ -87,7 +98,7 @@ function test_on_is_forwarded_to_devtools() {
}
function test_off_called_before_registering_devtools() {
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
function cb1() {}
let mock = createMockDevTools();
@ -100,7 +111,7 @@ function test_off_called_before_registering_devtools() {
}
function test_off_called_before_with_bad_callback() {
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
function cb1() {}
function cb2() {}
@ -117,7 +128,7 @@ function test_off_called_before_with_bad_callback() {
}
function test_registering_tool() {
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
let tool1 = {};
let tool2 = {};
@ -146,7 +157,7 @@ function test_registering_tool() {
}
function test_registering_theme() {
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
let theme1 = {};
let theme2 = {};
@ -175,7 +186,7 @@ function test_registering_theme() {
}
function test_events() {
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
let mock = createMockDevTools();
// Check emit was not called.
@ -191,6 +202,8 @@ function test_events() {
}
function test_scratchpad_apis() {
mockDevToolsUninstalled();
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
// Check that restoreScratchpadSession doesn't crash.
@ -201,10 +214,13 @@ function test_scratchpad_apis() {
"getOpenedScratchpads returns [] when DevTools are not installed");
let mock = createMockDevTools();
DevToolsShim.register(mock);
// Check that calls to restoreScratchpadSession are not held.
checkCalls(mock, "restoreScratchpadSession", 0);
mockDevToolsInstalled();
DevToolsShim._initDevTools = () => {
// Next call to getOpenedScratchpags is expected to initialize DevTools, which we
// simulate here by registering our mock.
DevToolsShim.register(mock);
};
DevToolsShim.getOpenedScratchpads();
checkCalls(mock, "getOpenedScratchpads", 1, []);

View File

@ -25,6 +25,8 @@ enum class CompositeOperation : uint8_t;
*/
struct PropertyValuePair
{
explicit PropertyValuePair(nsCSSPropertyID aProperty)
: mProperty(aProperty) { }
PropertyValuePair(nsCSSPropertyID aProperty, nsCSSValue&& aValue)
: mProperty(aProperty), mValue(Move(aValue)) { }
PropertyValuePair(nsCSSPropertyID aProperty,

View File

@ -1402,13 +1402,14 @@ static inline bool
RangeMatchesBeginPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset)
{
return aRange->GetStartContainer() == aNode &&
aRange->StartOffset() == aOffset;
static_cast<int32_t>(aRange->StartOffset()) == aOffset;
}
static inline bool
RangeMatchesEndPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset)
{
return aRange->GetEndContainer() == aNode && aRange->EndOffset() == aOffset;
return aRange->GetEndContainer() == aNode &&
static_cast<int32_t>(aRange->EndOffset()) == aOffset;
}
// Selection::EqualsRangeAtPoint

View File

@ -2774,6 +2774,9 @@ nsContentUtils::ComparePoints(nsINode* aParent1, int32_t aOffset1,
bool* aDisconnected)
{
if (aParent1 == aParent2) {
// XXX This is odd. aOffset1 and/or aOffset2 may be -1, e.g., it's result
// of nsINode::IndexOf(), but this compares such invalid offset with
// valid offset.
return aOffset1 < aOffset2 ? -1 :
aOffset1 > aOffset2 ? 1 :
0;
@ -2824,10 +2827,14 @@ nsContentUtils::ComparePoints(nsINode* aParent1, int32_t aOffset1,
if (!pos1) {
nsINode* child2 = parents2.ElementAt(--pos2);
// XXX aOffset1 may be -1 as mentioned above. So, why does this return
// it's *before* of the valid DOM point?
return aOffset1 <= parent->IndexOf(child2) ? -1 : 1;
}
nsINode* child1 = parents1.ElementAt(--pos1);
// XXX aOffset2 may be -1 as mentioned above. So, why does this return it's
// *after* of the valid DOM point?
return parent->IndexOf(child1) < aOffset2 ? -1 : 1;
}

View File

@ -404,6 +404,13 @@ public:
* NOTE! If the two nodes aren't in the same connected subtree,
* the result is 1, and the optional aDisconnected parameter
* is set to true.
*
* XXX aOffset1 and aOffset2 should be uint32_t since valid offset value is
* between 0 - UINT32_MAX. However, these methods work even with
* negative offset values! E.g., when aOffset1 is -1 and aOffset is 0,
* these methods return -1. Some root callers depend on this behavior.
* On the other hand, nsINode can have ATTRCHILD_ARRAY_MAX_CHILD_COUN
* (0x3FFFFF) at most. Therefore, they can be int32_t for now.
*/
static int32_t ComparePoints(nsINode* aParent1, int32_t aOffset1,
nsINode* aParent2, int32_t aOffset2,

View File

@ -1569,10 +1569,13 @@ nsHTMLCopyEncoder::IncludeInContext(nsINode *aNode)
nsresult
nsHTMLCopyEncoder::PromoteRange(nsIDOMRange *inRange)
{
if (!inRange) return NS_ERROR_NULL_POINTER;
RefPtr<nsRange> range = static_cast<nsRange*>(inRange);
if (!range) {
return NS_ERROR_NULL_POINTER;
}
nsresult rv;
nsCOMPtr<nsIDOMNode> startNode, endNode, common;
int32_t startOffset, endOffset;
uint32_t startOffset, endOffset;
rv = inRange->GetCommonAncestorContainer(getter_AddRefs(common));
NS_ENSURE_SUCCESS(rv, rv);
@ -1590,9 +1593,11 @@ nsHTMLCopyEncoder::PromoteRange(nsIDOMRange *inRange)
int32_t opStartOffset, opEndOffset;
// examine range endpoints.
rv = GetPromotedPoint( kStart, startNode, startOffset, address_of(opStartNode), &opStartOffset, common);
rv = GetPromotedPoint(kStart, startNode, static_cast<int32_t>(startOffset),
address_of(opStartNode), &opStartOffset, common);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetPromotedPoint( kEnd, endNode, endOffset, address_of(opEndNode), &opEndOffset, common);
rv = GetPromotedPoint(kEnd, endNode, static_cast<int32_t>(endOffset),
address_of(opEndNode), &opEndOffset, common);
NS_ENSURE_SUCCESS(rv, rv);
// if both range endpoints are at the common ancestor, check for possible inclusion of ancestors
@ -1604,9 +1609,9 @@ nsHTMLCopyEncoder::PromoteRange(nsIDOMRange *inRange)
}
// set the range to the new values
rv = inRange->SetStart(opStartNode, opStartOffset);
rv = inRange->SetStart(opStartNode, static_cast<uint32_t>(opStartOffset));
NS_ENSURE_SUCCESS(rv, rv);
rv = inRange->SetEnd(opEndNode, opEndOffset);
rv = inRange->SetEnd(opEndNode, static_cast<uint32_t>(opEndOffset));
return rv;
}

View File

@ -2512,7 +2512,7 @@ nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
nsCOMPtr<nsIDOMNode> startNode, endNode;
bool isCollapsed = false;
nsCOMPtr<nsIContent> startContent, endContent;
int32_t startOffset = 0;
uint32_t startOffset = 0;
if (domSelection) {
domSelection->GetIsCollapsed(&isCollapsed);
nsCOMPtr<nsIDOMRange> domRange;
@ -2526,7 +2526,6 @@ nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
startContent = do_QueryInterface(startNode);
if (startContent && startContent->IsElement()) {
NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
childContent = startContent->GetChildAt(startOffset);
if (childContent) {
startContent = childContent;
@ -2535,9 +2534,8 @@ nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
endContent = do_QueryInterface(endNode);
if (endContent && endContent->IsElement()) {
int32_t endOffset = 0;
uint32_t endOffset = 0;
domRange->GetEndOffset(&endOffset);
NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
childContent = endContent->GetChildAt(endOffset);
if (childContent) {
endContent = childContent;
@ -2565,7 +2563,7 @@ nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
bool isFormControl =
startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
if (nodeValue.Length() == (uint32_t)startOffset && !isFormControl &&
if (nodeValue.Length() == startOffset && !isFormControl &&
startContent != aDocument->GetRootElement()) {
// Yes, indeed we were at the end of the last node
nsCOMPtr<nsIFrameEnumerator> frameTraversal;

View File

@ -2726,6 +2726,11 @@ nsFrameLoader::UpdatePositionAndSize(nsSubDocumentFrame *aIFrame)
if (IsRemoteFrame()) {
if (mRemoteBrowser) {
ScreenIntSize size = aIFrame->GetSubdocumentSize();
// If we were not able to show remote frame before, we should probably
// retry now to send correct showInfo.
if (!mRemoteBrowserShown) {
ShowRemoteFrame(size, aIFrame);
}
nsIntRect dimensions;
NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), NS_ERROR_FAILURE);
mLazySize = size;

View File

@ -111,31 +111,37 @@ nsRange::CompareNodeToRange(nsINode* aNode, nsRange* aRange,
// so instead represent it by (node,0) and (node,numChildren)
parent = aNode;
nodeStart = 0;
nodeEnd = aNode->GetChildCount();
uint32_t childCount = aNode->GetChildCount();
MOZ_ASSERT(childCount <= INT32_MAX,
"There shouldn't be over INT32_MAX children");
nodeEnd = static_cast<int32_t>(childCount);
}
else {
nodeStart = parent->IndexOf(aNode);
nodeEnd = nodeStart + 1;
MOZ_ASSERT(nodeStart < nodeEnd, "nodeStart shouldn't be INT32_MAX");
}
nsINode* rangeStartContainer = aRange->GetStartContainer();
nsINode* rangeEndContainer = aRange->GetEndContainer();
int32_t rangeStartOffset = aRange->StartOffset();
int32_t rangeEndOffset = aRange->EndOffset();
uint32_t rangeStartOffset = aRange->StartOffset();
uint32_t rangeEndOffset = aRange->EndOffset();
// is RANGE(start) <= NODE(start) ?
bool disconnected = false;
*outNodeBefore = nsContentUtils::ComparePoints(rangeStartContainer,
rangeStartOffset,
parent, nodeStart,
&disconnected) > 0;
*outNodeBefore =
nsContentUtils::ComparePoints(rangeStartContainer,
static_cast<int32_t>(rangeStartOffset),
parent, nodeStart,
&disconnected) > 0;
NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
// is RANGE(end) >= NODE(end) ?
*outNodeAfter = nsContentUtils::ComparePoints(rangeEndContainer,
rangeEndOffset,
parent, nodeEnd,
&disconnected) < 0;
*outNodeAfter =
nsContentUtils::ComparePoints(rangeEndContainer,
static_cast<int32_t>(rangeEndOffset),
parent, nodeEnd,
&disconnected) < 0;
NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
return NS_OK;
}
@ -164,13 +170,17 @@ struct IsItemInRangeComparator
int operator()(const nsRange* const aRange) const
{
int32_t cmp = nsContentUtils::ComparePoints(mNode, mEndOffset,
aRange->GetStartContainer(),
aRange->StartOffset());
int32_t cmp =
nsContentUtils::ComparePoints(
mNode, static_cast<int32_t>(mEndOffset),
aRange->GetStartContainer(),
static_cast<int32_t>(aRange->StartOffset()));
if (cmp == 1) {
cmp = nsContentUtils::ComparePoints(mNode, mStartOffset,
aRange->GetEndContainer(),
aRange->EndOffset());
cmp =
nsContentUtils::ComparePoints(
mNode, static_cast<int32_t>(mStartOffset),
aRange->GetEndContainer(),
static_cast<int32_t>(aRange->EndOffset()));
if (cmp == -1) {
return 0;
}
@ -266,8 +276,8 @@ nsRange::nsRange(nsINode* aNode)
/* static */
nsresult
nsRange::CreateRange(nsINode* aStartContainer, int32_t aStartOffset,
nsINode* aEndParent, int32_t aEndOffset,
nsRange::CreateRange(nsINode* aStartContainer, uint32_t aStartOffset,
nsINode* aEndParent, uint32_t aEndOffset,
nsRange** aRange)
{
MOZ_ASSERT(aRange);
@ -285,8 +295,8 @@ nsRange::CreateRange(nsINode* aStartContainer, int32_t aStartOffset,
/* static */
nsresult
nsRange::CreateRange(nsIDOMNode* aStartContainer, int32_t aStartOffset,
nsIDOMNode* aEndParent, int32_t aEndOffset,
nsRange::CreateRange(nsIDOMNode* aStartContainer, uint32_t aStartOffset,
nsIDOMNode* aEndParent, uint32_t aEndOffset,
nsRange** aRange)
{
nsCOMPtr<nsINode> startContainer = do_QueryInterface(aStartContainer);
@ -297,8 +307,8 @@ nsRange::CreateRange(nsIDOMNode* aStartContainer, int32_t aStartOffset,
/* static */
nsresult
nsRange::CreateRange(nsIDOMNode* aStartContainer, int32_t aStartOffset,
nsIDOMNode* aEndParent, int32_t aEndOffset,
nsRange::CreateRange(nsIDOMNode* aStartContainer, uint32_t aStartOffset,
nsIDOMNode* aEndParent, uint32_t aEndOffset,
nsIDOMRange** aRange)
{
RefPtr<nsRange> range;
@ -456,17 +466,27 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
// again (when the new text node is notified).
nsINode* parentNode = aContent->GetParentNode();
int32_t index = -1;
if (parentNode == mEndContainer && mEndOffset > 0 &&
(index = parentNode->IndexOf(aContent)) + 1 == mEndOffset) {
newEndNode = mEndContainer;
newEndOffset = mEndOffset + 1;
mEndOffsetWasIncremented = true;
if (parentNode == mEndContainer && mEndOffset > 0) {
index = parentNode->IndexOf(aContent);
NS_WARNING_ASSERTION(index >= 0,
"Shouldn't be called during removing the node or something");
if (static_cast<uint32_t>(index + 1) == mEndOffset) {
newEndNode = mEndContainer;
newEndOffset = mEndOffset + 1;
MOZ_ASSERT(IsValidOffset(newEndOffset));
mEndOffsetWasIncremented = true;
}
}
if (parentNode == mStartContainer && mStartOffset > 0 &&
(index != -1 ? index : parentNode->IndexOf(aContent)) + 1 == mStartOffset) {
newStartNode = mStartContainer;
newStartOffset = mStartOffset + 1;
mStartOffsetWasIncremented = true;
if (parentNode == mStartContainer && mStartOffset > 0) {
if (index <= 0) {
index = parentNode->IndexOf(aContent);
}
if (static_cast<uint32_t>(index + 1) == mStartOffset) {
newStartNode = mStartContainer;
newStartOffset = mStartOffset + 1;
MOZ_ASSERT(IsValidOffset(newStartOffset));
mStartOffsetWasIncremented = true;
}
}
#ifdef DEBUG
if (mStartOffsetWasIncremented || mEndOffsetWasIncremented) {
@ -479,16 +499,15 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
// If the changed node contains our start boundary and the change starts
// before the boundary we'll need to adjust the offset.
if (aContent == mStartContainer &&
aInfo->mChangeStart < static_cast<uint32_t>(mStartOffset)) {
if (aContent == mStartContainer && aInfo->mChangeStart < mStartOffset) {
if (aInfo->mDetails) {
// splitText(), aInfo->mDetails->mNextSibling is the new text node
NS_ASSERTION(aInfo->mDetails->mType ==
CharacterDataChangeInfo::Details::eSplit,
"only a split can start before the end");
NS_ASSERTION(static_cast<uint32_t>(mStartOffset) <= aInfo->mChangeEnd + 1,
NS_ASSERTION(mStartOffset <= aInfo->mChangeEnd + 1,
"mStartOffset is beyond the end of this node");
newStartOffset = static_cast<uint32_t>(mStartOffset) - aInfo->mChangeStart;
newStartOffset = mStartOffset - aInfo->mChangeStart;
newStartNode = aInfo->mDetails->mNextSibling;
if (MOZ_UNLIKELY(aContent == mRoot)) {
newRoot = IsValidBoundary(newStartNode);
@ -507,7 +526,7 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
// If boundary is inside changed text, position it before change
// else adjust start offset for the change in length.
newStartNode = mStartContainer;
newStartOffset = static_cast<uint32_t>(mStartOffset) <= aInfo->mChangeEnd ?
newStartOffset = mStartOffset <= aInfo->mChangeEnd ?
aInfo->mChangeStart :
mStartOffset + aInfo->mChangeStart - aInfo->mChangeEnd +
aInfo->mReplaceLength;
@ -517,16 +536,15 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
// Do the same thing for the end boundary, except for splitText of a node
// with no parent then only switch to the new node if the start boundary
// did so too (otherwise the range would end up with disconnected nodes).
if (aContent == mEndContainer &&
aInfo->mChangeStart < static_cast<uint32_t>(mEndOffset)) {
if (aContent == mEndContainer && aInfo->mChangeStart < mEndOffset) {
if (aInfo->mDetails && (aContent->GetParentNode() || newStartNode)) {
// splitText(), aInfo->mDetails->mNextSibling is the new text node
NS_ASSERTION(aInfo->mDetails->mType ==
CharacterDataChangeInfo::Details::eSplit,
"only a split can start before the end");
NS_ASSERTION(static_cast<uint32_t>(mEndOffset) <= aInfo->mChangeEnd + 1,
NS_ASSERTION(mEndOffset <= aInfo->mChangeEnd + 1,
"mEndOffset is beyond the end of this node");
newEndOffset = static_cast<uint32_t>(mEndOffset) - aInfo->mChangeStart;
newEndOffset = mEndOffset - aInfo->mChangeStart;
newEndNode = aInfo->mDetails->mNextSibling;
bool isCommonAncestor =
@ -542,7 +560,7 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
}
} else {
newEndNode = mEndContainer;
newEndOffset = static_cast<uint32_t>(mEndOffset) <= aInfo->mChangeEnd ?
newEndOffset = mEndOffset <= aInfo->mChangeEnd ?
aInfo->mChangeStart :
mEndOffset + aInfo->mChangeStart - aInfo->mChangeEnd +
aInfo->mReplaceLength;
@ -555,14 +573,14 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
// that will be removed
nsIContent* removed = aInfo->mDetails->mNextSibling;
if (removed == mStartContainer) {
newStartOffset = static_cast<uint32_t>(mStartOffset) + aInfo->mChangeStart;
newStartOffset = mStartOffset + aInfo->mChangeStart;
newStartNode = aContent;
if (MOZ_UNLIKELY(removed == mRoot)) {
newRoot = IsValidBoundary(newStartNode);
}
}
if (removed == mEndContainer) {
newEndOffset = static_cast<uint32_t>(mEndOffset) + aInfo->mChangeStart;
newEndOffset = mEndOffset + aInfo->mChangeStart;
newEndNode = aContent;
if (MOZ_UNLIKELY(removed == mRoot)) {
newRoot = IsValidBoundary(newEndNode);
@ -576,13 +594,13 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
// point before the first child is never affected by normalize().)
nsINode* parentNode = aContent->GetParentNode();
if (parentNode == mStartContainer && mStartOffset > 0 &&
uint32_t(mStartOffset) < parentNode->GetChildCount() &&
mStartOffset < parentNode->GetChildCount() &&
removed == parentNode->GetChildAt(mStartOffset)) {
newStartNode = aContent;
newStartOffset = aInfo->mChangeStart;
}
if (parentNode == mEndContainer && mEndOffset > 0 &&
uint32_t(mEndOffset) < parentNode->GetChildCount() &&
mEndOffset < parentNode->GetChildCount() &&
removed == parentNode->GetChildAt(mEndOffset)) {
newEndNode = aContent;
newEndOffset = aInfo->mChangeEnd;
@ -650,14 +668,20 @@ nsRange::ContentInserted(nsIDocument* aDocument,
nsINode* container = NODE_FROM(aContainer, aDocument);
// Adjust position if a sibling was inserted.
if (container == mStartContainer && aIndexInContainer < mStartOffset &&
if (container == mStartContainer &&
(NS_WARN_IF(aIndexInContainer < 0) ||
static_cast<uint32_t>(aIndexInContainer) < mStartOffset) &&
!mStartOffsetWasIncremented) {
++newStartOffset;
MOZ_ASSERT(IsValidOffset(newStartOffset));
rangeChanged = true;
}
if (container == mEndContainer && aIndexInContainer < mEndOffset &&
if (container == mEndContainer &&
(NS_WARN_IF(aIndexInContainer < 0) ||
static_cast<uint32_t>(aIndexInContainer) < mEndOffset) &&
!mEndOffsetWasIncremented) {
++newEndOffset;
MOZ_ASSERT(IsValidOffset(newEndOffset));
rangeChanged = true;
}
if (container->IsSelectionDescendant() &&
@ -705,7 +729,7 @@ nsRange::ContentRemoved(nsIDocument* aDocument,
// Adjust position if a sibling was removed...
if (container == mStartContainer) {
if (aIndexInContainer < mStartOffset) {
if (aIndexInContainer < static_cast<int32_t>(mStartOffset)) {
--newStartOffset;
rangeChanged = true;
}
@ -717,7 +741,7 @@ nsRange::ContentRemoved(nsIDocument* aDocument,
// Do same thing for end boundry.
if (container == mEndContainer) {
if (aIndexInContainer < mEndOffset) {
if (aIndexInContainer < static_cast<int32_t>(mEndOffset)) {
--newEndOffset;
rangeChanged = true;
}
@ -773,12 +797,15 @@ nsRange::ParentChainChanged(nsIContent *aContent)
* Utilities for comparing points: API from nsIDOMRange
******************************************************/
NS_IMETHODIMP
nsRange::IsPointInRange(nsIDOMNode* aContainer, int32_t aOffset, bool* aResult)
nsRange::IsPointInRange(nsIDOMNode* aContainer, uint32_t aOffset, bool* aResult)
{
nsCOMPtr<nsINode> container = do_QueryInterface(aContainer);
if (!container) {
return NS_ERROR_DOM_NOT_OBJECT_ERR;
}
if (NS_WARN_IF(!IsValidOffset(aOffset))) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
ErrorResult rv;
*aResult = IsPointInRange(*container, aOffset, rv);
@ -801,7 +828,8 @@ nsRange::IsPointInRange(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv)
// returns -1 if point is before range, 0 if point is in range,
// 1 if point is after range.
NS_IMETHODIMP
nsRange::ComparePoint(nsIDOMNode* aContainer, int32_t aOffset, int16_t* aResult)
nsRange::ComparePoint(nsIDOMNode* aContainer, uint32_t aOffset,
int16_t* aResult)
{
nsCOMPtr<nsINode> container = do_QueryInterface(aContainer);
NS_ENSURE_TRUE(container, NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
@ -835,15 +863,18 @@ nsRange::ComparePoint(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv)
return 0;
}
int32_t cmp;
if ((cmp = nsContentUtils::ComparePoints(&aContainer, aOffset,
mStartContainer,
mStartOffset)) <= 0) {
int32_t cmp =
nsContentUtils::ComparePoints(&aContainer,
static_cast<int32_t>(aOffset),
mStartContainer,
static_cast<int32_t>(mStartOffset));
if (cmp <= 0) {
return cmp;
}
if (nsContentUtils::ComparePoints(mEndContainer, mEndOffset,
&aContainer, aOffset) == -1) {
if (nsContentUtils::ComparePoints(mEndContainer,
static_cast<int32_t>(mEndOffset),
&aContainer,
static_cast<int32_t>(aOffset)) == -1) {
return 1;
}
@ -886,12 +917,15 @@ nsRange::IntersectsNode(nsINode& aNode, ErrorResult& aRv)
// Steps 6-7.
// Note: if disconnected is true, ComparePoints returns 1.
bool disconnected = false;
bool result = nsContentUtils::ComparePoints(mStartContainer, mStartOffset,
parent, nodeIndex + 1,
&disconnected) < 0 &&
nsContentUtils::ComparePoints(parent, nodeIndex,
mEndContainer, mEndOffset,
&disconnected) < 0;
bool result =
nsContentUtils::ComparePoints(mStartContainer,
static_cast<int32_t>(mStartOffset),
parent, nodeIndex + 1,
&disconnected) < 0 &&
nsContentUtils::ComparePoints(parent, nodeIndex,
mEndContainer,
static_cast<int32_t>(mEndOffset),
&disconnected) < 0;
// Step 2.
if (disconnected) {
@ -910,8 +944,8 @@ nsRange::IntersectsNode(nsINode& aNode, ErrorResult& aRv)
// Calling DoSetRange with either parent argument null will collapse
// the range to have both endpoints point to the other node
void
nsRange::DoSetRange(nsINode* aStartN, int32_t aStartOffset,
nsINode* aEndN, int32_t aEndOffset,
nsRange::DoSetRange(nsINode* aStartN, uint32_t aStartOffset,
nsINode* aEndN, uint32_t aEndOffset,
nsINode* aRoot, bool aNotInsertedYet)
{
NS_PRECONDITION((aStartN && aEndN && aRoot) ||
@ -937,6 +971,8 @@ nsRange::DoSetRange(nsINode* aStartN, int32_t aStartOffset,
/*For backward compatibility*/
aRoot->IsNodeOfType(nsINode::eCONTENT))),
"Bad root");
MOZ_ASSERT(IsValidOffset(aStartOffset));
MOZ_ASSERT(IsValidOffset(aEndOffset));
if (mRoot != aRoot) {
if (mRoot) {
@ -1059,7 +1095,7 @@ nsRange::GetStartContainer(ErrorResult& aRv) const
}
NS_IMETHODIMP
nsRange::GetStartOffset(int32_t* aStartOffset)
nsRange::GetStartOffset(uint32_t* aStartOffset)
{
if (!mIsPositioned)
return NS_ERROR_NOT_INITIALIZED;
@ -1101,7 +1137,7 @@ nsRange::GetEndContainer(ErrorResult& aRv) const
}
NS_IMETHODIMP
nsRange::GetEndOffset(int32_t* aEndOffset)
nsRange::GetEndOffset(uint32_t* aEndOffset)
{
if (!mIsPositioned)
return NS_ERROR_NOT_INITIALIZED;
@ -1160,10 +1196,10 @@ nsRange::GetCommonAncestorContainer(nsIDOMNode** aCommonParent)
/* static */
bool
nsRange::IsValidOffset(nsINode* aNode, int32_t aOffset)
nsRange::IsValidOffset(nsINode* aNode, uint32_t aOffset)
{
return aNode &&
aOffset >= 0 &&
IsValidOffset(aOffset) &&
static_cast<size_t>(aOffset) <= aNode->Length();
}
@ -1235,7 +1271,7 @@ nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
}
NS_IMETHODIMP
nsRange::SetStart(nsIDOMNode* aContainer, int32_t aOffset)
nsRange::SetStart(nsIDOMNode* aContainer, uint32_t aOffset)
{
nsCOMPtr<nsINode> container = do_QueryInterface(aContainer);
if (!container) {
@ -1248,7 +1284,7 @@ nsRange::SetStart(nsIDOMNode* aContainer, int32_t aOffset)
}
/* virtual */ nsresult
nsRange::SetStart(nsINode* aContainer, int32_t aOffset)
nsRange::SetStart(nsINode* aContainer, uint32_t aOffset)
{
nsINode* newRoot = IsValidBoundary(aContainer);
if (!newRoot) {
@ -1262,8 +1298,10 @@ nsRange::SetStart(nsINode* aContainer, int32_t aOffset)
// Collapse if not positioned yet, if positioned in another doc or
// if the new start is after end.
if (!mIsPositioned || newRoot != mRoot ||
nsContentUtils::ComparePoints(aContainer, aOffset,
mEndContainer, mEndOffset) == 1) {
nsContentUtils::ComparePoints(aContainer,
static_cast<int32_t>(aOffset),
mEndContainer,
static_cast<int32_t>(mEndOffset)) == 1) {
DoSetRange(aContainer, aOffset, aContainer, aOffset, newRoot);
return NS_OK;
@ -1292,7 +1330,10 @@ nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv)
}
AutoInvalidateSelection atEndOfBlock(this);
int32_t offset = -1;
// If the node is being removed from its parent, GetContainerAndOffsetBefore()
// returns nullptr. Then, SetStart() will throw
// NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
uint32_t offset = UINT32_MAX;
nsINode* container = GetContainerAndOffsetBefore(&aNode, &offset);
aRv = SetStart(container, offset);
}
@ -1328,7 +1369,10 @@ nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv)
}
AutoInvalidateSelection atEndOfBlock(this);
int32_t offset = -1;
// If the node is being removed from its parent, GetContainerAndOffsetAfter()
// returns nullptr. Then, SetStart() will throw
// NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
uint32_t offset = UINT32_MAX;
nsINode* container = GetContainerAndOffsetAfter(&aNode, &offset);
aRv = SetStart(container, offset);
}
@ -1367,7 +1411,7 @@ nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
}
NS_IMETHODIMP
nsRange::SetEnd(nsIDOMNode* aContainer, int32_t aOffset)
nsRange::SetEnd(nsIDOMNode* aContainer, uint32_t aOffset)
{
nsCOMPtr<nsINode> container = do_QueryInterface(aContainer);
if (!container) {
@ -1380,7 +1424,7 @@ nsRange::SetEnd(nsIDOMNode* aContainer, int32_t aOffset)
}
/* virtual */ nsresult
nsRange::SetEnd(nsINode* aContainer, int32_t aOffset)
nsRange::SetEnd(nsINode* aContainer, uint32_t aOffset)
{
nsINode* newRoot = IsValidBoundary(aContainer);
if (!newRoot) {
@ -1394,8 +1438,10 @@ nsRange::SetEnd(nsINode* aContainer, int32_t aOffset)
// Collapse if not positioned yet, if positioned in another doc or
// if the new end is before start.
if (!mIsPositioned || newRoot != mRoot ||
nsContentUtils::ComparePoints(mStartContainer, mStartOffset,
aContainer, aOffset) == 1) {
nsContentUtils::ComparePoints(mStartContainer,
static_cast<int32_t>(mStartOffset),
aContainer,
static_cast<int32_t>(aOffset)) == 1) {
DoSetRange(aContainer, aOffset, aContainer, aOffset, newRoot);
return NS_OK;
@ -1407,8 +1453,8 @@ nsRange::SetEnd(nsINode* aContainer, int32_t aOffset)
}
nsresult
nsRange::SetStartAndEnd(nsINode* aStartContainer, int32_t aStartOffset,
nsINode* aEndContainer, int32_t aEndOffset)
nsRange::SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
nsINode* aEndContainer, uint32_t aEndOffset)
{
if (NS_WARN_IF(!aStartContainer) || NS_WARN_IF(!aEndContainer)) {
return NS_ERROR_INVALID_ARG;
@ -1455,8 +1501,10 @@ nsRange::SetStartAndEnd(nsINode* aStartContainer, int32_t aStartOffset,
// If the end point is before the start point, this should be collapsed at
// the end point.
if (nsContentUtils::ComparePoints(aStartContainer, aStartOffset,
aEndContainer, aEndOffset) == 1) {
if (nsContentUtils::ComparePoints(aStartContainer,
static_cast<int32_t>(aStartOffset),
aEndContainer,
static_cast<int32_t>(aEndOffset)) == 1) {
DoSetRange(aEndContainer, aEndOffset,
aEndContainer, aEndOffset, newEndRoot);
return NS_OK;
@ -1486,7 +1534,10 @@ nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv)
}
AutoInvalidateSelection atEndOfBlock(this);
int32_t offset = -1;
// If the node is being removed from its parent, GetContainerAndOffsetBefore()
// returns nullptr. Then, SetEnd() will throw
// NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
uint32_t offset = UINT32_MAX;
nsINode* container = GetContainerAndOffsetBefore(&aNode, &offset);
aRv = SetEnd(container, offset);
}
@ -1522,7 +1573,10 @@ nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv)
}
AutoInvalidateSelection atEndOfBlock(this);
int32_t offset = -1;
// If the node is being removed from its parent, GetContainerAndOffsetAfter()
// returns nullptr. Then, SetEnd() will throw
// NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
uint32_t offset = UINT32_MAX;
nsINode* container = GetContainerAndOffsetAfter(&aNode, &offset);
aRv = SetEnd(container, offset);
}
@ -1602,7 +1656,9 @@ nsRange::SelectNode(nsINode& aNode, ErrorResult& aRv)
}
int32_t index = container->IndexOf(&aNode);
if (index < 0) {
if (NS_WARN_IF(index < 0) ||
!IsValidOffset(static_cast<uint32_t>(index)) ||
!IsValidOffset(static_cast<uint32_t>(index) + 1)) {
aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
return;
}
@ -2059,9 +2115,9 @@ nsRange::CutContents(DocumentFragment** aFragment)
// of Range gravity during our edits!
nsCOMPtr<nsINode> startContainer = mStartContainer;
int32_t startOffset = mStartOffset;
uint32_t startOffset = mStartOffset;
nsCOMPtr<nsINode> endContainer = mEndContainer;
int32_t endOffset = mEndOffset;
uint32_t endOffset = mEndOffset;
if (retval) {
// For extractContents(), abort early if there's a doctype (bug 719533).
@ -2072,10 +2128,12 @@ nsRange::CutContents(DocumentFragment** aFragment)
RefPtr<DocumentType> doctype = commonAncestorDocument->GetDoctype();
if (doctype &&
nsContentUtils::ComparePoints(startContainer, startOffset,
nsContentUtils::ComparePoints(startContainer,
static_cast<int32_t>(startOffset),
doctype, 0) < 0 &&
nsContentUtils::ComparePoints(doctype, 0,
endContainer, endOffset) < 0) {
endContainer,
static_cast<int32_t>(endOffset)) < 0) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
}
@ -2170,8 +2228,7 @@ nsRange::CutContents(DocumentFragment** aFragment)
rv = charData->GetLength(&dataLength);
NS_ENSURE_SUCCESS(rv, rv);
if (dataLength >= (uint32_t)startOffset)
{
if (dataLength >= startOffset) {
nsMutationGuard guard;
nsCOMPtr<nsIDOMCharacterData> cutNode;
rv = SplitDataNode(charData, startOffset, getter_AddRefs(cutNode));
@ -2187,22 +2244,17 @@ nsRange::CutContents(DocumentFragment** aFragment)
else if (node == endContainer)
{
// Delete or extract everything before endOffset.
if (endOffset >= 0)
{
nsMutationGuard guard;
nsCOMPtr<nsIDOMCharacterData> cutNode;
/* The Range spec clearly states clones get cut and original nodes
remain behind, so use false as the last parameter.
*/
rv = SplitDataNode(charData, endOffset, getter_AddRefs(cutNode),
false);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_STATE(!guard.Mutated(1) ||
ValidateCurrentNode(this, iter));
nodeToResult = do_QueryInterface(cutNode);
}
nsMutationGuard guard;
nsCOMPtr<nsIDOMCharacterData> cutNode;
/* The Range spec clearly states clones get cut and original nodes
remain behind, so use false as the last parameter.
*/
rv = SplitDataNode(charData, endOffset, getter_AddRefs(cutNode),
false);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_STATE(!guard.Mutated(1) ||
ValidateCurrentNode(this, iter));
nodeToResult = do_QueryInterface(cutNode);
handled = true;
}
}
@ -2212,8 +2264,7 @@ nsRange::CutContents(DocumentFragment** aFragment)
if (node && node->IsElement() &&
((node == endContainer && endOffset == 0) ||
(node == startContainer &&
int32_t(node->AsElement()->GetChildCount()) == startOffset)))
{
node->AsElement()->GetChildCount() == startOffset))) {
if (retval) {
ErrorResult rv;
nodeToResult = node->CloneNode(false, rv);
@ -2358,7 +2409,7 @@ nsRange::CompareBoundaryPoints(uint16_t aHow, nsRange& aOtherRange,
}
nsINode *ourNode, *otherNode;
int32_t ourOffset, otherOffset;
uint32_t ourOffset, otherOffset;
switch (aHow) {
case nsIDOMRange::START_TO_START:
@ -2396,8 +2447,10 @@ nsRange::CompareBoundaryPoints(uint16_t aHow, nsRange& aOtherRange,
return 0;
}
return nsContentUtils::ComparePoints(ourNode, ourOffset,
otherNode, otherOffset);
return nsContentUtils::ComparePoints(ourNode,
static_cast<int32_t>(ourOffset),
otherNode,
static_cast<int32_t>(otherOffset));
}
/* static */ nsresult
@ -2514,8 +2567,7 @@ nsRange::CloneContents(ErrorResult& aRv)
bool deepClone = !node->IsElement() ||
(!(node == mEndContainer && mEndOffset == 0) &&
!(node == mStartContainer &&
mStartOffset ==
int32_t(node->AsElement()->GetChildCount())));
mStartOffset == node->AsElement()->GetChildCount()));
// Clone the current subtree!
@ -2544,8 +2596,7 @@ nsRange::CloneContents(ErrorResult& aRv)
return nullptr;
}
if (dataLength > (uint32_t)mEndOffset)
{
if (dataLength > mEndOffset) {
aRv = charData->DeleteData(mEndOffset, dataLength - mEndOffset);
if (aRv.Failed()) {
return nullptr;
@ -2702,7 +2753,7 @@ nsRange::InsertNode(nsINode& aNode, ErrorResult& aRv)
return;
}
int32_t tStartOffset = StartOffset();
uint32_t tStartOffset = StartOffset();
nsCOMPtr<nsINode> tStartContainer = GetStartContainer(aRv);
if (aRv.Failed()) {
@ -2763,18 +2814,20 @@ nsRange::InsertNode(nsINode& aNode, ErrorResult& aRv)
// We might need to update the end to include the new node (bug 433662).
// Ideally we'd only do this if needed, but it's tricky to know when it's
// needed in advance (bug 765799).
int32_t newOffset;
uint32_t newOffset;
if (referenceNode) {
newOffset = IndexOf(referenceNode);
int32_t indexInParent = IndexOf(referenceNode);
if (NS_WARN_IF(indexInParent < 0)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
newOffset = static_cast<uint32_t>(indexInParent);
} else {
uint32_t length;
aRv = tChildList->GetLength(&length);
aRv = tChildList->GetLength(&newOffset);
if (aRv.Failed()) {
return;
}
newOffset = length;
}
if (aNode.NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
@ -3120,11 +3173,16 @@ nsRange::CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
Sequence<nsString>* aTextList,
nsRange* aRange,
nsINode* aStartContainer,
int32_t aStartOffset,
uint32_t aStartOffset,
nsINode* aEndContainer,
int32_t aEndOffset,
uint32_t aEndOffset,
bool aClampToEdge, bool aFlushLayout)
{
// Currently, this method is called with start of end offset of nsRange.
// So, they must be between 0 - INT32_MAX.
MOZ_ASSERT(IsValidOffset(aStartOffset));
MOZ_ASSERT(IsValidOffset(aEndOffset));
// Hold strong pointers across the flush
nsCOMPtr<nsINode> startContainer = aStartContainer;
nsCOMPtr<nsINode> endContainer = aEndContainer;
@ -3155,13 +3213,15 @@ nsRange::CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
if (textFrame) {
int32_t outOffset;
nsIFrame* outFrame;
textFrame->GetChildFrameContainingOffset(aStartOffset, false,
&outOffset, &outFrame);
textFrame->GetChildFrameContainingOffset(
static_cast<int32_t>(aStartOffset), false,
&outOffset, &outFrame);
if (outFrame) {
nsIFrame* relativeTo =
nsLayoutUtils::GetContainingBlockForClientRect(outFrame);
nsRect r = outFrame->GetRectRelativeToSelf();
ExtractRectFromOffset(outFrame, aStartOffset, &r, false, aClampToEdge);
ExtractRectFromOffset(outFrame, static_cast<int32_t>(aStartOffset),
&r, false, aClampToEdge);
r.width = 0;
r = nsLayoutUtils::TransformFrameRectToAncestor(outFrame, r, relativeTo);
aCollector->AddRect(r);
@ -3180,12 +3240,14 @@ nsRange::CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
if (content->IsNodeOfType(nsINode::eTEXT)) {
if (node == startContainer) {
int32_t offset = startContainer == endContainer ?
aEndOffset : content->GetText()->GetLength();
GetPartialTextRect(aCollector, aTextList, content, aStartOffset, offset,
static_cast<int32_t>(aEndOffset) : content->GetText()->GetLength();
GetPartialTextRect(aCollector, aTextList, content,
static_cast<int32_t>(aStartOffset), offset,
aClampToEdge, aFlushLayout);
continue;
} else if (node == endContainer) {
GetPartialTextRect(aCollector, aTextList, content, 0, aEndOffset,
GetPartialTextRect(aCollector, aTextList, content,
0, static_cast<int32_t>(aEndOffset),
aClampToEdge, aFlushLayout);
continue;
}
@ -3560,7 +3622,7 @@ ElementIsVisibleNoFlush(Element* aElement)
static void
AppendTransformedText(InnerTextAccumulator& aResult,
nsGenericDOMDataNode* aTextNode,
int32_t aStart, int32_t aEnd)
uint32_t aStart, uint32_t aEnd)
{
nsIFrame* frame = aTextNode->GetPrimaryFrame();
if (!IsVisibleAndNotInReplacedElement(frame)) {
@ -3669,7 +3731,7 @@ nsRange::GetInnerTextNoFlush(DOMString& aValue, ErrorResult& aError,
if (aEndContainer->IsNodeOfType(nsINode::eTEXT)) {
endState = AT_NODE;
} else {
if (uint32_t(aEndOffset) < aEndContainer->GetChildCount()) {
if (aEndOffset < aEndContainer->GetChildCount()) {
endNode = aEndContainer->GetChildAt(aEndOffset);
endState = AT_NODE;
}

View File

@ -47,14 +47,20 @@ class nsRange final : public nsIDOMRange,
public:
explicit nsRange(nsINode* aNode);
static nsresult CreateRange(nsIDOMNode* aStartContainer, int32_t aStartOffset,
nsIDOMNode* aEndContainer, int32_t aEndOffset,
static nsresult CreateRange(nsIDOMNode* aStartContainer,
uint32_t aStartOffset,
nsIDOMNode* aEndContainer,
uint32_t aEndOffset,
nsRange** aRange);
static nsresult CreateRange(nsIDOMNode* aStartContainer, int32_t aStartOffset,
nsIDOMNode* aEndContainer, int32_t aEndOffset,
static nsresult CreateRange(nsIDOMNode* aStartContainer,
uint32_t aStartOffset,
nsIDOMNode* aEndContainer,
uint32_t aEndOffset,
nsIDOMRange** aRange);
static nsresult CreateRange(nsINode* aStartContainer, int32_t aStartOffset,
nsINode* aEndContainer, int32_t aEndOffset,
static nsresult CreateRange(nsINode* aStartContainer,
uint32_t aStartOffset,
nsINode* aEndContainer,
uint32_t aEndOffset,
nsRange** aRange);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@ -83,12 +89,12 @@ public:
return mEndContainer;
}
int32_t StartOffset() const
uint32_t StartOffset() const
{
return mStartOffset;
}
int32_t EndOffset() const
uint32_t EndOffset() const
{
return mEndOffset;
}
@ -147,8 +153,8 @@ public:
* When you set both start and end of a range, you should use
* SetStartAndEnd() instead.
*/
nsresult SetStart(nsINode* aContainer, int32_t aOffset);
nsresult SetEnd(nsINode* aContainer, int32_t aOffset);
nsresult SetStart(nsINode* aContainer, uint32_t aOffset);
nsresult SetEnd(nsINode* aContainer, uint32_t aOffset);
already_AddRefed<nsRange> CloneRange() const;
@ -160,15 +166,15 @@ public:
* collapsed at the end point. Similarly, if they are in different root,
* the range will be collapsed at the end point.
*/
nsresult SetStartAndEnd(nsINode* aStartContainer, int32_t aStartOffset,
nsINode* aEndContainer, int32_t aEndOffset);
nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
nsINode* aEndContainer, uint32_t aEndOffset);
/**
* CollapseTo() works similar to call both SetStart() and SetEnd() with
* same node and offset. This just calls SetStartAndParent() to set
* collapsed range at aContainer and aOffset.
*/
nsresult CollapseTo(nsINode* aContainer, int32_t aOffset)
nsresult CollapseTo(nsINode* aContainer, uint32_t aOffset)
{
return SetStartAndEnd(aContainer, aOffset, aContainer, aOffset);
}
@ -177,23 +183,36 @@ public:
* Retrieves node and offset for setting start or end of a range to
* before or after aNode.
*/
static nsINode* GetContainerAndOffsetAfter(nsINode* aNode, int32_t* aOffset)
static nsINode* GetContainerAndOffsetAfter(nsINode* aNode, uint32_t* aOffset)
{
MOZ_ASSERT(aNode);
MOZ_ASSERT(aOffset);
*aOffset = 0;
nsINode* parentNode = aNode->GetParentNode();
*aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
if (*aOffset >= 0) {
(*aOffset)++;
if (!parentNode) {
return nullptr;
}
int32_t indexInParent = parentNode->IndexOf(aNode);
if (NS_WARN_IF(indexInParent < 0)) {
return nullptr;
}
*aOffset = static_cast<uint32_t>(indexInParent) + 1;
return parentNode;
}
static nsINode* GetContainerAndOffsetBefore(nsINode* aNode, int32_t* aOffset)
static nsINode* GetContainerAndOffsetBefore(nsINode* aNode, uint32_t* aOffset)
{
MOZ_ASSERT(aNode);
MOZ_ASSERT(aOffset);
*aOffset = 0;
nsINode* parentNode = aNode->GetParentNode();
*aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
if (!parentNode) {
return nullptr;
}
int32_t indexInParent = parentNode->IndexOf(aNode);
if (NS_WARN_IF(indexInParent < 0)) {
return nullptr;
}
*aOffset = static_cast<uint32_t>(indexInParent);
return parentNode;
}
@ -327,9 +346,9 @@ public:
mozilla::dom::Sequence<nsString>* aTextList,
nsRange* aRange,
nsINode* aStartContainer,
int32_t aStartOffset,
uint32_t aStartOffset,
nsINode* aEndContainer,
int32_t aEndOffset,
uint32_t aEndOffset,
bool aClampToEdge, bool aFlushLayout);
/**
@ -350,14 +369,25 @@ protected:
void RegisterCommonAncestor(nsINode* aNode);
void UnregisterCommonAncestor(nsINode* aNode);
nsINode* IsValidBoundary(nsINode* aNode);
static bool IsValidOffset(nsINode* aNode, int32_t aOffset);
/**
* XXX nsRange should accept 0 - UINT32_MAX as offset. However, users of
* nsRange treat offset as int32_t. Additionally, some other internal
* APIs like nsINode::IndexOf() use int32_t. Therefore, nsRange should
* accept only 0 - INT32_MAX as valid offset for now.
*/
static bool IsValidOffset(uint32_t aOffset)
{
return aOffset <= INT32_MAX;
}
static bool IsValidOffset(nsINode* aNode, uint32_t aOffset);
// CharacterDataChanged set aNotInsertedYet to true to disable an assertion
// and suppress re-registering a range common ancestor node since
// the new text node of a splitText hasn't been inserted yet.
// CharacterDataChanged does the re-registering when needed.
void DoSetRange(nsINode* aStartN, int32_t aStartOffset,
nsINode* aEndN, int32_t aEndOffset,
void DoSetRange(nsINode* aStartN, uint32_t aStartOffset,
nsINode* aEndN, uint32_t aEndOffset,
nsINode* aRoot, bool aNotInsertedYet = false);
/**
@ -430,8 +460,8 @@ protected:
nsCOMPtr<nsINode> mStartContainer;
nsCOMPtr<nsINode> mEndContainer;
RefPtr<mozilla::dom::Selection> mSelection;
int32_t mStartOffset;
int32_t mEndOffset;
uint32_t mStartOffset;
uint32_t mEndOffset;
bool mIsPositioned : 1;
bool mMaySpanAnonymousSubtrees : 1;

View File

@ -505,20 +505,23 @@ GamepadManager::Update(const GamepadChangeEvent& aEvent)
return;
}
if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
const GamepadAdded& a = aEvent.get_GamepadAdded();
AddGamepad(a.index(), a.id(),
const uint32_t index = aEvent.index();
GamepadServiceType serviceType = aEvent.service_type();
GamepadChangeEventBody body = aEvent.body();
if (body.type() == GamepadChangeEventBody::TGamepadAdded) {
const GamepadAdded& a = body.get_GamepadAdded();
AddGamepad(index, a.id(),
static_cast<GamepadMappingType>(a.mapping()),
static_cast<GamepadHand>(a.hand()),
a.service_type(),
serviceType,
a.display_id(),
a.num_buttons(), a.num_axes(),
a.num_haptics());
return;
}
if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
const GamepadRemoved& a = aEvent.get_GamepadRemoved();
RemoveGamepad(a.index(), a.service_type());
if (body.type() == GamepadChangeEventBody::TGamepadRemoved) {
RemoveGamepad(index, serviceType);
return;
}
@ -555,96 +558,75 @@ GamepadManager::MaybeConvertToNonstandardGamepadEvent(const GamepadChangeEvent&
return;
}
RefPtr<Gamepad> gamepad;
RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aEvent.index());
const GamepadChangeEventBody& body = aEvent.body();
switch (aEvent.type()) {
case GamepadChangeEvent::TGamepadButtonInformation:
if (gamepad) {
switch (body.type()) {
case GamepadChangeEventBody::TGamepadButtonInformation:
{
const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
gamepad = aWindow->GetGamepad(a.index());
if (gamepad) {
FireButtonEvent(aWindow, gamepad, a.button(), a.value());
}
const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
FireButtonEvent(aWindow, gamepad, a.button(), a.value());
break;
}
break;
case GamepadChangeEvent::TGamepadAxisInformation:
case GamepadChangeEventBody::TGamepadAxisInformation:
{
const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
gamepad = aWindow->GetGamepad(a.index());
if (gamepad) {
FireAxisMoveEvent(aWindow, gamepad, a.axis(), a.value());
}
const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
FireAxisMoveEvent(aWindow, gamepad, a.axis(), a.value());
break;
}
break;
default:
break;
default:
break;
}
}
}
bool
GamepadManager::SetGamepadByEvent(const GamepadChangeEvent& aEvent, nsGlobalWindow *aWindow)
{
uint32_t index;
RefPtr<Gamepad> gamepad;
bool ret = false;
bool firstTime = false;
switch (aEvent.type()) {
case GamepadChangeEvent::TGamepadButtonInformation:
{
const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
index = GetGamepadIndexWithServiceType(a.index(), a.service_type());
if (aWindow) {
firstTime = MaybeWindowHasSeenGamepad(aWindow, index);
}
gamepad = aWindow ? aWindow->GetGamepad(index) : GetGamepad(index);
if (gamepad) {
const uint32_t index = GetGamepadIndexWithServiceType(aEvent.index(),
aEvent.service_type());
if (aWindow) {
firstTime = MaybeWindowHasSeenGamepad(aWindow, index);
}
RefPtr<Gamepad> gamepad = aWindow ? aWindow->GetGamepad(index) : GetGamepad(index);
const GamepadChangeEventBody& body = aEvent.body();
if (gamepad) {
switch (body.type()) {
case GamepadChangeEventBody::TGamepadButtonInformation:
{
const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
gamepad->SetButton(a.button(), a.pressed(), a.touched(), a.value());
ret = true;
break;
}
} break;
case GamepadChangeEvent::TGamepadAxisInformation:
{
const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
index = GetGamepadIndexWithServiceType(a.index(), a.service_type());
if (aWindow) {
firstTime = MaybeWindowHasSeenGamepad(aWindow, index);
}
gamepad = aWindow ? aWindow->GetGamepad(index) : GetGamepad(index);
if (gamepad) {
case GamepadChangeEventBody::TGamepadAxisInformation:
{
const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
gamepad->SetAxis(a.axis(), a.value());
ret = true;
break;
}
} break;
case GamepadChangeEvent::TGamepadPoseInformation:
{
const GamepadPoseInformation& a = aEvent.get_GamepadPoseInformation();
index = GetGamepadIndexWithServiceType(a.index(), a.service_type());
if (aWindow) {
firstTime = MaybeWindowHasSeenGamepad(aWindow, index);
}
gamepad = aWindow ? aWindow->GetGamepad(index) : GetGamepad(index);
if (gamepad) {
case GamepadChangeEventBody::TGamepadPoseInformation:
{
const GamepadPoseInformation& a = body.get_GamepadPoseInformation();
gamepad->SetPose(a.pose_state());
ret = true;
break;
}
} break;
case GamepadChangeEvent::TGamepadHandInformation:
{
const GamepadHandInformation& a = aEvent.get_GamepadHandInformation();
index = GetGamepadIndexWithServiceType(a.index(), a.service_type());
if (aWindow) {
firstTime = MaybeWindowHasSeenGamepad(aWindow, index);
}
gamepad = aWindow ? aWindow->GetGamepad(index) : GetGamepad(index);
if (gamepad) {
case GamepadChangeEventBody::TGamepadHandInformation:
{
const GamepadHandInformation& a = body.get_GamepadHandInformation();
gamepad->SetHand(a.hand());
ret = true;
break;
}
} break;
default:
MOZ_ASSERT(false);
break;
default:
MOZ_ASSERT(false);
break;
}
ret = true;
}
if (aWindow && firstTime) {

View File

@ -58,14 +58,15 @@ GamepadPlatformService::GetParentService()
template<class T>
void
GamepadPlatformService::NotifyGamepadChange(const T& aInfo)
GamepadPlatformService::NotifyGamepadChange(uint32_t aIndex, const T& aInfo)
{
// This method is called by monitor populated in
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadChangeEvent e(aInfo);
GamepadChangeEventBody body(aInfo);
GamepadChangeEvent e(aIndex, GamepadServiceType::Standard, body);
// mChannelParents may be accessed by background thread in the
// same time, we use mutex to prevent possible race condtion
@ -96,12 +97,12 @@ GamepadPlatformService::AddGamepad(const char* aID,
MOZ_ASSERT(!NS_IsMainThread());
uint32_t index = ++mGamepadIndex;
// Only VR controllers has displayID, we give 0 to the general gamepads.
GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
aMapping, aHand, GamepadServiceType::Standard,
0, aNumButtons, aNumAxes, aHaptics);
NotifyGamepadChange<GamepadAdded>(a);
// Only VR controllers has displayID, we give 0 to the general gamepads.
GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)),
aMapping, aHand, 0, aNumButtons, aNumAxes, aHaptics);
NotifyGamepadChange<GamepadAdded>(index, a);
return index;
}
@ -112,8 +113,8 @@ GamepadPlatformService::RemoveGamepad(uint32_t aIndex)
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadRemoved a(aIndex, GamepadServiceType::Standard);
NotifyGamepadChange<GamepadRemoved>(a);
GamepadRemoved a;
NotifyGamepadChange<GamepadRemoved>(aIndex, a);
}
void
@ -125,9 +126,8 @@ GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadButtonInformation a(aIndex, GamepadServiceType::Standard,
aButton, aValue, aPressed, aTouched);
NotifyGamepadChange<GamepadButtonInformation>(a);
GamepadButtonInformation a(aButton, aValue, aPressed, aTouched);
NotifyGamepadChange<GamepadButtonInformation>(aIndex, a);
}
void
@ -162,9 +162,8 @@ GamepadPlatformService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadAxisInformation a(aIndex, GamepadServiceType::Standard,
aAxis, aValue);
NotifyGamepadChange<GamepadAxisInformation>(a);
GamepadAxisInformation a(aAxis, aValue);
NotifyGamepadChange<GamepadAxisInformation>(aIndex, a);
}
void
@ -175,9 +174,8 @@ GamepadPlatformService::NewPoseEvent(uint32_t aIndex,
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadPoseInformation a(aIndex, GamepadServiceType::Standard,
aPose);
NotifyGamepadChange<GamepadPoseInformation>(a);
GamepadPoseInformation a(aPose);
NotifyGamepadChange<GamepadPoseInformation>(aIndex, a);
}
void

View File

@ -81,7 +81,7 @@ class GamepadPlatformService final
private:
GamepadPlatformService();
~GamepadPlatformService();
template<class T> void NotifyGamepadChange(const T& aInfo);
template<class T> void NotifyGamepadChange(uint32_t aIndex, const T& aInfo);
// Flush all pending events buffered in mPendingEvents, must be called
// with mMutex held

View File

@ -124,11 +124,11 @@ GamepadServiceTest::AddGamepad(const nsAString& aID,
}
// Only VR controllers has displayID, we give 0 to the general gamepads.
GamepadAdded a(nsString(aID), 0,
aMapping, aHand,
GamepadServiceType::Standard, 0,
GamepadAdded a(nsString(aID),
aMapping, aHand, 0,
aNumButtons, aNumAxes, aNumHaptics);
GamepadChangeEvent e(a);
GamepadChangeEventBody body(a);
GamepadChangeEvent e(0, GamepadServiceType::Standard, body);
nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
RefPtr<Promise> p = Promise::Create(go, aRv);
@ -154,8 +154,9 @@ GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
return;
}
GamepadRemoved a(aIndex, GamepadServiceType::Standard);
GamepadChangeEvent e(a);
GamepadRemoved a;
GamepadChangeEventBody body(a);
GamepadChangeEvent e(aIndex, GamepadServiceType::Standard, body);
uint32_t id = ++mEventNumber;
if (mChild) {
@ -176,9 +177,9 @@ GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
return;
}
GamepadButtonInformation a(aIndex, GamepadServiceType::Standard,
aButton, aPressed ? 1.0 : 0, aPressed, aTouched);
GamepadChangeEvent e(a);
GamepadButtonInformation a(aButton, aPressed ? 1.0 : 0, aPressed, aTouched);
GamepadChangeEventBody body(a);
GamepadChangeEvent e(aIndex, GamepadServiceType::Standard, body);
uint32_t id = ++mEventNumber;
if (mChild) {
@ -200,9 +201,9 @@ GamepadServiceTest::NewButtonValueEvent(uint32_t aIndex,
return;
}
GamepadButtonInformation a(aIndex, GamepadServiceType::Standard,
aButton, aValue, aPressed, aTouched);
GamepadChangeEvent e(a);
GamepadButtonInformation a(aButton, aValue, aPressed, aTouched);
GamepadChangeEventBody body(a);
GamepadChangeEvent e(aIndex, GamepadServiceType::Standard, body);
uint32_t id = ++mEventNumber;
if (mChild) {
@ -222,9 +223,9 @@ GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
return;
}
GamepadAxisInformation a(aIndex, GamepadServiceType::Standard,
aAxis, aValue);
GamepadChangeEvent e(a);
GamepadAxisInformation a(aAxis, aValue);
GamepadChangeEventBody body(a);
GamepadChangeEvent e(aIndex, GamepadServiceType::Standard, body);
uint32_t id = ++mEventNumber;
if (mChild) {
@ -305,9 +306,9 @@ GamepadServiceTest::NewPoseMove(uint32_t aIndex,
poseState.linearAcceleration[2] = value.Data()[2];
}
GamepadPoseInformation a(aIndex, GamepadServiceType::Standard,
poseState);
GamepadChangeEvent e(a);
GamepadPoseInformation a(poseState);
GamepadChangeEventBody body(a);
GamepadChangeEvent e(aIndex, GamepadServiceType::Standard, body);
uint32_t id = ++mEventNumber;
if (mChild) {

View File

@ -13,31 +13,22 @@ namespace dom {
struct GamepadAdded {
nsString id;
uint32_t index;
GamepadMappingType mapping;
GamepadHand hand;
GamepadServiceType service_type;
uint32_t display_id;
uint32_t num_buttons;
uint32_t num_axes;
uint32_t num_haptics;
};
struct GamepadRemoved {
uint32_t index;
GamepadServiceType service_type;
};
struct GamepadRemoved {};
struct GamepadAxisInformation {
uint32_t index;
GamepadServiceType service_type;
uint32_t axis;
double value;
};
struct GamepadButtonInformation {
uint32_t index;
GamepadServiceType service_type;
uint32_t button;
double value;
bool pressed;
@ -45,18 +36,14 @@ struct GamepadButtonInformation {
};
struct GamepadPoseInformation {
uint32_t index;
GamepadServiceType service_type;
GamepadPoseState pose_state;
};
struct GamepadHandInformation {
uint32_t index;
GamepadServiceType service_type;
GamepadHand hand;
};
union GamepadChangeEvent {
union GamepadChangeEventBody {
GamepadAdded;
GamepadRemoved;
GamepadAxisInformation;
@ -65,5 +52,11 @@ union GamepadChangeEvent {
GamepadHandInformation;
};
struct GamepadChangeEvent {
uint32_t index;
GamepadServiceType service_type;
GamepadChangeEventBody body;
};
} // namespace dom
} // namespace mozilla

View File

@ -18,8 +18,10 @@ GamepadTestChannelParent::RecvGamepadTestEvent(const uint32_t& aID,
RefPtr<GamepadPlatformService> service =
GamepadPlatformService::GetParentService();
MOZ_ASSERT(service);
if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
const GamepadAdded& a = aEvent.get_GamepadAdded();
const uint32_t index = aEvent.index();
const GamepadChangeEventBody& body = aEvent.body();
if (body.type() == GamepadChangeEventBody::TGamepadAdded) {
const GamepadAdded& a = body.get_GamepadAdded();
nsCString gamepadID;
LossyCopyUTF16toASCII(a.id(), gamepadID);
uint32_t index = service->AddGamepad(gamepadID.get(),
@ -33,25 +35,24 @@ GamepadTestChannelParent::RecvGamepadTestEvent(const uint32_t& aID,
}
return IPC_OK();
}
if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
const GamepadRemoved& a = aEvent.get_GamepadRemoved();
service->RemoveGamepad(a.index());
if (body.type() == GamepadChangeEventBody::TGamepadRemoved) {
service->RemoveGamepad(index);
return IPC_OK();
}
if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) {
const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
service->NewButtonEvent(a.index(), a.button(), a.pressed(), a.touched(),
if (body.type() == GamepadChangeEventBody::TGamepadButtonInformation) {
const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
service->NewButtonEvent(index, a.button(), a.pressed(), a.touched(),
a.value());
return IPC_OK();
}
if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) {
const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
service->NewAxisMoveEvent(a.index(), a.axis(), a.value());
if (body.type() == GamepadChangeEventBody::TGamepadAxisInformation) {
const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
service->NewAxisMoveEvent(index, a.axis(), a.value());
return IPC_OK();
}
if (aEvent.type() == GamepadChangeEvent::TGamepadPoseInformation) {
const GamepadPoseInformation& a = aEvent.get_GamepadPoseInformation();
service->NewPoseEvent(a.index(), a.pose_state());
if (body.type() == GamepadChangeEventBody::TGamepadPoseInformation) {
const GamepadPoseInformation& a = body.get_GamepadPoseInformation();
service->NewPoseEvent(index, a.pose_state());
return IPC_OK();
}

View File

@ -16,14 +16,14 @@
interface nsIDOMRange : nsISupports
{
readonly attribute nsIDOMNode startContainer;
readonly attribute long startOffset;
readonly attribute unsigned long startOffset;
readonly attribute nsIDOMNode endContainer;
readonly attribute long endOffset;
readonly attribute unsigned long endOffset;
readonly attribute boolean collapsed;
readonly attribute nsIDOMNode commonAncestorContainer;
void setStart(in nsIDOMNode refNode, in long offset);
void setEnd(in nsIDOMNode refNode, in long offset);
void setStart(in nsIDOMNode refNode, in unsigned long offset);
void setEnd(in nsIDOMNode refNode, in unsigned long offset);
void setStartBefore(in nsIDOMNode refNode);
void setStartAfter(in nsIDOMNode refNode);
void setEndBefore(in nsIDOMNode refNode);
@ -56,14 +56,14 @@ interface nsIDOMRange : nsISupports
// This returns true if parent+offset equals either
// of the boundary points or is between them.
boolean isPointInRange(in nsIDOMNode parent,
in long offset);
in unsigned long offset);
// comparePoint returns
// -1 if point is before the start boundary point,
// 0 if point is either of the boundary points or between them,
// 1 if point is after the end boundary point.
// Sort of a strcmp for ranges.
short comparePoint(in nsIDOMNode parent, in long offset);
short comparePoint(in nsIDOMNode parent, in unsigned long offset);
/**
* Returns whether the range intersects node.

View File

@ -1020,8 +1020,9 @@ ContentChild::ProvideWindowCommon(TabChild* aTabOpener,
nsCOMPtr<nsILoadContext> context = do_QueryInterface(openerShell);
showInfo = ShowInfo(EmptyString(), false,
context->UsePrivateBrowsing(), true, false,
aTabOpener->mDPI, aTabOpener->mRounding,
aTabOpener->mDefaultScale);
aTabOpener->WebWidget()->GetDPI(),
aTabOpener->WebWidget()->RoundsWidgetCoordinatesTo(),
aTabOpener->WebWidget()->GetDefaultScale().scale);
}
newChild->SetMaxTouchPoints(maxTouchPoints);

View File

@ -87,6 +87,7 @@ struct ScreenDetails {
int32_t colorDepth;
DesktopToLayoutDeviceScale contentsScaleFactor;
CSSToLayoutDeviceScale defaultCSSScaleFactor;
float dpi;
};
struct DimensionInfo

View File

@ -359,21 +359,6 @@ parent:
sync IsParentWindowMainWidgetVisible() returns (bool visible);
/**
* Gets the DPI of the screen corresponding to this browser.
*/
sync GetDPI() returns (float value);
/**
* Gets the default scaling factor of the screen corresponding to this browser.
*/
sync GetDefaultScale() returns (double value);
/**
* Gets the rounding of coordinates in the widget.
*/
sync GetWidgetRounding() returns (int32_t value);
/**
* Set the native cursor.
* @param value

View File

@ -390,9 +390,6 @@ TabChild::TabChild(nsIContentChild* aManager,
, mHasValidInnerSize(false)
, mDestroyed(false)
, mUniqueId(aTabId)
, mDPI(0)
, mRounding(0)
, mDefaultScale(0)
, mIsTransparent(false)
, mIPCOpen(false)
, mParentIsActive(false)
@ -1135,6 +1132,15 @@ TabChild::DoFakeShow(const TextureFactoryIdentifier& aTextureFactoryIdentifier,
void
TabChild::ApplyShowInfo(const ShowInfo& aInfo)
{
// Even if we already set real show info, the dpi / rounding & scale may still
// be invalid (if TabParent wasn't able to get widget it would just send 0).
// So better to always set up-to-date values here.
if (aInfo.dpi() > 0) {
mPuppetWidget->UpdateBackingScaleCache(aInfo.dpi(),
aInfo.widgetRounding(),
aInfo.defaultScale());
}
if (mDidSetRealShowInfo) {
return;
}
@ -1175,9 +1181,6 @@ TabChild::ApplyShowInfo(const ShowInfo& aInfo)
}
}
}
mDPI = aInfo.dpi();
mRounding = aInfo.widgetRounding();
mDefaultScale = aInfo.defaultScale();
mIsTransparent = aInfo.isTransparent();
}
@ -2691,56 +2694,6 @@ TabChild::InitAPZState()
cbc->SendPAPZConstructor(apzChild, mLayersId);
}
void
TabChild::GetDPI(float* aDPI)
{
*aDPI = -1.0;
if (!(mDidFakeShow || mDidSetRealShowInfo)) {
return;
}
if (mDPI > 0) {
*aDPI = mDPI;
return;
}
// Fallback to a sync call if needed.
SendGetDPI(aDPI);
}
void
TabChild::GetDefaultScale(double* aScale)
{
*aScale = -1.0;
if (!(mDidFakeShow || mDidSetRealShowInfo)) {
return;
}
if (mDefaultScale > 0) {
*aScale = mDefaultScale;
return;
}
// Fallback to a sync call if needed.
SendGetDefaultScale(aScale);
}
void
TabChild::GetWidgetRounding(int32_t* aRounding)
{
*aRounding = 1;
if (!(mDidFakeShow || mDidSetRealShowInfo)) {
return;
}
if (mRounding > 0) {
*aRounding = mRounding;
return;
}
// Fallback to a sync call if needed.
SendGetWidgetRounding(aRounding);
}
void
TabChild::NotifyPainted()
{
@ -3138,10 +3091,9 @@ TabChild::RecvUIResolutionChanged(const float& aDpi,
const double& aScale)
{
ScreenIntSize oldScreenSize = GetInnerSize();
mDPI = 0;
mRounding = 0;
mDefaultScale = 0;
static_cast<PuppetWidget*>(mPuppetWidget.get())->UpdateBackingScaleCache(aDpi, aRounding, aScale);
if (aDpi > 0) {
mPuppetWidget->UpdateBackingScaleCache(aDpi, aRounding, aScale);
}
nsCOMPtr<nsIDocument> document(GetDocument());
nsCOMPtr<nsIPresShell> presShell = document->GetShell();
if (presShell) {

View File

@ -502,13 +502,6 @@ public:
virtual PuppetWidget* WebWidget() override { return mPuppetWidget; }
/** Return the DPI of the widget this TabChild draws to. */
void GetDPI(float* aDPI);
void GetDefaultScale(double *aScale);
void GetWidgetRounding(int32_t* aRounding);
bool IsTransparent() const { return mIsTransparent; }
void GetMaxTouchPoints(uint32_t* aTouchPoints)
@ -855,9 +848,6 @@ private:
Maybe<mozilla::layers::CompositorOptions> mCompositorOptions;
friend class ContentChild;
float mDPI;
int32_t mRounding;
double mDefaultScale;
bool mIsTransparent;

View File

@ -2349,39 +2349,6 @@ TabParent::RecvIsParentWindowMainWidgetVisible(bool* aIsVisible)
return IPC_OK();
}
mozilla::ipc::IPCResult
TabParent::RecvGetDPI(float* aValue)
{
TryCacheDPIAndScale();
MOZ_ASSERT(mDPI > 0 || mFrameElement,
"Must not ask for DPI before OwnerElement is received!");
*aValue = mDPI;
return IPC_OK();
}
mozilla::ipc::IPCResult
TabParent::RecvGetDefaultScale(double* aValue)
{
TryCacheDPIAndScale();
MOZ_ASSERT(mDefaultScale.scale > 0 || mFrameElement,
"Must not ask for scale before OwnerElement is received!");
*aValue = mDefaultScale.scale;
return IPC_OK();
}
mozilla::ipc::IPCResult
TabParent::RecvGetWidgetRounding(int32_t* aValue)
{
TryCacheDPIAndScale();
MOZ_ASSERT(mRounding > 0 || mFrameElement,
"Must not ask for rounding before OwnerElement is received!");
*aValue = mRounding;
return IPC_OK();
}
already_AddRefed<nsIWidget>
TabParent::GetTopLevelWidget()
{

View File

@ -299,11 +299,6 @@ public:
virtual mozilla::ipc::IPCResult RecvHideTooltip() override;
virtual mozilla::ipc::IPCResult RecvGetDPI(float* aValue) override;
virtual mozilla::ipc::IPCResult RecvGetDefaultScale(double* aValue) override;
virtual mozilla::ipc::IPCResult RecvGetWidgetRounding(int32_t* aValue) override;
virtual mozilla::ipc::IPCResult RecvSetNativeChildOfShareableWindow(const uintptr_t& childWindow) override;

View File

@ -457,7 +457,9 @@ IsAACCodecString(const nsAString& aCodec)
{
return
aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
aCodec.EqualsLiteral("mp4a.40.02") || // MPEG4 AAC-LC(for compatibility)
aCodec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
aCodec.EqualsLiteral("mp4a.40.05") || // MPEG4 HE-AAC(for compatibility)
aCodec.EqualsLiteral("mp4a.67") || // MPEG2 AAC-LC
aCodec.EqualsLiteral("mp4a.40.29"); // MPEG4 HE-AACv2
}

View File

@ -324,11 +324,28 @@ private:
* a constructor. Please add below (UniquePtr covers a lot of ground though).
*/
template<typename T>
class Refcountable : public T
class RefcountableBase
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable<T>)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefcountableBase)
protected:
virtual ~RefcountableBase() {}
};
template<typename T>
class Refcountable : public T, public RefcountableBase
{
public:
NS_METHOD_(MozExternalRefCountType) AddRef()
{
return RefcountableBase::AddRef();
}
NS_METHOD_(MozExternalRefCountType) Release()
{
return RefcountableBase::Release();
}
private:
~Refcountable<T>() {}
};

View File

@ -1057,7 +1057,6 @@ skip-if = android_version == '17' # android(bug 1232305)
[test_standalone.html]
skip-if = toolkit == 'android' # bug 1372457
[test_streams_autoplay.html]
skip-if = toolkit == 'android' # bug 1372457
tags=msg capturestream
[test_streams_capture_origin.html]
skip-if = toolkit == 'android' # bug 1372457

View File

@ -354,7 +354,7 @@ public:
void GetSettings(dom::MediaTrackSettings& aOutSettings)
{
MOZ_ASSERT(NS_IsMainThread());
aOutSettings = mSettings;
aOutSettings = *mSettings;
}
protected:
@ -362,6 +362,7 @@ protected:
explicit MediaEngineSource(MediaEngineState aState)
: mState(aState)
, mInShutdown(false)
, mSettings(MakeRefPtr<media::Refcountable<dom::MediaTrackSettings>>())
{}
/* UpdateSingleSource - Centralized abstract function to implement in those
@ -448,8 +449,10 @@ protected:
nsTArray<RefPtr<AllocationHandle>> mRegisteredHandles;
bool mInShutdown;
// Main-thread only:
dom::MediaTrackSettings mSettings;
// The following is accessed on main-thread only. It has its own ref-count to
// avoid ref-counting MediaEngineSource itself in runnables.
// (MediaEngineSource subclasses balk on ref-counts too late during shutdown.)
RefPtr<media::Refcountable<dom::MediaTrackSettings>> mSettings;
};
class MediaEngineVideoSource : public MediaEngineSource

View File

@ -60,8 +60,6 @@ public:
const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
const nsString& aDeviceId) const override;
void Shutdown() override {};
protected:
struct CapabilityCandidate {
explicit CapabilityCandidate(uint8_t index, uint32_t distance = 0)

View File

@ -36,9 +36,9 @@ MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
mScary(aScary)
{
MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
mSettings.mWidth.Construct(0);
mSettings.mHeight.Construct(0);
mSettings.mFrameRate.Construct(0);
mSettings->mWidth.Construct(0);
mSettings->mHeight.Construct(0);
mSettings->mFrameRate.Construct(0);
Init();
}
@ -328,12 +328,12 @@ MediaEngineRemoteVideoSource::SetLastCapability(
default:
break;
}
RefPtr<MediaEngineRemoteVideoSource> that = this;
auto settings = mSettings;
NS_DispatchToMainThread(media::NewRunnableFrom([that, cap]() mutable {
that->mSettings.mWidth.Value() = cap.width;
that->mSettings.mHeight.Value() = cap.height;
that->mSettings.mFrameRate.Value() = cap.maxFPS;
NS_DispatchToMainThread(media::NewRunnableFrom([settings, cap]() mutable {
settings->mWidth.Value() = cap.width;
settings->mHeight.Value() = cap.height;
settings->mFrameRate.Value() = cap.maxFPS;
return NS_OK;
}));
}
@ -371,10 +371,10 @@ MediaEngineRemoteVideoSource::FrameSizeChange(unsigned int w, unsigned int h)
mWidth = w;
mHeight = h;
RefPtr<MediaEngineRemoteVideoSource> that = this;
NS_DispatchToMainThread(media::NewRunnableFrom([that, w, h]() mutable {
that->mSettings.mWidth.Value() = w;
that->mSettings.mHeight.Value() = h;
auto settings = mSettings;
NS_DispatchToMainThread(media::NewRunnableFrom([settings, w, h]() mutable {
settings->mWidth.Value() = w;
settings->mHeight.Value() = h;
return NS_OK;
}));
}

View File

@ -198,10 +198,10 @@ MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource(
mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
mDeviceUUID.Assign(uuid);
mListener = new mozilla::WebRTCAudioDataListener(this);
mSettings.mEchoCancellation.Construct(0);
mSettings.mAutoGainControl.Construct(0);
mSettings.mNoiseSuppression.Construct(0);
mSettings.mChannelCount.Construct(0);
mSettings->mEchoCancellation.Construct(0);
mSettings->mAutoGainControl.Construct(0);
mSettings->mNoiseSuppression.Construct(0);
mSettings->mChannelCount.Construct(0);
// We'll init lazily as needed
}
@ -417,10 +417,10 @@ MediaEngineWebRTCMicrophoneSource::SetLastPrefs(
RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
NS_DispatchToMainThread(media::NewRunnableFrom([that, aPrefs]() mutable {
that->mSettings.mEchoCancellation.Value() = aPrefs.mAecOn;
that->mSettings.mAutoGainControl.Value() = aPrefs.mAgcOn;
that->mSettings.mNoiseSuppression.Value() = aPrefs.mNoiseOn;
that->mSettings.mChannelCount.Value() = aPrefs.mChannels;
that->mSettings->mEchoCancellation.Value() = aPrefs.mAecOn;
that->mSettings->mAutoGainControl.Value() = aPrefs.mAgcOn;
that->mSettings->mNoiseSuppression.Value() = aPrefs.mNoiseOn;
that->mSettings->mChannelCount.Value() = aPrefs.mChannels;
return NS_OK;
}));
}

View File

@ -59,10 +59,6 @@ dictionary RTCOfferOptions : RTCOfferAnswerOptions {
long offerToReceiveVideo;
long offerToReceiveAudio;
boolean iceRestart = false;
// Mozilla proprietary options (at risk: Bug 1196974)
boolean mozDontOfferDataChannel;
boolean mozBundleOnly;
};
interface RTCDataChannel;

View File

@ -0,0 +1,36 @@
<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
<head>
<bindings xmlns="http://www.mozilla.org/xbl">
<binding id="foo"><content><span xmlns="http://www.w3.org/1999/xhtml"/></content></binding>
</bindings>
<script type="text/javascript">
// <![CDATA[
function boom()
{
// Apply an invalid binding, and wait two ticks of the refresh driver for the async load.
var d = document.getElementById("d");
d.style.MozBinding = "url(#bar)";
requestAnimationFrame(function() {
requestAnimationFrame(function() {
// Trigger the InnerText getter.
document.getElementById("s").innerText;
document.documentElement.removeAttribute("class");
});
});
}
]]>
</script>
</head>
<body onload="boom();">
<div id="d">
<span id="s">SpanText</span>
</div>
</body>
</html>

View File

@ -38,3 +38,4 @@ load 507991-1.xhtml
load 830614-1.xul
load 895805-1.xhtml
load set-field-bad-this.xhtml
load 1382357.xhtml

View File

@ -116,7 +116,15 @@ public:
if (!doc)
return;
// Destroy the frames for mBoundElement.
// Get the binding.
bool ready = false;
nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready);
if (!ready)
return;
// Destroy the frames for mBoundElement. Do this after getting the binding,
// since if the binding fetch fails then we don't want to destroy the
// frames.
nsIContent* destroyedFramesFor = nullptr;
nsIPresShell* shell = doc->GetShell();
if (shell) {
@ -124,12 +132,6 @@ public:
}
MOZ_ASSERT(!mBoundElement->GetPrimaryFrame());
// Get the binding.
bool ready = false;
nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready);
if (!ready)
return;
// If |mBoundElement| is (in addition to having binding |mBinding|)
// also a descendant of another element with binding |mBinding|,
// then we might have just constructed it due to the

View File

@ -5319,7 +5319,7 @@ EditorBase::GetIMESelectionStartOffsetIn(nsINode* aTextNode)
return -1;
}
int32_t minOffset = INT32_MAX;
uint32_t minOffset = UINT32_MAX;
static const SelectionType kIMESelectionTypes[] = {
SelectionType::eIMERawClause,
SelectionType::eIMESelectedRawClause,
@ -5339,15 +5339,11 @@ EditorBase::GetIMESelectionStartOffsetIn(nsINode* aTextNode)
if (NS_WARN_IF(range->GetStartContainer() != aTextNode)) {
// ignore the start offset...
} else {
MOZ_ASSERT(range->StartOffset() >= 0,
"start offset shouldn't be negative");
minOffset = std::min(minOffset, range->StartOffset());
}
if (NS_WARN_IF(range->GetEndContainer() != aTextNode)) {
// ignore the end offset...
} else {
MOZ_ASSERT(range->EndOffset() >= 0,
"start offset shouldn't be negative");
minOffset = std::min(minOffset, range->EndOffset());
}
}

View File

@ -458,7 +458,7 @@ HTMLEditRules::AfterEditInner(EditAction action,
NS_ENSURE_STATE(selection);
nsCOMPtr<nsIDOMNode> rangeStartContainer, rangeEndContainer;
int32_t rangeStartOffset = 0, rangeEndOffset = 0;
uint32_t rangeStartOffset = 0, rangeEndOffset = 0;
// do we have a real range to act on?
bool bDamagedRange = false;
if (mDocChangeRange) {
@ -568,8 +568,8 @@ HTMLEditRules::AfterEditInner(EditAction action,
action, selection,
GetAsDOMNode(mRangeItem->mStartContainer),
mRangeItem->mStartOffset,
rangeStartContainer, rangeStartOffset,
rangeEndContainer, rangeEndOffset);
rangeStartContainer, static_cast<int32_t>(rangeStartOffset),
rangeEndContainer, static_cast<int32_t>(rangeEndOffset));
NS_ENSURE_SUCCESS(rv, rv);
// detect empty doc
@ -5317,9 +5317,8 @@ HTMLEditRules::NormalizeSelection(Selection* inSelection)
RefPtr<nsRange> range = inSelection->GetRangeAt(0);
NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIDOMNode> startNode, endNode;
int32_t startOffset, endOffset;
uint32_t startOffset, endOffset;
nsCOMPtr<nsIDOMNode> newStartNode, newEndNode;
int32_t newStartOffset, newEndOffset;
rv = range->GetStartContainer(getter_AddRefs(startNode));
NS_ENSURE_SUCCESS(rv, rv);
@ -5332,22 +5331,22 @@ HTMLEditRules::NormalizeSelection(Selection* inSelection)
// adjusted values default to original values
newStartNode = startNode;
newStartOffset = startOffset;
uint32_t newStartOffset = startOffset;
newEndNode = endNode;
newEndOffset = endOffset;
uint32_t newEndOffset = endOffset;
// some locals we need for whitespace code
nsCOMPtr<nsINode> unused;
int32_t offset;
int32_t offset = -1;
WSType wsType;
// let the whitespace code do the heavy lifting
WSRunObject wsEndObj(mHTMLEditor, endNode, endOffset);
WSRunObject wsEndObj(mHTMLEditor, endNode, static_cast<int32_t>(endOffset));
// is there any intervening visible whitespace? if so we can't push selection past that,
// it would visibly change maening of users selection
nsCOMPtr<nsINode> endNode_(do_QueryInterface(endNode));
wsEndObj.PriorVisibleNode(endNode_, endOffset, address_of(unused),
&offset, &wsType);
wsEndObj.PriorVisibleNode(endNode_, static_cast<int32_t>(endOffset),
address_of(unused), &offset, &wsType);
if (wsType != WSType::text && wsType != WSType::normalWS) {
// eThisBlock and eOtherBlock conveniently distinquish cases
// of going "down" into a block and "up" out of a block.
@ -5357,36 +5356,44 @@ HTMLEditRules::NormalizeSelection(Selection* inSelection)
GetAsDOMNode(mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode,
true));
if (child) {
newEndNode = EditorBase::GetNodeLocation(child, &newEndOffset);
++newEndOffset; // offset *after* child
int32_t offset = -1;
newEndNode = EditorBase::GetNodeLocation(child, &offset);
// offset *after* child
newEndOffset = static_cast<uint32_t>(offset + 1);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsEndObj.mStartReason == WSType::thisBlock) {
// endpoint is just after start of this block
nsCOMPtr<nsIDOMNode> child;
NS_ENSURE_STATE(mHTMLEditor);
mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child));
mHTMLEditor->GetPriorHTMLNode(endNode, static_cast<int32_t>(endOffset),
address_of(child));
if (child) {
newEndNode = EditorBase::GetNodeLocation(child, &newEndOffset);
++newEndOffset; // offset *after* child
int32_t offset = -1;
newEndNode = EditorBase::GetNodeLocation(child, &offset);
// offset *after* child
newEndOffset = static_cast<uint32_t>(offset + 1);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsEndObj.mStartReason == WSType::br) {
// endpoint is just after break. lets adjust it to before it.
int32_t offset = -1;
newEndNode =
EditorBase::GetNodeLocation(GetAsDOMNode(wsEndObj.mStartReasonNode),
&newEndOffset);
&offset);
newEndOffset = static_cast<uint32_t>(offset);;
}
}
// similar dealio for start of range
WSRunObject wsStartObj(mHTMLEditor, startNode, startOffset);
WSRunObject wsStartObj(mHTMLEditor, startNode,
static_cast<int32_t>(startOffset));
// is there any intervening visible whitespace? if so we can't push selection past that,
// it would visibly change maening of users selection
nsCOMPtr<nsINode> startNode_(do_QueryInterface(startNode));
wsStartObj.NextVisibleNode(startNode_, startOffset, address_of(unused),
&offset, &wsType);
wsStartObj.NextVisibleNode(startNode_, static_cast<int32_t>(startOffset),
address_of(unused), &offset, &wsType);
if (wsType != WSType::text && wsType != WSType::normalWS) {
// eThisBlock and eOtherBlock conveniently distinquish cases
// of going "down" into a block and "up" out of a block.
@ -5396,24 +5403,31 @@ HTMLEditRules::NormalizeSelection(Selection* inSelection)
GetAsDOMNode(mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode,
true));
if (child) {
newStartNode = EditorBase::GetNodeLocation(child, &newStartOffset);
int32_t offset = -1;
newStartNode = EditorBase::GetNodeLocation(child, &offset);
newStartOffset = static_cast<uint32_t>(offset);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsStartObj.mEndReason == WSType::thisBlock) {
// startpoint is just before end of this block
nsCOMPtr<nsIDOMNode> child;
NS_ENSURE_STATE(mHTMLEditor);
mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child));
mHTMLEditor->GetNextHTMLNode(startNode, static_cast<int32_t>(startOffset),
address_of(child));
if (child) {
newStartNode = EditorBase::GetNodeLocation(child, &newStartOffset);
int32_t offset = -1;
newStartNode = EditorBase::GetNodeLocation(child, &offset);
newStartOffset = static_cast<uint32_t>(offset);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsStartObj.mEndReason == WSType::br) {
// startpoint is just before a break. lets adjust it to after it.
int32_t offset = -1;
newStartNode =
EditorBase::GetNodeLocation(GetAsDOMNode(wsStartObj.mEndReasonNode),
&newStartOffset);
++newStartOffset; // offset *after* break
&offset);
// offset *after* break
newStartOffset = static_cast<uint32_t>(offset + 1);
}
}
@ -8193,7 +8207,7 @@ HTMLEditRules::UpdateDocChangeRange(nsRange* aRange)
NS_ENSURE_SUCCESS(rv, rv);
// Positive result means mDocChangeRange start is after aRange start.
if (result > 0) {
int32_t startOffset;
uint32_t startOffset;
rv = aRange->GetStartOffset(&startOffset);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDocChangeRange->SetStart(startNode, startOffset);
@ -8207,9 +8221,9 @@ HTMLEditRules::UpdateDocChangeRange(nsRange* aRange)
// Negative result means mDocChangeRange end is before aRange end.
if (result < 0) {
nsCOMPtr<nsIDOMNode> endNode;
int32_t endOffset;
rv = aRange->GetEndContainer(getter_AddRefs(endNode));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t endOffset;
rv = aRange->GetEndOffset(&endOffset);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDocChangeRange->SetEnd(endNode, endOffset);

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