Merge autoland to mozilla-central. a=merge

This commit is contained in:
Brindusan Cristian 2020-03-19 23:55:44 +02:00
Родитель 8ccf549d4a c182c59955
Коммит 026c5990d5
189 изменённых файлов: 3338 добавлений и 2956 удалений

Просмотреть файл

@ -1,3 +1,7 @@
/* 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/. */
module.exports = {
plugins: [
"@babel/plugin-syntax-optional-chaining",

Просмотреть файл

@ -126,6 +126,12 @@
];
this.invoke = function changeDOMSelection_invoke() {
// HyperTextAccessible::GetSelectionDOMRanges ignores hidden selections.
// Here we may be focusing an editable element (and thus hiding the
// main document selection), so blur it so that we test what we want to
// test.
document.activeElement.blur();
var sel = window.getSelection();
var range = document.createRange();
range.setStart(getNode(aNodeID1), aNodeOffset1);

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -492,7 +492,12 @@ pref("browser.tabs.delayHidingAudioPlayingIconMS", 3000);
// for about: pages. This pref name did not age well: we will have multiple
// types of privileged content processes, each with different privileges.
// types of privleged content processes, each with different privleges.
#if defined(MOZ_CODE_COVERAGE) && defined(XP_LINUX)
// Disabled on Linux ccov builds due to bug 1621269.
pref("browser.tabs.remote.separatePrivilegedContentProcess", false);
#else
pref("browser.tabs.remote.separatePrivilegedContentProcess", true);
#endif
// This pref will cause assertions when a remoteType triggers a process switch
// to a new remoteType it should not be able to trigger.
pref("browser.tabs.remote.enforceRemoteTypeRestrictions", true);

Просмотреть файл

@ -1739,9 +1739,13 @@ var gIdentityHandler = {
indicator.appendChild(icon);
indicator.appendChild(text);
document
.getElementById("identity-popup-geo-container")
.appendChild(indicator);
let geoContainer = document.getElementById("identity-popup-geo-container");
// Check whether geoContainer still exists.
// We are async, the identity popup could have been closed already.
if (geoContainer) {
geoContainer.appendChild(indicator);
}
},
_createBlockedPopupIndicator(aTotalBlockedPopups) {

Просмотреть файл

@ -6206,13 +6206,18 @@ nsBrowserAccess.prototype = {
browsingContext =
window.content && BrowsingContext.getFromWindow(window.content);
if (aURI) {
let loadflags = isExternal
? Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL
: Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (isExternal) {
loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
} else if (!aTriggeringPrincipal.isSystemPrincipal) {
// XXX this code must be reviewed and changed when bug 1616353
// lands.
loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
}
gBrowser.loadURI(aURI.spec, {
triggeringPrincipal: aTriggeringPrincipal,
csp: aCsp,
flags: loadflags,
loadFlags,
referrerInfo,
});
}

Просмотреть файл

@ -2845,6 +2845,10 @@
}
if (fromExternal) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
} else if (!triggeringPrincipal.isSystemPrincipal) {
// XXX this code must be reviewed and changed when bug 1616353
// lands.
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
}
if (allowMixedContent) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;

Просмотреть файл

@ -37,7 +37,6 @@ const whitelist = {
// Session store
"resource:///modules/sessionstore/ContentSessionStore.jsm",
"resource://gre/modules/sessionstore/SessionHistory.jsm",
// Browser front-end
"resource:///actors/AboutReaderChild.jsm",
@ -86,6 +85,10 @@ const intermittently_loaded_whitelist = {
"resource://gre/modules/nsAsyncShutdown.jsm",
"resource://gre/modules/sessionstore/Utils.jsm",
// Session store.
"resource://gre/modules/sessionstore/SessionHistory.jsm",
"resource:///modules/sessionstore/SessionHistoryListener.jsm",
"resource://specialpowers/SpecialPowersChild.jsm",
"resource://specialpowers/WrapPrivileged.jsm",

Просмотреть файл

@ -7,7 +7,6 @@ support-files=
[browser_canvas_fingerprinting_resistance.js]
skip-if = debug || os == "linux" && asan # Bug 1522069
[browser_permissions.js]
skip-if = debug && os == "mac" # Bug 1601576
[browser_permissions_delegate_vibrate.js]
support-files=
empty.html

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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/. */
module.exports = {
// When adding items to this file please check for effects on sub-directories.
parserOptions: {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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/. */
/* eslint-disable import/no-commonjs */
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -58,6 +58,8 @@ function ContentRestore(chromeGlobal) {
let EXPORTED_METHODS = [
"restoreHistory",
"finishRestoreHistory",
"restoreOnNewEntry",
"restoreTabContent",
"restoreDocument",
"resetRestore",
@ -94,6 +96,25 @@ function ContentRestoreInternal(chromeGlobal) {
// data from the network. Set in restoreHistory() and restoreTabContent(),
// removed in resetRestore().
this._progressListener = null;
this._shistoryInParent = false;
}
function kickOffNewLoadFromBlankPage(webNavigation, newURI) {
// Reset the tab's URL to what it's actually showing. Without this loadURI()
// would use the current document and change the displayed URL only.
webNavigation.setCurrentURI(Services.io.newURI("about:blank"));
// Kick off a new load so that we navigate away from about:blank to the
// new URL that was passed to loadURI(). The new load will cause a
// STATE_START notification to be sent and the ProgressListener will then
// notify the parent and do the rest.
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
let loadURIOptions = {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
loadFlags,
};
webNavigation.loadURI(newURI, loadURIOptions);
}
/**
@ -111,7 +132,7 @@ ContentRestoreInternal.prototype = {
* non-zero) is passed through to all the callbacks. If a load in the tab
* is started while it is pending, the appropriate callbacks are called.
*/
restoreHistory(tabData, loadArguments, callbacks) {
restoreHistory(tabData, loadArguments, callbacks, shistoryInParent) {
this._tabData = tabData;
// In case about:blank isn't done yet.
@ -130,27 +151,46 @@ ContentRestoreInternal.prototype = {
webNavigation.setCurrentURI(Services.io.newURI(uri));
}
this._shistoryInParent = shistoryInParent;
if (this._shistoryInParent) {
callbacks.requestRestoreSHistory();
} else {
SessionHistory.restore(this.docShell, tabData);
// Add a listener to watch for reloads.
let listener = new HistoryListener(this.docShell, () => {
// On reload, restore tab contents.
this.restoreTabContent(null, false, callbacks.onLoadFinished);
this.restoreTabContent(
null,
false,
callbacks.onLoadFinished,
null,
null
);
});
webNavigation.sessionHistory.legacySHistory.addSHistoryListener(listener);
this._historyListener = listener;
this.finishRestoreHistory(callbacks);
}
},
finishRestoreHistory(callbacks) {
// Make sure to reset the capabilities and attributes in case this tab gets
// reused.
SessionStoreUtils.restoreDocShellCapabilities(
this.docShell,
tabData.disallow
this._tabData.disallow
);
if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
SessionStoreUtils.restoreSessionStorage(this.docShell, tabData.storage);
delete tabData.storage;
if (this._tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
SessionStoreUtils.restoreSessionStorage(
this.docShell,
this._tabData.storage
);
delete this._tabData.storage;
}
// Add a progress listener to correctly handle browser.loadURI()
@ -162,7 +202,10 @@ ContentRestoreInternal.prototype = {
this._tabData = null;
// Listen for the tab to finish loading.
this.restoreTabContentStarted(callbacks.onLoadFinished);
this.restoreTabContentStarted(
callbacks.onLoadFinished,
callbacks.removeRestoreListener
);
// Notify the parent.
callbacks.onLoadStarted();
@ -170,11 +213,22 @@ ContentRestoreInternal.prototype = {
});
},
restoreOnNewEntry(newURI) {
let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
kickOffNewLoadFromBlankPage(webNavigation, newURI);
},
/**
* Start loading the current page. When the data has finished loading from the
* network, finishCallback is called. Returns true if the load was successful.
*/
restoreTabContent(loadArguments, isRemotenessUpdate, finishCallback) {
restoreTabContent(
loadArguments,
isRemotenessUpdate,
finishCallback,
removeListenerCallback,
reloadSHistoryCallback
) {
let tabData = this._tabData;
this._tabData = null;
@ -182,7 +236,7 @@ ContentRestoreInternal.prototype = {
let history = webNavigation.sessionHistory.legacySHistory;
// Listen for the tab to finish loading.
this.restoreTabContentStarted(finishCallback);
this.restoreTabContentStarted(finishCallback, removeListenerCallback);
// Reset the current URI to about:blank. We changed it above for
// switch-to-tab, but now it must go back to the correct value before the
@ -273,7 +327,11 @@ ContentRestoreInternal.prototype = {
// In order to work around certain issues in session history, we need to
// force session history to update its internal index and call reload
// instead of gotoIndex. See bug 597315.
if (this._shistoryInParent) {
reloadSHistoryCallback();
} else {
history.reloadCurrentEntry();
}
} else {
// If there's nothing to restore, we should still blank the page.
let loadURIOptions = {
@ -298,10 +356,14 @@ ContentRestoreInternal.prototype = {
* To be called after restoreHistory(). Removes all listeners needed for
* pending tabs and makes sure to notify when the tab finished loading.
*/
restoreTabContentStarted(finishCallback) {
restoreTabContentStarted(finishCallback, removeListenerCallback) {
// The reload listener is no longer needed.
if (!this._shistoryInParent) {
this._historyListener.uninstall();
this._historyListener = null;
} else {
removeListenerCallback();
}
// Remove the old progress listener.
this._progressListener.uninstall();
@ -415,20 +477,7 @@ HistoryListener.prototype = {
return;
}
// Reset the tab's URL to what it's actually showing. Without this loadURI()
// would use the current document and change the displayed URL only.
this.webNavigation.setCurrentURI(Services.io.newURI("about:blank"));
// Kick off a new load so that we navigate away from about:blank to the
// new URL that was passed to loadURI(). The new load will cause a
// STATE_START notification to be sent and the ProgressListener will then
// notify the parent and do the rest.
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
let loadURIOptions = {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
loadFlags,
};
this.webNavigation.loadURI(newURI.spec, loadURIOptions);
kickOffNewLoadFromBlankPage(this.webNavigation, newURI);
},
OnHistoryReload() {

Просмотреть файл

@ -21,8 +21,8 @@ ChromeUtils.defineModuleGetter(
);
ChromeUtils.defineModuleGetter(
this,
"SessionHistory",
"resource://gre/modules/sessionstore/SessionHistory.jsm"
"SessionHistoryListener",
"resource:///modules/sessionstore/SessionHistoryListener.jsm"
);
// This pref controls whether or not we send updates to the parent on a timeout
@ -31,9 +31,6 @@ const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
const PREF_INTERVAL = "browser.sessionstore.interval";
const kNoIndex = Number.MAX_SAFE_INTEGER;
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
class Handler {
constructor(store) {
this.store = store;
@ -54,82 +51,7 @@ class Handler {
get messageQueue() {
return this.store.messageQueue;
}
get stateChangeNotifier() {
return this.store.stateChangeNotifier;
}
}
/**
* Listens for state change notifcations from webProgress and notifies each
* registered observer for either the start of a page load, or its completion.
*/
class StateChangeNotifier extends Handler {
constructor(store) {
super(store);
this._observers = new Set();
let ifreq = this.mm.docShell.QueryInterface(Ci.nsIInterfaceRequestor);
let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(
this,
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
);
}
/**
* Adds a given observer |obs| to the set of observers that will be notified
* when when a new document starts or finishes loading.
*
* @param obs (object)
*/
addObserver(obs) {
this._observers.add(obs);
}
/**
* Notifies all observers that implement the given |method|.
*
* @param method (string)
*/
notifyObservers(method) {
for (let obs of this._observers) {
if (typeof obs[method] == "function") {
obs[method]();
}
}
}
/**
* @see nsIWebProgressListener.onStateChange
*/
onStateChange(webProgress, request, stateFlags, status) {
// Ignore state changes for subframes because we're only interested in the
// top-document starting or stopping its load.
if (!webProgress.isTopLevel || webProgress.DOMWindow != this.mm.content) {
return;
}
// onStateChange will be fired when loading the initial about:blank URI for
// a browser, which we don't actually care about. This is particularly for
// the case of unrestored background tabs, where the content has not yet
// been restored: we don't want to accidentally send any updates to the
// parent when the about:blank placeholder page has loaded.
if (!this.mm.docShell.hasLoadedNonBlankURI) {
return;
}
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
this.notifyObservers("onPageLoadStarted");
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
this.notifyObservers("onPageLoadCompleted");
}
}
}
StateChangeNotifier.prototype.QueryInterface = ChromeUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
]);
/**
* Listens for and handles content events that we need for the
@ -177,139 +99,6 @@ class EventListener extends Handler {
}
}
/**
* Listens for changes to the session history. Whenever the user navigates
* we will collect URLs and everything belonging to session history.
*
* Causes a SessionStore:update message to be sent that contains the current
* session history.
*
* Example:
* {entries: [{url: "about:mozilla", ...}, ...], index: 1}
*/
class SessionHistoryListener extends Handler {
constructor(store) {
super(store);
this._fromIdx = kNoIndex;
// The state change observer is needed to handle initial subframe loads.
// It will redundantly invalidate with the SHistoryListener in some cases
// but these invalidations are very cheap.
this.stateChangeNotifier.addObserver(this);
// By adding the SHistoryListener immediately, we will unfortunately be
// notified of every history entry as the tab is restored. We don't bother
// waiting to add the listener later because these notifications are cheap.
// We will likely only collect once since we are batching collection on
// a delay.
this.mm.docShell
.QueryInterface(Ci.nsIWebNavigation)
.sessionHistory.legacySHistory.addSHistoryListener(this);
// Collect data if we start with a non-empty shistory.
if (!SessionHistory.isEmpty(this.mm.docShell)) {
this.collect();
// When a tab is detached from the window, for the new window there is a
// new SessionHistoryListener created. Normally it is empty at this point
// but in a test env. the initial about:blank might have a children in which
// case we fire off a history message here with about:blank in it. If we
// don't do it ASAP then there is going to be a browser swap and the parent
// will be all confused by that message.
this.messageQueue.send();
}
// Listen for page title changes.
this.mm.addEventListener("DOMTitleChanged", this);
}
uninit() {
let sessionHistory = this.mm.docShell.QueryInterface(Ci.nsIWebNavigation)
.sessionHistory;
if (sessionHistory) {
sessionHistory.legacySHistory.removeSHistoryListener(this);
}
}
collect() {
// We want to send down a historychange even for full collects in case our
// session history is a partial session history, in which case we don't have
// enough information for a full update. collectFrom(-1) tells the collect
// function to collect all data avaliable in this process.
if (this.mm.docShell) {
this.collectFrom(-1);
}
}
// History can grow relatively big with the nested elements, so if we don't have to, we
// don't want to send the entire history all the time. For a simple optimization
// we keep track of the smallest index from after any change has occured and we just send
// the elements from that index. If something more complicated happens we just clear it
// and send the entire history. We always send the additional info like the current selected
// index (so for going back and forth between history entries we set the index to kLastIndex
// if nothing else changed send an empty array and the additonal info like the selected index)
collectFrom(idx) {
if (this._fromIdx <= idx) {
// If we already know that we need to update history fromn index N we can ignore any changes
// tha happened with an element with index larger than N.
// Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
// here, and in case of navigation in the history back and forth we use kLastIndex which ignores
// only the subsequent navigations, but not any new elements added.
return;
}
this._fromIdx = idx;
this.messageQueue.push("historychange", () => {
if (this._fromIdx === kNoIndex) {
return null;
}
let history = SessionHistory.collect(this.mm.docShell, this._fromIdx);
this._fromIdx = kNoIndex;
return history;
});
}
handleEvent(event) {
this.collect();
}
onPageLoadCompleted() {
this.collect();
}
onPageLoadStarted() {
this.collect();
}
OnHistoryNewEntry(newURI, oldIndex) {
// We ought to collect the previously current entry as well, see bug 1350567.
this.collectFrom(oldIndex);
}
OnHistoryGotoIndex() {
// We ought to collect the previously current entry as well, see bug 1350567.
this.collectFrom(kLastIndex);
}
OnHistoryPurge() {
this.collect();
}
OnHistoryReload() {
this.collect();
return true;
}
OnHistoryReplaceEntry() {
this.collect();
}
}
SessionHistoryListener.prototype.QueryInterface = ChromeUtils.generateQI([
Ci.nsISHistoryListener,
Ci.nsISupportsWeakReference,
]);
/**
* A message queue that takes collected data and will take care of sending it
* to the chrome process. It allows flushing using synchronous messages and
@ -550,6 +339,9 @@ class MessageQueue extends Handler {
*/
const MESSAGES = [
"SessionStore:restoreHistory",
"SessionStore:finishRestoreHistory",
"SessionStore:OnHistoryReload",
"SessionStore:OnHistoryNewEntry",
"SessionStore:restoreTabContent",
"SessionStore:resetRestore",
"SessionStore:flush",
@ -560,23 +352,30 @@ class ContentSessionStore {
constructor(mm) {
this.mm = mm;
this.messageQueue = new MessageQueue(this);
this.stateChangeNotifier = new StateChangeNotifier(this);
this.epoch = 0;
this.contentRestoreInitialized = false;
this.waitRestoreSHistoryInParent = false;
this.restoreTabContentData = null;
XPCOMUtils.defineLazyGetter(this, "contentRestore", () => {
this.contentRestoreInitialized = true;
return new ContentRestore(mm);
});
this.handlers = [
new EventListener(this),
new SessionHistoryListener(this),
this.stateChangeNotifier,
this.messageQueue,
];
this.handlers = [new EventListener(this), this.messageQueue];
this._shistoryInParent = Services.prefs.getBoolPref(
"fission.sessionHistoryInParent",
false
);
if (this._shistoryInParent) {
this.mm.sendAsyncMessage("SessionStore:addSHistoryListener");
} else {
this.handlers.push(new SessionHistoryListener(this));
}
MESSAGES.forEach(m => mm.addMessageListener(m, this));
@ -597,7 +396,7 @@ class ContentSessionStore {
// override that to signal a new era in this tab's life. This enables it
// to ignore async messages that were already sent but not yet received
// and would otherwise confuse the internal tab state.
if (data.epoch && data.epoch != this.epoch) {
if (data && data.epoch && data.epoch != this.epoch) {
this.epoch = data.epoch;
}
@ -605,8 +404,45 @@ class ContentSessionStore {
case "SessionStore:restoreHistory":
this.restoreHistory(data);
break;
case "SessionStore:finishRestoreHistory":
this.finishRestoreHistory();
break;
case "SessionStore:OnHistoryNewEntry":
this.contentRestore.restoreOnNewEntry(data.uri);
break;
case "SessionStore:OnHistoryReload":
// On reload, restore tab contents.
this.contentRestore.restoreTabContent(
null,
false,
() => {
// Tell SessionStore.jsm that it may want to restore some more tabs,
// since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
epoch: this.epoch,
});
},
() => {
// Tell SessionStore.jsm to remove restoreListener.
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
epoch: this.epoch,
});
},
() => {
// Tell SessionStore.jsm to reload currentEntry.
this.mm.sendAsyncMessage("SessionStore:reloadCurrentEntry", {
epoch: this.epoch,
});
}
);
break;
case "SessionStore:restoreTabContent":
if (this.waitRestoreSHistoryInParent) {
// Queue the TabContentData if we haven't finished sHistoryRestore yet.
this.restoreTabContentData = data;
} else {
this.restoreTabContent(data);
}
break;
case "SessionStore:resetRestore":
this.contentRestore.resetRestore();
@ -615,7 +451,9 @@ class ContentSessionStore {
this.flush(data);
break;
case "SessionStore:becomeActiveProcess":
if (!this._shistoryInParent) {
SessionHistoryListener.collect();
}
break;
default:
debug("received unknown message '" + name + "'");
@ -624,7 +462,10 @@ class ContentSessionStore {
}
restoreHistory({ epoch, tabData, loadArguments, isRemotenessUpdate }) {
this.contentRestore.restoreHistory(tabData, loadArguments, {
this.contentRestore.restoreHistory(
tabData,
loadArguments,
{
// Note: The callbacks passed here will only be used when a load starts
// that was not initiated by sessionstore itself. This can happen when
// some code calls browser.loadURI() or browser.reload() on a pending
@ -644,7 +485,32 @@ class ContentSessionStore {
epoch,
});
},
removeRestoreListener: () => {
if (!this._shistoryInParent) {
return;
}
// Notify the parent that the tab is no longer pending.
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
epoch,
});
},
requestRestoreSHistory: () => {
if (!this._shistoryInParent) {
return;
}
this.waitRestoreSHistoryInParent = true;
// Send tabData to the parent process.
this.mm.sendAsyncMessage("SessionStore:restoreSHistoryInParent", {
epoch,
});
},
},
this._shistoryInParent
);
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
// For non-remote tabs, when restoreHistory finishes, we send a synchronous
@ -661,7 +527,7 @@ class ContentSessionStore {
epoch,
isRemotenessUpdate,
});
} else {
} else if (!this._shistoryInParent) {
this.mm.sendAsyncMessage("SessionStore:restoreHistoryComplete", {
epoch,
isRemotenessUpdate,
@ -669,6 +535,49 @@ class ContentSessionStore {
}
}
finishRestoreHistory() {
this.contentRestore.finishRestoreHistory({
// Note: The callbacks passed here will only be used when a load starts
// that was not initiated by sessionstore itself. This can happen when
// some code calls browser.loadURI() or browser.reload() on a pending
// browser/tab.
onLoadStarted: () => {
// Notify the parent that the tab is no longer pending.
this.mm.sendAsyncMessage("SessionStore:restoreTabContentStarted", {
epoch: this.epoch,
});
},
onLoadFinished: () => {
// Tell SessionStore.jsm that it may want to restore some more tabs,
// since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
epoch: this.epoch,
});
},
removeRestoreListener: () => {
if (!this._shistoryInParent) {
return;
}
// Notify the parent that the tab is no longer pending.
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
epoch: this.epoch,
});
},
});
this.mm.sendAsyncMessage("SessionStore:restoreHistoryComplete", {
epoch: this.epoch,
});
if (this.restoreTabContentData) {
this.restoreTabContent(this.restoreTabContentData);
this.restoreTabContentData = null;
}
this.waitRestoreSHistoryInParent = false;
}
restoreTabContent({ loadArguments, isRemotenessUpdate, reason }) {
let epoch = this.epoch;
@ -683,6 +592,17 @@ class ContentSessionStore {
epoch,
isRemotenessUpdate,
});
},
() => {
// Tell SessionStore.jsm to remove restore listener.
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
epoch,
});
},
() => {
this.mm.sendAsyncMessage("SessionStore:reloadCurrentEntry", {
epoch,
});
}
);

Просмотреть файл

@ -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";
var EXPORTED_SYMBOLS = ["SessionHistoryListener"];
ChromeUtils.defineModuleGetter(
this,
"SessionHistory",
"resource://gre/modules/sessionstore/SessionHistory.jsm"
);
const kNoIndex = Number.MAX_SAFE_INTEGER;
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
/**
* Listens for state change notifcations from webProgress and notifies each
* registered observer for either the start of a page load, or its completion.
*/
class StateChangeNotifier {
constructor(store) {
this.store = store;
this._observers = new Set();
let webProgress = this.mm.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(
this,
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
);
}
get mm() {
return this.store.mm;
}
/**
* Adds a given observer |obs| to the set of observers that will be notified
* when when a new document starts or finishes loading.
*
* @param obs (object)
*/
addObserver(obs) {
this._observers.add(obs);
}
/**
* Notifies all observers that implement the given |method|.
*
* @param method (string)
*/
notifyObservers(method) {
for (let obs of this._observers) {
if (typeof obs[method] == "function") {
obs[method]();
}
}
}
/**
* @see nsIWebProgressListener.onStateChange
*/
onStateChange(webProgress, request, stateFlags, status) {
// Ignore state changes for subframes because we're only interested in the
// top-document starting or stopping its load.
if (!webProgress.isTopLevel || webProgress.DOMWindow != this.mm.content) {
return;
}
// onStateChange will be fired when loading the initial about:blank URI for
// a browser, which we don't actually care about. This is particularly for
// the case of unrestored background tabs, where the content has not yet
// been restored: we don't want to accidentally send any updates to the
// parent when the about:blank placeholder page has loaded.
if (!this.mm.docShell.hasLoadedNonBlankURI) {
return;
}
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
this.notifyObservers("onPageLoadStarted");
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
this.notifyObservers("onPageLoadCompleted");
}
}
}
StateChangeNotifier.prototype.QueryInterface = ChromeUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
]);
/**
* Listens for changes to the session history. Whenever the user navigates
* we will collect URLs and everything belonging to session history.
*
* Causes a SessionStore:update message to be sent that contains the current
* session history.
*
* Example:
* {entries: [{url: "about:mozilla", ...}, ...], index: 1}
*/
class SessionHistoryListener {
constructor(store) {
this.store = store;
this._fromIdx = kNoIndex;
// The state change observer is needed to handle initial subframe loads.
// It will redundantly invalidate with the SHistoryListener in some cases
// but these invalidations are very cheap.
this.stateChangeNotifier = new StateChangeNotifier(this);
this.stateChangeNotifier.addObserver(this);
// By adding the SHistoryListener immediately, we will unfortunately be
// notified of every history entry as the tab is restored. We don't bother
// waiting to add the listener later because these notifications are cheap.
// We will likely only collect once since we are batching collection on
// a delay.
this.mm.docShell
.QueryInterface(Ci.nsIWebNavigation)
.sessionHistory.legacySHistory.addSHistoryListener(this);
// Collect data if we start with a non-empty shistory.
if (!SessionHistory.isEmpty(this.mm.docShell)) {
this.collect();
// When a tab is detached from the window, for the new window there is a
// new SessionHistoryListener created. Normally it is empty at this point
// but in a test env. the initial about:blank might have a children in which
// case we fire off a history message here with about:blank in it. If we
// don't do it ASAP then there is going to be a browser swap and the parent
// will be all confused by that message.
this.store.messageQueue.send();
}
// Listen for page title changes.
this.mm.addEventListener("DOMTitleChanged", this);
}
get mm() {
return this.store.mm;
}
uninit() {
let sessionHistory = this.mm.docShell.QueryInterface(Ci.nsIWebNavigation)
.sessionHistory;
if (sessionHistory) {
sessionHistory.legacySHistory.removeSHistoryListener(this);
}
}
collect() {
// We want to send down a historychange even for full collects in case our
// session history is a partial session history, in which case we don't have
// enough information for a full update. collectFrom(-1) tells the collect
// function to collect all data avaliable in this process.
if (this.mm.docShell) {
this.collectFrom(-1);
}
}
// History can grow relatively big with the nested elements, so if we don't have to, we
// don't want to send the entire history all the time. For a simple optimization
// we keep track of the smallest index from after any change has occured and we just send
// the elements from that index. If something more complicated happens we just clear it
// and send the entire history. We always send the additional info like the current selected
// index (so for going back and forth between history entries we set the index to kLastIndex
// if nothing else changed send an empty array and the additonal info like the selected index)
collectFrom(idx) {
if (this._fromIdx <= idx) {
// If we already know that we need to update history fromn index N we can ignore any changes
// tha happened with an element with index larger than N.
// Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
// here, and in case of navigation in the history back and forth we use kLastIndex which ignores
// only the subsequent navigations, but not any new elements added.
return;
}
this._fromIdx = idx;
this.store.messageQueue.push("historychange", () => {
if (this._fromIdx === kNoIndex) {
return null;
}
let history = SessionHistory.collect(this.mm.docShell, this._fromIdx);
this._fromIdx = kNoIndex;
return history;
});
}
handleEvent(event) {
this.collect();
}
onPageLoadCompleted() {
this.collect();
}
onPageLoadStarted() {
this.collect();
}
OnHistoryNewEntry(newURI, oldIndex) {
// We ought to collect the previously current entry as well, see bug 1350567.
this.collectFrom(oldIndex);
}
OnHistoryGotoIndex() {
// We ought to collect the previously current entry as well, see bug 1350567.
this.collectFrom(kLastIndex);
}
OnHistoryPurge() {
this.collect();
}
OnHistoryReload() {
this.collect();
return true;
}
OnHistoryReplaceEntry() {
this.collect();
}
}
SessionHistoryListener.prototype.QueryInterface = ChromeUtils.generateQI([
Ci.nsISHistoryListener,
Ci.nsISupportsWeakReference,
]);

Просмотреть файл

@ -97,6 +97,22 @@ const MESSAGES = [
// The content script encountered an error.
"SessionStore:error",
// The content script asks us to add the session history listener in the
// parent process when sessionHistory is in the parent process.
"SessionStore:addSHistoryListener",
// The content script asks us to remove the session history listener which
// is added in the restore process when sessionHistory is in the parent process.
"SessionStore:removeRestoreListener",
// The content script asks us to restore session history in the parent process
// when sessionHistory is in the parent process.
"SessionStore:restoreSHistoryInParent",
// The content script asks us to reload the current session history entry when
// sessionHistory is in the parent process.
"SessionStore:reloadCurrentEntry",
];
// The list of messages we accept from <xul:browser>s that have no tab
@ -112,6 +128,9 @@ const NOTAB_MESSAGES = new Set([
// For a description see above.
"SessionStore:error",
// For a description see above.
"SessionStore:addSHistoryListener",
]);
// The list of messages we accept without an "epoch" parameter.
@ -122,6 +141,9 @@ const NOEPOCH_MESSAGES = new Set([
// For a description see above.
"SessionStore:error",
// For a description see above.
"SessionStore:addSHistoryListener",
]);
// The list of messages we want to receive even during the short period after a
@ -175,6 +197,10 @@ const RESTORE_TAB_CONTENT_REASON = {
// 'browser.startup.page' preference value to resume the previous session.
const BROWSER_STARTUP_RESUME_SESSION = 3;
// Used by SessionHistoryListener.
const kNoIndex = Number.MAX_SAFE_INTEGER;
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
ChromeUtils.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
@ -182,6 +208,12 @@ ChromeUtils.import("resource://gre/modules/Timer.jsm", this);
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
ChromeUtils.defineModuleGetter(
this,
"SessionHistory",
"resource://gre/modules/sessionstore/SessionHistory.jsm"
);
XPCOMUtils.defineLazyServiceGetters(this, {
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
@ -501,6 +533,15 @@ var SessionStoreInternal = {
// windows yet to be restored
_restoreCount: -1,
// For each <browser> element, records the SHistoryListener.
_browserSHistoryListener: new WeakMap(),
// For each <browser> element, records the SHistoryListener.
_browserSHistoryListenerForRestore: new WeakMap(),
// The history data needed to be restored in the parent.
_shistoryToRestore: new WeakMap(),
// For each <browser> element, records the current epoch.
_browserEpochs: new WeakMap(),
@ -824,6 +865,11 @@ var SessionStoreInternal = {
"privacy.resistFingerprinting"
);
Services.prefs.addObserver("privacy.resistFingerprinting", this);
this._shistoryInParent = Services.prefs.getBoolPref(
"fission.sessionHistoryInParent",
false
);
},
/**
@ -907,6 +953,205 @@ var SessionStoreInternal = {
}
},
// Create a sHistoryLister and register it.
// We also need to save the SHistoryLister into this._browserSHistoryListener.
addSHistoryListener(aBrowser) {
function SHistoryListener(browser) {
browser.frameLoader.browsingContext.sessionHistory.addSHistoryListener(
this
);
this.browser = browser;
this.frameLoader = browser.frameLoader;
this._fromIdx = kNoIndex;
this._sHistoryChanges = false;
if (this.browser.currentURI && this.browser.ownerGlobal) {
this._lastKnownUri = browser.currentURI.displaySpec;
this._lastKnownBody = browser.ownerGlobal.document.body;
this._lastKnownUserContextId =
browser.contentPrincipal.originAttributes.userContextId;
}
}
SHistoryListener.prototype = {
QueryInterface: ChromeUtils.generateQI([
Ci.nsISHistoryListener,
Ci.nsISupportsWeakReference,
]),
notifySHistoryChanges(index) {
if (this._fromIdx <= index) {
// If we already know that we need to update history from index N we can ignore any changes
// that happened with an element with index larger than N.
// Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
// here, and in case of navigation in the history back and forth we use kLastIndex which ignores
// only the subsequent navigations, but not any new elements added.
return;
}
if (!this._sHistoryChanges) {
this.frameLoader.requestSHistoryUpdate(/*aImmediately*/ false);
this._sHistoryChanges = true;
}
this._fromIdx = index;
if (this.browser.currentURI && this.browser.ownerGlobal) {
this._lastKnownUri = this.browser.currentURI.displaySpec;
this._lastKnownBody = this.browser.ownerGlobal.document.body;
this._lastKnownUserContextId = this.browser.contentPrincipal.originAttributes.userContextId;
}
},
uninstall() {
if (this.frameLoader.browsingContext) {
let shistory = this.frameLoader.browsingContext.sessionHistory;
if (shistory) {
shistory.removeSHistoryListener(this);
}
}
},
OnHistoryNewEntry(newURI, oldIndex) {
this.notifySHistoryChanges(oldIndex);
},
OnHistoryGotoIndex() {
this.notifySHistoryChanges(kLastIndex);
},
OnHistoryPurge() {
this.notifySHistoryChanges(-1);
},
OnHistoryReload() {
this.notifySHistoryChanges(-1);
return true;
},
OnHistoryReplaceEntry() {
this.notifySHistoryChanges(-1);
let win = this.browser.ownerGlobal;
let tab = win ? win.gBrowser.getTabForBrowser(this.browser) : null;
if (tab) {
let event = tab.ownerDocument.createEvent("CustomEvent");
event.initCustomEvent("SSHistoryReplaceEntry", true, false);
tab.dispatchEvent(event);
}
},
};
let spec = null;
if (aBrowser.currentURI) {
spec = aBrowser.currentURI.displaySpec;
}
if (!aBrowser.frameLoader) {
debug(
"addSHistoryListener(), aBrowser.frameLoader doesn't exist" +
",browser.currentURI.displaySpec=" +
spec
);
return;
}
if (!aBrowser.frameLoader.browsingContext) {
debug(
"addSHistoryListener(), aBrowser.fl.browsingContext doesn't exists" +
",browser.currentURI.displaySpec=" +
spec
);
return;
}
if (!aBrowser.frameLoader.browsingContext.sessionHistory) {
debug(
"addSHistoryListener(), aBrowser.fl.bc.sessionHistory doesn't exists" +
",browser.currentURI.displaySpec=" +
spec
);
return;
}
let listener = new SHistoryListener(aBrowser);
this._browserSHistoryListener.set(aBrowser.permanentKey, listener);
// Collect data if we start with a non-empty shistory.
let uri = aBrowser.currentURI.displaySpec;
let history = aBrowser.frameLoader.browsingContext.sessionHistory;
if (uri != "about:blank" || history.count != 0) {
aBrowser.frameLoader.requestSHistoryUpdate(/*aImmediately*/ true);
}
},
/**
* This listener detects when a page being restored is reloaded. It triggers a
* callback and cancels the reload. The callback will send a message to
* SessionStore.jsm so that it can restore the content immediately.
*/
addSHistoryListenerForRestore(aBrowser) {
function SHistoryListener(browser) {
browser.frameLoader.browsingContext.sessionHistory.addSHistoryListener(
this
);
this.browser = browser;
}
SHistoryListener.prototype = {
QueryInterface: ChromeUtils.generateQI([
Ci.nsISHistoryListener,
Ci.nsISupportsWeakReference,
]),
uninstall() {
let shistory = this.browser.frameLoader.browsingContext.sessionHistory;
if (shistory) {
shistory.removeSHistoryListener(this);
}
},
OnHistoryGotoIndex() {},
OnHistoryPurge() {},
OnHistoryReplaceEntry() {},
// This will be called for a pending tab when loadURI(uri) is called where
// the given |uri| only differs in the fragment.
OnHistoryNewEntry(newURI) {
let currentURI = this.browser.currentURI;
// Ignore new SHistory entries with the same URI as those do not indicate
// a navigation inside a document by changing the #hash part of the URL.
// We usually hit this when purging session history for browsers.
if (currentURI && currentURI.displaySpec == newURI.spec) {
return;
}
// Notify ContentSessionStore.jsm to restore on new entry.
this.browser.messageManager.sendAsyncMessage(
"SessionStore:OnHistoryNewEntry",
{ uri: newURI.spec }
);
},
OnHistoryReload() {
// Notify ContentSessionStore.jsm to restore tab contents.
this.browser.messageManager.sendAsyncMessage(
"SessionStore:OnHistoryReload"
);
// Cancel the load.
return false;
},
};
if (
!aBrowser.frameLoader ||
!aBrowser.frameLoader.browsingContext ||
!aBrowser.frameLoader.browsingContext.sessionHistory
) {
return;
}
let listener = new SHistoryListener(aBrowser);
this._browserSHistoryListenerForRestore.set(
aBrowser.permanentKey,
listener
);
},
updateSessionStoreFromTablistener(aBrowser, aData) {
if (aBrowser.permanentKey == undefined) {
return;
@ -917,6 +1162,72 @@ var SessionStoreInternal = {
return;
}
let sHistoryChangedInListener = false;
let listener = this._browserSHistoryListener.get(aBrowser.permanentKey);
if (listener) {
sHistoryChangedInListener = listener._sHistoryChanges;
}
if (aData.sHistoryNeeded || sHistoryChangedInListener) {
if (!listener) {
debug(
"updateSessionStoreFromTablistener() with aData.sHistoryNeeded, but no SHlistener. Add again!!!"
);
this.addSHistoryListener(aBrowser);
listener = this._browserSHistoryListener.get(aBrowser.permanentKey);
}
if (listener) {
if (!aData.sHistoryNeeded && listener._fromIdx == kNoIndex) {
// No shistory changes needed.
listener._sHistoryChanges = false;
} else {
// |browser.frameLoader| might be empty if the browser was already
// destroyed and its tab removed. In that case we still have the last
// frameLoader we know about to compare.
let frameLoader =
aBrowser.frameLoader ||
this._lastKnownFrameLoader.get(aBrowser.permanentKey);
if (
frameLoader &&
frameLoader.browsingContext &&
frameLoader.browsingContext.sessionHistory
) {
let uri = aBrowser.currentURI
? aBrowser.currentURI.displaySpec
: listener._lastKnownUri;
let body = aBrowser.ownerGlobal
? aBrowser.ownerGlobal.document.body
: listener._lastKnownBody;
let userContextId = aBrowser.contentPrincipal
? aBrowser.contentPrincipal.originAttributes.userContextId
: listener._lastKnownUserContextId;
aData.data.historychange = SessionHistory.collectFromParent(
uri,
body,
frameLoader.browsingContext.sessionHistory,
userContextId,
listener._sHistoryChanges ? listener._fromIdx : -1
);
listener._sHistoryChanges = false;
listener._fromIdx = kNoIndex;
} else {
debug(
"updateSessionStoreFromTablistener() with sHistoryNeeded, but no fL.bC.sessionHistory."
);
}
}
} else {
debug(
"updateSessionStoreFromTablistener() with sHistoryNeeded, but no sHlistener."
);
}
}
if ("sHistoryNeeded" in aData) {
delete aData.sHistoryNeeded;
}
TabState.update(aBrowser, aData);
let win = aBrowser.ownerGlobal;
this.saveStateDelayed(win);
@ -965,6 +1276,34 @@ var SessionStoreInternal = {
}
switch (aMessage.name) {
case "SessionStore:addSHistoryListener":
this.addSHistoryListener(browser);
break;
case "SessionStore:restoreSHistoryInParent":
if (
browser.frameLoader &&
browser.frameLoader.browsingContext &&
browser.frameLoader.browsingContext.sessionHistory
) {
let tabData = this._shistoryToRestore.get(browser.permanentKey);
if (tabData) {
this._shistoryToRestore.delete(browser.permanentKey);
SessionHistory.restoreFromParent(
browser.frameLoader.browsingContext.sessionHistory,
tabData
);
}
this.addSHistoryListenerForRestore(browser);
} else {
debug(
"receive SessionStore:restoreSHistoryInParent: but cannot find sessionHistory from bc."
);
}
browser.messageManager.sendAsyncMessage(
"SessionStore:finishRestoreHistory"
);
break;
case "SessionStore:update":
// |browser.frameLoader| might be empty if the browser was already
// destroyed and its tab removed. In that case we still have the last
@ -984,6 +1323,13 @@ var SessionStoreInternal = {
// late and will never respond. If they have been sent shortly after
// switching a browser's remoteness there isn't too much data to skip.
TabStateFlusher.resolveAll(browser);
let listener = this._browserSHistoryListener.get(
browser.permanentKey
);
if (listener) {
listener.uninstall();
this._browserSHistoryListener.delete(browser.permanentKey);
}
} else if (aMessage.data.flushID) {
// This is an update kicked off by an async flush request. Notify the
// TabStateFlusher so that it can finish the request and notify its
@ -1073,6 +1419,39 @@ var SessionStoreInternal = {
tab.dispatchEvent(event);
break;
}
case "SessionStore:removeRestoreListener":
let listener = this._browserSHistoryListenerForRestore.get(
browser.permanentKey
);
if (listener) {
listener.uninstall();
this._browserSHistoryListenerForRestore.delete(browser.permanentKey);
}
break;
case "SessionStore:reloadCurrentEntry":
let fL =
browser.frameLoader ||
this._lastKnownFrameLoader.get(browser.permanentKey);
if (fL) {
if (fL.browsingContext) {
if (fL.browsingContext.sessionHistory) {
fL.browsingContext.sessionHistory.reloadCurrentEntry();
} else {
debug(
"receive SessionStore:reloadCurrentEntry browser.fL.bC.sessionHistory is null."
);
}
} else {
debug(
"receive SessionStore:reloadCurrentEntry browser.fL.browsingContext is null."
);
}
} else {
debug(
"receive SessionStore:reloadCurrentEntry browser.frameLoader is null."
);
}
break;
case "SessionStore:restoreTabContentStarted":
if (TAB_STATE_FOR_BROWSER.get(browser) == TAB_STATE_NEEDS_RESTORE) {
// If a load not initiated by sessionstore was started in a
@ -1213,6 +1592,11 @@ var SessionStoreInternal = {
epoch: newEpoch,
}
);
let listener = this._browserSHistoryListener.get(target.permanentKey);
if (listener) {
listener.notifySHistoryChanges(-1);
}
break;
default:
throw new Error(`unhandled event ${aEvent.type}?`);
@ -5799,6 +6183,11 @@ var SessionStoreInternal = {
}
}
if (this._shistoryInParent) {
// Save the history data for restoring in the parent process.
this._shistoryToRestore.set(browser.permanentKey, options.tabData);
}
browser.messageManager.sendAsyncMessage(
"SessionStore:restoreHistory",
options

Просмотреть файл

@ -17,6 +17,7 @@ EXTRA_JS_MODULES.sessionstore = [
'RunState.jsm',
'SessionCookies.jsm',
'SessionFile.jsm',
'SessionHistoryListener.jsm',
'SessionMigration.jsm',
'SessionSaver.jsm',
'SessionStartup.jsm',

Просмотреть файл

@ -32,12 +32,26 @@ function restoreClosedTabWithValue(rval) {
return ss.undoCloseTab(window, index);
}
function promiseNewLocationAndHistoryEntryReplaced(browser, snippet) {
function promiseNewLocationAndHistoryEntryReplaced(tab, snippet) {
let browser = tab.linkedBrowser;
if (Services.prefs.getBoolPref("fission.sessionHistoryInParent", false)) {
SpecialPowers.spawn(browser, [snippet], async function(codeSnippet) {
// Need to define 'webNavigation' for 'codeSnippet'
// eslint-disable-next-line no-unused-vars
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
// Evaluate the snippet that changes the location.
// eslint-disable-next-line no-eval
eval(codeSnippet);
});
return promiseOnHistoryReplaceEntry(tab);
}
return SpecialPowers.spawn(browser, [snippet], async function(codeSnippet) {
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
let shistory = webNavigation.sessionHistory.legacySHistory;
// Evaluate the snippet that the changes the location.
// Evaluate the snippet that changes the location.
// eslint-disable-next-line no-eval
eval(codeSnippet);
@ -121,7 +135,7 @@ add_task(async function save_worthy_tabs_remote_final() {
let snippet =
'webNavigation.loadURI("https://example.com/",\
{triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()})';
await promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
await promiseNewLocationAndHistoryEntryReplaced(tab, snippet);
// Remotness shouldn't have changed.
ok(browser.isRemoteBrowser, "browser is still remote");
@ -164,11 +178,10 @@ add_task(async function save_worthy_tabs_nonremote_final() {
add_task(async function dont_save_empty_tabs_final() {
let { tab, r } = await createTabWithRandomValue("https://example.com/");
let browser = tab.linkedBrowser;
// Replace the current page with an about:blank entry.
let snippet = 'content.location.replace("about:blank")';
await promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
await promiseNewLocationAndHistoryEntryReplaced(tab, snippet);
// Remove the tab before the update arrives.
let promise = promiseRemoveTabAndSessionState(tab);

Просмотреть файл

@ -38,8 +38,12 @@ add_task(async function test_add_interesting_window() {
content.location = newPage;
});
if (Services.prefs.getBoolPref("fission.sessionHistoryInParent", false)) {
let tab = newWin.gBrowser.selectedTab;
await promiseOnHistoryReplaceEntry(tab);
} else {
await promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
}
// Clear out the userTypedValue so that the new window looks like
// it's really not worth restoring.
browser.userTypedValue = null;

Просмотреть файл

@ -556,6 +556,10 @@ function promiseDelayedStartupFinished(aWindow) {
return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve));
}
function promiseOnHistoryReplaceEntry(tab) {
return BrowserTestUtils.waitForEvent(tab, "SSHistoryReplaceEntry");
}
function promiseTabRestored(tab) {
return BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
}

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -648,14 +648,15 @@ class UrlbarInput {
if (
result.heuristic &&
this.window.gKeywordURIFixup &&
UrlbarUtils.looksLikeSingleWordHost(originalUntrimmedValue)
) {
// The docshell when fixes a single word to a search, also checks the
// dns and prompts the user whether they wanted to rather visit that
// as a host. On a positive answer, it adds to the domains whitelist
// that we use to make decisions. Because here we are directly asking
// for a search, bypassing the docshell, we must do it here.
// See URIFixupChild.jsm and keyword-uri-fixup.
// When fixing a single word to a search, the docShell also checks the
// DNS and asks the user whether they would rather visit that as a
// host. On a positive answer, it adds to the domain whitelist that
// we use to make decisions. Because we are directly asking for a
// search here, bypassing the docShell, we need invoke the same check
// ourselves. See also URIFixupChild.jsm and keyword-uri-fixup.
let flags =
Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -38,6 +38,5 @@ CHECK(RefCountedCopyConstructorChecker, "refcounted-copy-constructor")
CHECK(RefCountedInsideLambdaChecker, "refcounted-inside-lambda")
CHECK(ScopeChecker, "scope")
CHECK(SprintfLiteralChecker, "sprintf-literal")
CHECK(TempRefPtrChecker, "performance-temp-refptr")
CHECK(TrivialCtorDtorChecker, "trivial-constructor-destructor")
CHECK(TrivialDtorChecker, "trivial-destructor")

Просмотреть файл

@ -39,6 +39,5 @@
#include "RefCountedInsideLambdaChecker.h"
#include "ScopeChecker.h"
#include "SprintfLiteralChecker.h"
#include "TempRefPtrChecker.h"
#include "TrivialCtorDtorChecker.h"
#include "TrivialDtorChecker.h"

Просмотреть файл

@ -6,3 +6,4 @@
// to be in alpha stage development.
// CHECK(AlphaChecker, "alpha-checker")
CHECK(TempRefPtrChecker, "performance-temp-refptr")

Просмотреть файл

@ -0,0 +1 @@
#include "TempRefPtrChecker.h"

Просмотреть файл

@ -6,4 +6,5 @@
HOST_SOURCES += [
# 'AlphaChecker.cpp',
'TempRefPtrChecker.cpp',
]

Просмотреть файл

@ -6,4 +6,5 @@
SOURCES += [
# 'AlphaTest.cpp',
'TestTempRefPtr.cpp',
]

Просмотреть файл

@ -43,7 +43,6 @@ HOST_SOURCES += [
'RefCountedInsideLambdaChecker.cpp',
'ScopeChecker.cpp',
'SprintfLiteralChecker.cpp',
'TempRefPtrChecker.cpp',
'TrivialCtorDtorChecker.cpp',
'TrivialDtorChecker.cpp',
'VariableUsageHelpers.cpp',

Просмотреть файл

@ -375,7 +375,11 @@ struct DisallowConstNonRefPtrMemberArgs {
};
MOZ_CAN_RUN_SCRIPT void test_temporary_1() {
#ifdef MOZ_CLANG_PLUGIN_ALPHA
RefPtr<RefCountedBase>(new RefCountedBase())->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}}
#else
RefPtr<RefCountedBase>(new RefCountedBase())->method_test();
#endif
}
MOZ_CAN_RUN_SCRIPT void test_temporary_2() {

Просмотреть файл

@ -49,7 +49,6 @@ SOURCES += [
'TestStackClass.cpp',
'TestStaticLocalClass.cpp',
'TestTemporaryClass.cpp',
'TestTempRefPtr.cpp',
'TestTrivialCtorDtor.cpp',
'TestTrivialDtor.cpp',
]

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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/. */
// Parent config file for all devtools browser mochitest files.
module.exports = {
"extends": [

Просмотреть файл

@ -1,3 +1,7 @@
/* 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/. */
// Parent config file for all devtools xpcshell files.
module.exports = {
"extends": [

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -22,6 +22,9 @@ class AccessibilityProxy {
constructor(toolbox) {
this.toolbox = toolbox;
this.accessibilityEventsMap = new Map();
this.accessibleWalkerEventsMap = new Map();
this.audit = this.audit.bind(this);
this.disableAccessibility = this.disableAccessibility.bind(this);
this.enableAccessibility = this.enableAccessibility.bind(this);
@ -39,16 +42,17 @@ class AccessibilityProxy {
this.stopListeningForLifecycleEvents = this.stopListeningForLifecycleEvents.bind(
this
);
}
get target() {
return this.toolbox.target;
this._onTargetAvailable = this._onTargetAvailable.bind(this);
}
get enabled() {
return this.accessibilityFront && this.accessibilityFront.enabled;
}
get currentTarget() {
return this._currentTarget;
}
/**
* Perform an audit for a given filter.
*
@ -67,17 +71,18 @@ class AccessibilityProxy {
*/
audit(filter, onError, onProgress, onCompleted) {
return new Promise(resolve => {
const front = this.accessibleWalkerFront;
const types =
filter === FILTERS.ALL ? Object.values(AUDIT_TYPE) : [filter];
const auditEventHandler = ({ type, ancestries, progress }) => {
switch (type) {
case "error":
this.accessibleWalkerFront.off("audit-event", auditEventHandler);
this._off(front, "audit-event", auditEventHandler);
onError();
resolve();
break;
case "completed":
this.accessibleWalkerFront.off("audit-event", auditEventHandler);
this._off(front, "audit-event", auditEventHandler);
onCompleted(ancestries);
resolve();
break;
@ -89,8 +94,8 @@ class AccessibilityProxy {
}
};
this.accessibleWalkerFront.on("audit-event", auditEventHandler);
this.accessibleWalkerFront.startAudit({ types });
this._on(front, "audit-event", auditEventHandler);
front.startAudit({ types });
});
}
@ -98,11 +103,12 @@ class AccessibilityProxy {
* Stop picking and remove all walker listeners.
*/
async cancelPick(onHovered, onPicked, onPreviewed, onCanceled) {
await this.accessibleWalkerFront.cancelPick();
this.accessibleWalkerFront.off("picker-accessible-hovered", onHovered);
this.accessibleWalkerFront.off("picker-accessible-picked", onPicked);
this.accessibleWalkerFront.off("picker-accessible-previewed", onPreviewed);
this.accessibleWalkerFront.off("picker-accessible-canceled", onCanceled);
const front = this.accessibleWalkerFront;
await front.cancelPick();
this._off(front, "picker-accessible-hovered", onHovered);
this._off(front, "picker-accessible-picked", onPicked);
this._off(front, "picker-accessible-previewed", onPreviewed);
this._off(front, "picker-accessible-canceled", onCanceled);
}
async disableAccessibility() {
@ -152,11 +158,12 @@ class AccessibilityProxy {
* If true, move keyboard focus into content.
*/
async pick(doFocus, onHovered, onPicked, onPreviewed, onCanceled) {
this.accessibleWalkerFront.on("picker-accessible-hovered", onHovered);
this.accessibleWalkerFront.on("picker-accessible-picked", onPicked);
this.accessibleWalkerFront.on("picker-accessible-previewed", onPreviewed);
this.accessibleWalkerFront.on("picker-accessible-canceled", onCanceled);
await this.accessibleWalkerFront.pick(doFocus);
const front = this.accessibleWalkerFront;
this._on(front, "picker-accessible-hovered", onHovered);
this._on(front, "picker-accessible-picked", onPicked);
this._on(front, "picker-accessible-previewed", onPreviewed);
this._on(front, "picker-accessible-canceled", onCanceled);
await front.pick(doFocus);
}
async resetAccessiblity() {
@ -168,48 +175,49 @@ class AccessibilityProxy {
startListeningForAccessibilityEvents(eventMap) {
for (const [type, listener] of Object.entries(eventMap)) {
this.accessibleWalkerFront.on(type, listener);
this._on(this.accessibleWalkerFront, type, listener);
}
}
stopListeningForAccessibilityEvents(eventMap) {
for (const [type, listener] of Object.entries(eventMap)) {
this.accessibleWalkerFront.off(type, listener);
this._off(this.accessibleWalkerFront, type, listener);
}
}
startListeningForLifecycleEvents(eventMap) {
for (let [type, listeners] of Object.entries(eventMap)) {
listeners = Array.isArray(listeners) ? listeners : [listeners];
for (const [type, listeners] of Object.entries(eventMap)) {
const accessibilityFront =
// TODO: Remove parentAccessibilityFront check after Firefox 75.
this.parentAccessibilityFront &&
PARENT_ACCESSIBILITY_EVENTS.includes(type)
? this.parentAccessibilityFront
: this.accessibilityFront;
for (const listener of listeners) {
accessibilityFront.on(type, listener);
}
this._on(accessibilityFront, type, listeners);
}
}
stopListeningForLifecycleEvents(eventMap) {
for (let [type, listeners] of Object.entries(eventMap)) {
listeners = Array.isArray(listeners) ? listeners : [listeners];
for (const [type, listeners] of Object.entries(eventMap)) {
// TODO: Remove parentAccessibilityFront check after Firefox 75.
const accessibilityFront =
this.parentAccessibilityFront &&
PARENT_ACCESSIBILITY_EVENTS.includes(type)
? this.parentAccessibilityFront
: this.accessibilityFront;
for (const listener of listeners) {
accessibilityFront.off(type, listener);
}
this._off(accessibilityFront, type, listeners);
}
}
async ensureReady() {
const { mainRoot } = this.target.client;
/**
* Part of the proxy initialization only needs to be done when the accessibility panel starts.
* To avoid performance issues, the panel will explicitly call this method every time a new
* target becomes available.
*/
async initializeProxyForPanel(targetFront) {
await this._updateTarget(targetFront);
const { mainRoot } = this._currentTarget.client;
if (await mainRoot.hasActor("parentAccessibility")) {
this.parentAccessibilityFront = await mainRoot.getFront(
"parentaccessibility"
@ -221,11 +229,116 @@ class AccessibilityProxy {
if (this.simulatorFront) {
this.simulate = types => this.simulatorFront.simulate({ types });
}
// Move front listeners to new front.
for (const [type, listeners] of this.accessibilityEventsMap.entries()) {
const accessibilityFront =
// TODO: Remove parentAccessibilityFront check after Firefox 75.
this.parentAccessibilityFront &&
PARENT_ACCESSIBILITY_EVENTS.includes(type)
? this.parentAccessibilityFront
: this.accessibilityFront;
for (const listener of listeners) {
accessibilityFront.on(type, listener);
}
}
for (const [type, listeners] of this.accessibleWalkerEventsMap.entries()) {
for (const listener of listeners) {
this.accessibleWalkerFront.on(type, listener);
}
}
}
async initialize() {
try {
this.accessibilityFront = await this.target.getFront("accessibility");
await this.toolbox.targetList.watchTargets(
[this.toolbox.targetList.TYPES.FRAME],
this._onTargetAvailable
);
return true;
} catch (e) {
// toolbox may be destroyed during this step.
return false;
}
}
destroy() {
this.toolbox.targetList.unwatchTargets(
[this.toolbox.targetList.TYPES.FRAME],
this._onTargetAvailable
);
this.accessibilityEventsMap = null;
this.accessibleWalkerEventsMap = null;
this.accessibilityFront = null;
this.parentAccessibilityFront = null;
this.accessibleWalkerFront = null;
this.simulatorFront = null;
this.simulate = null;
this.toolbox = null;
}
_getEventsMap(front) {
return front === this.accessibleWalkerFront
? this.accessibleWalkerEventsMap
: this.accessibilityEventsMap;
}
async _onTargetAvailable({ targetFront, isTopLevel }) {
if (isTopLevel) {
await this._updateTarget(targetFront);
}
}
_on(front, type, listeners) {
listeners = Array.isArray(listeners) ? listeners : [listeners];
for (const listener of listeners) {
front.on(type, listener);
}
const eventsMap = this._getEventsMap(front);
const eventsMapListeners = eventsMap.has(type)
? [...eventsMap.get(type), ...listeners]
: listeners;
eventsMap.set(type, eventsMapListeners);
}
_off(front, type, listeners) {
listeners = Array.isArray(listeners) ? listeners : [listeners];
for (const listener of listeners) {
front.off(type, listener);
}
const eventsMap = this._getEventsMap(front);
if (!eventsMap.has(type)) {
return;
}
const eventsMapListeners = eventsMap
.get(type)
.filter(l => !listeners.includes(l));
if (eventsMapListeners.length) {
eventsMap.set(type, eventsMapListeners);
} else {
eventsMap.delete(type);
}
}
async _updateTarget(targetFront) {
if (this._updatePromise && this._currentTarget === targetFront) {
return this._updatePromise;
}
this._currentTarget = targetFront;
this._updatePromise = (async () => {
this.accessibilityFront = await this._currentTarget.getFront(
"accessibility"
);
// Finalize accessibility front initialization. See accessibility front
// bootstrap method description.
await this.accessibilityFront.bootstrap();
@ -235,22 +348,11 @@ class AccessibilityProxy {
//
// [this.supports.simulation] = await Promise.all([
// // Please specify the version of Firefox when the feature was added.
// this.target.actorHasMethod("accessibility", "getSimulator"),
// this._currentTarget.actorHasMethod("accessibility", "getSimulator"),
// ]);
return true;
} catch (e) {
// toolbox may be destroyed during this step.
return false;
}
}
})();
destroy() {
this.accessibilityFront = null;
this.parentAccessibilityFront = null;
this.accessibleWalkerFront = null;
this.simulatorFront = null;
this.simulate = null;
this.toolbox = null;
return this._updatePromise;
}
}

Просмотреть файл

@ -40,6 +40,7 @@ function AccessibilityPanel(iframeWindow, toolbox, startup) {
this.startup = startup;
this.onTabNavigated = this.onTabNavigated.bind(this);
this.onTargetAvailable = this.onTargetAvailable.bind(this);
this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this);
this.onNewAccessibleFrontSelected = this.onNewAccessibleFrontSelected.bind(
this
@ -73,7 +74,6 @@ AccessibilityPanel.prototype = {
this._telemetry = new Telemetry();
this.panelWin.gTelemetry = this._telemetry;
this.target.on("navigate", this.onTabNavigated);
this._toolbox.on("select", this.onPanelVisibilityChange);
this.panelWin.EVENTS = EVENTS;
@ -90,7 +90,12 @@ AccessibilityPanel.prototype = {
this.shouldRefresh = true;
await this.startup.initAccessibility();
await this.accessibilityProxy.ensureReady();
await this._toolbox.targetList.watchTargets(
[this._toolbox.targetList.TYPES.FRAME],
this.onTargetAvailable
);
this.picker = new Picker(this);
this.fluentBundles = await this.createFluentBundles();
@ -147,6 +152,17 @@ AccessibilityPanel.prototype = {
this._opening.then(() => this.refresh());
},
async onTargetAvailable({ targetFront, isTopLevel, isTargetSwitching }) {
if (isTopLevel) {
await this.accessibilityProxy.initializeProxyForPanel(targetFront);
this.accessibilityProxy.currentTarget.on("navigate", this.onTabNavigated);
}
if (isTargetSwitching) {
this.onTabNavigated();
}
},
/**
* Make sure the panel is refreshed (if needed) when it's selected.
*/
@ -266,19 +282,20 @@ AccessibilityPanel.prototype = {
return this._toolbox.currentToolId === "accessibility";
},
get target() {
return this._toolbox.target;
},
destroy() {
if (this._destroyed) {
return;
}
this._destroyed = true;
this._toolbox.targetList.unwatchTargets(
[this._toolbox.targetList.TYPES.FRAME],
this.onTargetAvailable
);
this.postContentMessage("destroy");
this.target.off("navigate", this.onTabNavigated);
this.accessibilityProxy.currentTarget.off("navigate", this.onTabNavigated);
this._toolbox.off("select", this.onPanelVisibilityChange);
this.panelWin.off(

Просмотреть файл

@ -14,6 +14,7 @@ support-files =
skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184
[browser_accessibility_context_menu_inspector.js]
skip-if = (os == 'win' && processor == 'aarch64') # bug 1533484
[browser_accessibility_fission_switch_target.js]
[browser_accessibility_mutations.js]
skip-if = (os == 'win' && processor == 'aarch64') # bug 1533534
[browser_accessibility_panel_highlighter.js]

Просмотреть файл

@ -39,6 +39,9 @@ addA11YPanelTask(
"Test show accessibility properties context menu in browser.",
TEST_URI,
async function({ panel, toolbox, browser }) {
// Load the inspector to ensure it to use in this test.
await toolbox.loadTool("inspector");
const headerSelector = "#h1";
const contextMenu = document.getElementById("contentAreaContextMenu");

Просмотреть файл

@ -83,6 +83,9 @@ addA11YPanelTask(
"Test show accessibility properties context menu.",
TEST_URI,
async function testShowAccessibilityPropertiesContextMenu(env) {
// Load the inspector to ensure it to use in this test.
await env.toolbox.loadTool("inspector");
let allMenuItems = await openContextMenuForNode(env);
let showA11YPropertiesNode = checkShowA11YPropertiesNode(
allMenuItems,

Просмотреть файл

@ -0,0 +1,50 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test switching for the top-level target.
const MAIN_PROCESS_URL = "about:robots";
const MAIN_PROCESS_EXPECTED = [
{
expected: {
sidebar: {
name: "Gort! Klaatu barada nikto!",
role: "document",
},
},
},
];
const CONTENT_PROCESS_URL = buildURL(`<title>Test page</title>`);
const CONTENT_PROCESS_EXPECTED = [
{
expected: {
sidebar: {
name: "Test page",
role: "document",
},
},
},
];
add_task(async () => {
await pushPref("devtools.target-switching.enabled", true);
info(
"Open a test page running on the content process and accessibility panel"
);
const env = await addTestTab(CONTENT_PROCESS_URL);
await runA11yPanelTests(CONTENT_PROCESS_EXPECTED, env);
info("Navigate to a page running on the main process");
await navigateTo(MAIN_PROCESS_URL);
await runA11yPanelTests(MAIN_PROCESS_EXPECTED, env);
info("Back to a page running on the content process");
await navigateTo(CONTENT_PROCESS_URL);
await runA11yPanelTests(CONTENT_PROCESS_EXPECTED, env);
await disableAccessibilityInspector(env);
});

Просмотреть файл

@ -59,7 +59,7 @@ const tests = [
},
{
desc: "Reload the page.",
setup: async ({ panel }) => reload(panel.target),
setup: async ({ panel }) => reload(panel.accessibilityProxy.currentTarget),
expected: {
tree: [
{

Просмотреть файл

@ -140,10 +140,6 @@ async function addTestTab(url) {
state.details.accessible.role === "document"
);
// Wait for inspector load here to avoid protocol errors on shutdown, since
// accessibility panel test can be too fast.
await panel._toolbox.loadTool("inspector");
return {
tab,
browser: tab.linkedBrowser,

Просмотреть файл

@ -1,3 +1,7 @@
/* 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/. */
const fs = require("fs");
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -1,3 +1,7 @@
/* 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";
module.exports = {

Просмотреть файл

@ -10,7 +10,9 @@
#include "nsServiceManagerUtils.h"
#include "nsDocShellCID.h"
#include "nsIWebNavigationInfo.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/Unused.h"
#include "nsError.h"
#include "nsContentSecurityManager.h"
#include "nsDocShellLoadTypes.h"
@ -41,14 +43,21 @@ void MaybeCloseWindowHelper::SetShouldCloseWindow(bool aShouldCloseWindow) {
}
BrowsingContext* MaybeCloseWindowHelper::MaybeCloseWindow() {
if (mShouldCloseWindow) {
if (!mShouldCloseWindow) {
return mBrowsingContext;
}
// This method should not be called more than once, but it's better to avoid
// closing the current window again.
mShouldCloseWindow = false;
// Reset the window context to the opener window so that the dependent
// dialogs have a parent
RefPtr<BrowsingContext> opener = mBrowsingContext->GetOpener();
RefPtr<BrowsingContext> newBC = ChooseNewBrowsingContext(mBrowsingContext);
if (opener && !opener->IsDiscarded()) {
if (newBC != mBrowsingContext && newBC && !newBC->IsDiscarded()) {
mBCToClose = mBrowsingContext;
mBrowsingContext = opener;
mBrowsingContext = newBC;
// Now close the old window. Do it on a timer so that we don't run
// into issues trying to close the window before it has fully opened.
@ -56,10 +65,32 @@ BrowsingContext* MaybeCloseWindowHelper::MaybeCloseWindow() {
NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, 0,
nsITimer::TYPE_ONE_SHOT);
}
}
return mBrowsingContext;
}
already_AddRefed<BrowsingContext>
MaybeCloseWindowHelper::ChooseNewBrowsingContext(BrowsingContext* aBC) {
RefPtr<BrowsingContext> bc = aBC;
RefPtr<BrowsingContext> opener = bc->GetOpener();
if (opener && !opener->IsDiscarded()) {
return opener.forget();
}
if (!XRE_IsParentProcess()) {
return bc.forget();
}
CanonicalBrowsingContext* cbc = CanonicalBrowsingContext::Cast(aBC);
RefPtr<WindowGlobalParent> wgp = cbc->GetEmbedderWindowGlobal();
if (!wgp) {
return bc.forget();
}
return do_AddRef(wgp->BrowsingContext());
}
NS_IMETHODIMP
MaybeCloseWindowHelper::Notify(nsITimer* timer) {
NS_ASSERTION(mBCToClose, "No window to close after timer fired");
@ -135,7 +166,7 @@ nsDSURIContentListener::DoContent(const nsACString& aContentType,
RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
new MaybeCloseWindowHelper(mDocShell->GetBrowsingContext());
maybeCloseWindowHelper->SetShouldCloseWindow(true);
maybeCloseWindowHelper->MaybeCloseWindow();
Unused << maybeCloseWindowHelper->MaybeCloseWindow();
}
return NS_OK;
}

Просмотреть файл

@ -17,7 +17,7 @@ class nsIInterfaceRequestor;
class nsIWebNavigationInfo;
class nsPIDOMWindowOuter;
// Helper Class to eventually close an already openend window
// Helper Class to eventually close an already opened window
class MaybeCloseWindowHelper final : public nsITimerCallback {
public:
NS_DECL_ISUPPORTS
@ -27,9 +27,12 @@ class MaybeCloseWindowHelper final : public nsITimerCallback {
mozilla::dom::BrowsingContext* aContentContext);
/**
* Closes the provided window async (if mShouldCloseWindow is true)
* and returns its opener if the window was just opened. Otherwise
* returns the BrowsingContext provided in the constructor.
* Closes the provided window async (if mShouldCloseWindow is true) and
* returns a valid browsingContext to be used instead as parent for dialogs or
* similar things.
* In case mShouldCloseWindow is true, the final browsing context will be the
* a valid new chrome window to use. It can be the opener, or the opener's
* top, or the top chrome window.
*/
mozilla::dom::BrowsingContext* MaybeCloseWindow();
@ -39,6 +42,9 @@ class MaybeCloseWindowHelper final : public nsITimerCallback {
~MaybeCloseWindowHelper();
private:
already_AddRefed<mozilla::dom::BrowsingContext> ChooseNewBrowsingContext(
mozilla::dom::BrowsingContext* aBC);
/**
* The dom window associated to handle content.
*/

Просмотреть файл

@ -5933,6 +5933,29 @@ Nullable<WindowProxyHolder> Document::GetDefaultView() const {
return WindowProxyHolder(win->GetBrowsingContext());
}
nsIContent* Document::GetUnretargetedFocusedContent() const {
nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
if (!window) {
return nullptr;
}
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
window, nsFocusManager::eOnlyCurrentWindow,
getter_AddRefs(focusedWindow));
if (!focusedContent) {
return nullptr;
}
// be safe and make sure the element is from this document
if (focusedContent->OwnerDoc() != this) {
return nullptr;
}
if (focusedContent->ChromeOnlyAccess()) {
return focusedContent->FindFirstNonChromeOnlyAccessContent();
}
return focusedContent;
}
Element* Document::GetActiveElement() {
// Get the focused element.
Element* focusedElement = GetRetargetedFocusedElement();

Просмотреть файл

@ -3293,6 +3293,7 @@ class Document : public nsINode,
mozilla::ErrorResult& rv);
Nullable<WindowProxyHolder> GetDefaultView() const;
Element* GetActiveElement();
nsIContent* GetUnretargetedFocusedContent() const;
bool HasFocus(ErrorResult& rv) const;
void GetDesignMode(nsAString& aDesignMode);
void SetDesignMode(const nsAString& aDesignMode,

Просмотреть файл

@ -256,25 +256,13 @@ nsIContent* DocumentOrShadowRoot::Retarget(nsIContent* aContent) const {
}
Element* DocumentOrShadowRoot::GetRetargetedFocusedElement() {
if (nsCOMPtr<nsPIDOMWindowOuter> window = AsNode().OwnerDoc()->GetWindow()) {
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
window, nsFocusManager::eOnlyCurrentWindow,
getter_AddRefs(focusedWindow));
// be safe and make sure the element is from this document
if (focusedContent && focusedContent->OwnerDoc() == AsNode().OwnerDoc()) {
if (focusedContent->ChromeOnlyAccess()) {
focusedContent = focusedContent->FindFirstNonChromeOnlyAccessContent();
auto* content = AsNode().OwnerDoc()->GetUnretargetedFocusedContent();
if (!content) {
return nullptr;
}
if (focusedContent) {
if (nsIContent* retarget = Retarget(focusedContent)) {
if (nsIContent* retarget = Retarget(content)) {
return retarget->AsElement();
}
}
}
}
return nullptr;
}

Просмотреть файл

@ -357,8 +357,11 @@ class Selection final : public nsSupportsWeakReference,
* "documentboundary". Throws NS_ERROR_INVALID_ARG if alter, direction,
* or granularity has an unrecognized value.
*/
void Modify(const nsAString& aAlter, const nsAString& aDirection,
const nsAString& aGranularity, mozilla::ErrorResult& aRv);
// TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
MOZ_CAN_RUN_SCRIPT_BOUNDARY void Modify(const nsAString& aAlter,
const nsAString& aDirection,
const nsAString& aGranularity,
mozilla::ErrorResult& aRv);
MOZ_CAN_RUN_SCRIPT
void SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,

Просмотреть файл

@ -17,6 +17,7 @@
#include "imgIContainer.h"
#include "imgIRequest.h"
#include "nsFocusManager.h"
#include "nsFrameSelection.h"
#include "mozilla/dom/DataTransfer.h"
#include "nsIDocShell.h"
@ -673,37 +674,27 @@ static nsresult AppendImagePromise(nsITransferable* aTransferable,
}
#endif // XP_WIN
nsIContent* nsCopySupport::GetSelectionForCopy(Document* aDocument,
Selection** aSelection) {
*aSelection = nullptr;
already_AddRefed<Selection> nsCopySupport::GetSelectionForCopy(
Document* aDocument) {
PresShell* presShell = aDocument->GetPresShell();
if (!presShell) {
return nullptr;
}
nsCOMPtr<nsIContent> focusedContent;
nsCOMPtr<nsISelectionController> selectionController =
presShell->GetSelectionControllerForFocusedContent(
getter_AddRefs(focusedContent));
if (!selectionController) {
RefPtr<nsFrameSelection> frameSel = presShell->GetLastFocusedFrameSelection();
if (!frameSel) {
return nullptr;
}
RefPtr<Selection> sel = selectionController->GetSelection(
nsISelectionController::SELECTION_NORMAL);
sel.forget(aSelection);
return focusedContent;
RefPtr<Selection> sel = frameSel->GetSelection(SelectionType::eNormal);
return sel.forget();
}
bool nsCopySupport::CanCopy(Document* aDocument) {
if (!aDocument) return false;
RefPtr<Selection> sel;
GetSelectionForCopy(aDocument, getter_AddRefs(sel));
NS_ENSURE_TRUE(sel, false);
return !sel->IsCollapsed();
RefPtr<Selection> sel = GetSelectionForCopy(aDocument);
return sel && !sel->IsCollapsed();
}
static bool IsInsideRuby(nsINode* aNode) {
@ -717,7 +708,6 @@ static bool IsInsideRuby(nsINode* aNode) {
static bool IsSelectionInsideRuby(Selection* aSelection) {
uint32_t rangeCount = aSelection->RangeCount();
;
for (auto i : IntegerRange(rangeCount)) {
nsRange* range = aSelection->GetRangeAt(i);
if (!IsInsideRuby(range->GetClosestCommonInclusiveAncestor())) {
@ -776,7 +766,7 @@ bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
// If a selection was not supplied, try to find it.
RefPtr<Selection> sel = aSelection;
if (!sel) {
GetSelectionForCopy(doc, getter_AddRefs(sel));
sel = GetSelectionForCopy(doc);
}
// Retrieve the event target node from the start of the selection.

Просмотреть файл

@ -69,8 +69,8 @@ class nsCopySupport {
* and this focused content node returned. Otherwise, aSelection will be
* set to the document's selection and null will be returned.
*/
static nsIContent* GetSelectionForCopy(mozilla::dom::Document* aDocument,
mozilla::dom::Selection** aSelection);
static already_AddRefed<mozilla::dom::Selection> GetSelectionForCopy(
mozilla::dom::Document* aDocument);
/**
* Returns true if a copy operation is currently permitted based on the

Просмотреть файл

@ -3209,6 +3209,18 @@ void nsFrameLoader::RequestEpochUpdate(uint32_t aEpoch) {
}
}
void nsFrameLoader::RequestSHistoryUpdate(bool aImmediately) {
if (mSessionStoreListener) {
mSessionStoreListener->UpdateSHistoryChanges(aImmediately);
return;
}
// If remote browsing (e10s), handle this with the BrowserParent.
if (auto* browserParent = GetBrowserParent()) {
Unused << browserParent->SendUpdateSHistory(aImmediately);
}
}
void nsFrameLoader::Print(uint64_t aOuterWindowID,
nsIPrintSettings* aPrintSettings,
nsIWebProgressListener* aProgressListener,

Просмотреть файл

@ -211,6 +211,8 @@ class nsFrameLoader final : public nsStubMutationObserver,
void RequestEpochUpdate(uint32_t aEpoch);
void RequestSHistoryUpdate(bool aImmediately = false);
void Print(uint64_t aOuterWindowID, nsIPrintSettings* aPrintSettings,
nsIWebProgressListener* aProgressListener,
mozilla::ErrorResult& aRv);

Просмотреть файл

@ -83,6 +83,23 @@ interface nsISelectionController : nsISelectionDisplay
[noscript,nostdcall,notxpcom,binaryname(GetSelection)]
Selection getDOMSelection(in short aType);
/**
* Called when the selection controller should take the focus.
*
* This will take care to hide the previously-focused selection, show this
* selection, and repaint both.
*/
[noscript,nostdcall,notxpcom]
void selectionWillTakeFocus();
/**
* Called when the selection controller has lost the focus.
*
* This will take care to hide and repaint the selection.
*/
[noscript,nostdcall,notxpcom]
void selectionWillLoseFocus();
const short SCROLL_SYNCHRONOUS = 1<<1;
const short SCROLL_FIRST_ANCESTOR_ONLY = 1<<2;
const short SCROLL_CENTER_VERTICALLY = 1<<4;

Просмотреть файл

@ -635,6 +635,7 @@ skip-if = toolkit == 'android' || headless #bug 904183
skip-if = toolkit == 'android' || headless #bug 904183
[test_copypaste.xhtml]
skip-if = headless #bug 904183
[test_copypaste_disabled.html]
[test_createHTMLDocument.html]
[test_data_uri.html]
skip-if = verify

Просмотреть файл

@ -0,0 +1,90 @@
<!doctype html>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="copypaste.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<style>
@font-face {
font-family: Ahem;
src: url("Ahem.ttf");
}
body { font-family: Ahem; font-size: 20px; }
input, textarea {
font: inherit;
-moz-appearance: none;
padding: 0;
border: 0;
scrollbar-width: none;
}
</style>
<input id="disabled-input" disabled value="abcd"> efgh <br> <textarea rows=1 id="disabled-textarea" disabled>ijkl</textarea> mnop
<script>
function dragSelect(e, x1, x2, x3) {
dir = x2 > x1 ? 1 : -1;
synthesizeMouse(e, x1, 5, { type: "mousedown" });
synthesizeMouse(e, x1 + dir, 5, { type: "mousemove" });
if (x3)
synthesizeMouse(e, x3, 5, { type: "mousemove" });
synthesizeMouse(e, x2 - dir, 5, { type: "mousemove" });
synthesizeMouse(e, x2, 5, { type: "mouseup" });
}
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async function() {
const docShell = SpecialPowers.wrap(window).docShell;
const documentViewer = docShell.contentViewer.QueryInterface(
SpecialPowers.Ci.nsIContentViewerEdit
);
const clipboard = SpecialPowers.Services.clipboard;
function copySelectionToClipboard() {
return SimpleTest.promiseClipboardChange(
() => true,
() => {
documentViewer.copySelection();
}
);
}
function getLoadContext() {
return docShell.QueryInterface(SpecialPowers.Ci.nsILoadContext);
}
function getClipboardData(mime) {
var transferable = SpecialPowers.Cc[
"@mozilla.org/widget/transferable;1"
].createInstance(SpecialPowers.Ci.nsITransferable);
transferable.init(getLoadContext());
transferable.addDataFlavor(mime);
clipboard.getData(transferable, 1);
var data = SpecialPowers.createBlankObject();
transferable.getTransferData(mime, data);
return data;
}
function testClipboardValue(mime, expected) {
var data = SpecialPowers.wrap(getClipboardData(mime));
is(
data.value == null
? data.value
: data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data,
expected,
mime + " value in the clipboard"
);
return data.value;
}
for (let id of ["disabled-input", "disabled-textarea"]) {
let element = document.getElementById(id);
dragSelect(element, 0, 60);
await copySelectionToClipboard();
testClipboardValue("text/unicode", element.value.substr(0, 3));
}
SimpleTest.finish();
});
</script>

Просмотреть файл

@ -317,16 +317,15 @@ nsresult ContentEventHandler::InitCommon(SelectionType aSelectionType,
nsresult rv = InitBasic(aRequireFlush);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISelectionController> selectionController;
RefPtr<nsFrameSelection> frameSel;
if (PresShell* presShell = mDocument->GetPresShell()) {
selectionController = presShell->GetSelectionControllerForFocusedContent();
frameSel = presShell->GetLastFocusedFrameSelection();
}
if (NS_WARN_IF(!selectionController)) {
if (NS_WARN_IF(!frameSel)) {
return NS_ERROR_NOT_AVAILABLE;
}
mSelection =
selectionController->GetSelection(ToRawSelectionType(aSelectionType));
mSelection = frameSel->GetSelection(aSelectionType);
if (NS_WARN_IF(!mSelection)) {
return NS_ERROR_NOT_AVAILABLE;
}
@ -335,8 +334,7 @@ nsresult ContentEventHandler::InitCommon(SelectionType aSelectionType,
if (mSelection->Type() == SelectionType::eNormal) {
normalSelection = mSelection;
} else {
normalSelection = selectionController->GetSelection(
nsISelectionController::SELECTION_NORMAL);
normalSelection = frameSel->GetSelection(SelectionType::eNormal);
if (NS_WARN_IF(!normalSelection)) {
return NS_ERROR_NOT_AVAILABLE;
}

Просмотреть файл

@ -5165,7 +5165,7 @@ nsresult EventStateManager::HandleMiddleClickPaste(
if (NS_WARN_IF(!document)) {
return NS_ERROR_FAILURE;
}
nsCopySupport::GetSelectionForCopy(document, getter_AddRefs(selection));
selection = nsCopySupport::GetSelectionForCopy(document);
if (NS_WARN_IF(!selection)) {
return NS_ERROR_FAILURE;
}
@ -5214,8 +5214,7 @@ nsresult EventStateManager::HandleMiddleClickPaste(
}
// Check if the editor is still the good target to paste.
if (aTextEditor->Destroyed() || aTextEditor->IsReadonly() ||
aTextEditor->IsDisabled()) {
if (aTextEditor->Destroyed() || aTextEditor->IsReadonly()) {
// XXX Should we consume the event when the editor is readonly and/or
// disabled?
return NS_OK;

Просмотреть файл

@ -6639,18 +6639,13 @@ void HTMLInputElement::OnValueChanged(ValueChangeKind aKind) {
}
bool HTMLInputElement::HasCachedSelection() {
bool isCached = false;
TextControlState* state = GetEditorState();
if (state) {
isCached = state->IsSelectionCached() &&
state->HasNeverInitializedBefore() &&
if (!state) {
return false;
}
return state->IsSelectionCached() && state->HasNeverInitializedBefore() &&
state->GetSelectionProperties().GetStart() !=
state->GetSelectionProperties().GetEnd();
if (isCached) {
state->WillInitEagerly();
}
}
return isCached;
}
void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) {

Просмотреть файл

@ -174,10 +174,6 @@ class RestoreSelectionState : public Runnable {
mFrame->SetSelectionRange(properties.GetStart(), properties.GetEnd(),
properties.GetDirection());
}
if (!mTextControlState->mSelectionRestoreEagerInit) {
mTextControlState->HideSelectionIfBlurred();
}
mTextControlState->mSelectionRestoreEagerInit = false;
}
if (mTextControlState) {
@ -217,7 +213,6 @@ class MOZ_RAII AutoRestoreEditorState final {
// appearing the method in profile. So, this class should check if it's
// necessary to call.
uint32_t flags = mSavedFlags;
flags &= ~(nsIEditor::eEditorDisabledMask);
flags &= ~(nsIEditor::eEditorReadonlyMask);
flags |= nsIEditor::eEditorDontEchoPassword;
if (mSavedFlags != flags) {
@ -361,6 +356,8 @@ class TextInputSelectionController final : public nsSupportsWeakReference,
int16_t aStartOffset,
int16_t aEndOffset,
bool* aRetval) override;
void SelectionWillTakeFocus() override;
void SelectionWillLoseFocus() override;
private:
RefPtr<nsFrameSelection> mFrameSelection;
@ -771,6 +768,22 @@ TextInputSelectionController::SelectAll() {
return frameSelection->SelectAll();
}
void TextInputSelectionController::SelectionWillTakeFocus() {
if (mFrameSelection) {
if (PresShell* shell = mFrameSelection->GetPresShell()) {
shell->FrameSelectionWillTakeFocus(*mFrameSelection);
}
}
}
void TextInputSelectionController::SelectionWillLoseFocus() {
if (mFrameSelection) {
if (PresShell* shell = mFrameSelection->GetPresShell()) {
shell->FrameSelectionWillLoseFocus(*mFrameSelection);
}
}
}
NS_IMETHODIMP
TextInputSelectionController::CheckVisibility(nsINode* node,
int16_t startOffset,
@ -1396,7 +1409,6 @@ TextControlState::TextControlState(TextControlElement* aOwningElement)
mEditorInitialized(false),
mValueTransferInProgress(false),
mSelectionCached(true),
mSelectionRestoreEagerInit(false),
mPlaceholderVisibility(false),
mPreviewVisibility(false)
// When adding more member variable initializations here, add the same
@ -1419,7 +1431,6 @@ TextControlState* TextControlState::Construct(
state->mEditorInitialized = false;
state->mValueTransferInProgress = false;
state->mSelectionCached = true;
state->mSelectionRestoreEagerInit = false;
state->mPlaceholderVisibility = false;
state->mPreviewVisibility = false;
// When adding more member variable initializations here, add the same
@ -1640,7 +1651,9 @@ nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
mTextListener = new TextInputListener(mTextCtrlElement);
mTextListener->SetFrame(mBoundFrame);
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
// Editor will override this as needed from InitializeSelection.
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
// Get the caret and make it a selection listener.
// FYI: It's safe to use raw pointer for calling
@ -1884,21 +1897,13 @@ nsresult TextControlState::PrepareEditor(const nsAString* aValue) {
editorFlags = newTextEditor->Flags();
// Check if the readonly attribute is set.
if (mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) {
//
// TODO: Should probably call IsDisabled(), as it is cheaper.
if (mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
editorFlags |= nsIEditor::eEditorReadonlyMask;
}
// Check if the disabled attribute is set.
// TODO: call IsDisabled() here!
if (mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
editorFlags |= nsIEditor::eEditorDisabledMask;
}
// Disable the selection if necessary.
if (newTextEditor->IsDisabled()) {
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_OFF);
}
SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
if (shouldInitializeEditor) {
@ -2385,6 +2390,10 @@ void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
AutoTextControlHandlingState handlingUnbindFromFrame(
*this, TextControlAction::UnbindFromFrame);
if (mSelCon) {
mSelCon->SelectionWillLoseFocus();
}
// We need to start storing the value outside of the editor if we're not
// going to use it anymore, so retrieve it for now.
nsAutoString value;
@ -3087,13 +3096,6 @@ void TextControlState::UpdateOverlayTextVisibility(bool aNotify) {
}
}
void TextControlState::HideSelectionIfBlurred() {
MOZ_ASSERT(mSelCon, "Should have a selection controller if we have a frame!");
if (!nsContentUtils::IsFocusedContent(mTextCtrlElement)) {
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
}
}
bool TextControlState::EditorHasComposition() {
return mTextEditor && mTextEditor->IsIMEComposing();
}

Просмотреть файл

@ -274,7 +274,6 @@ class TextControlState final : public SupportsWeakPtr<TextControlState> {
void SetPreviewText(const nsAString& aValue, bool aNotify);
void GetPreviewText(nsAString& aValue);
bool GetPreviewVisibility() { return mPreviewVisibility; }
void HideSelectionIfBlurred();
struct SelectionProperties {
public:
@ -315,7 +314,6 @@ class TextControlState final : public SupportsWeakPtr<TextControlState> {
bool IsSelectionCached() const { return mSelectionCached; }
SelectionProperties& GetSelectionProperties() { return mSelectionProperties; }
MOZ_CAN_RUN_SCRIPT void SetSelectionProperties(SelectionProperties& aProps);
void WillInitEagerly() { mSelectionRestoreEagerInit = true; }
bool HasNeverInitializedBefore() const { return !mEverInited; }
// Sync up our selection properties with our editor prior to being destroyed.
// This will invoke UnbindFromFrame() to ensure that we grab whatever
@ -456,8 +454,6 @@ class TextControlState final : public SupportsWeakPtr<TextControlState> {
bool mValueTransferInProgress; // Whether a value is being transferred to the
// frame
bool mSelectionCached; // Whether mSelectionProperties is valid
mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing
// because of selection restore
bool mPlaceholderVisibility;
bool mPreviewVisibility;

Просмотреть файл

@ -2015,6 +2015,14 @@ mozilla::ipc::IPCResult BrowserChild::RecvUpdateEpoch(const uint32_t& aEpoch) {
return IPC_OK();
}
mozilla::ipc::IPCResult BrowserChild::RecvUpdateSHistory(
const bool& aImmediately) {
if (mSessionStoreListener) {
mSessionStoreListener->UpdateSHistoryChanges(aImmediately);
}
return IPC_OK();
}
// In case handling repeated keys takes much time, we skip firing new ones.
bool BrowserChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent) {
if (mRepeatedKeyEventTime.IsNull() || !aEvent.CanSkipInRemoteProcess() ||
@ -4003,8 +4011,9 @@ bool BrowserChild::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
Unused << SendSessionStoreUpdate(
docShellCaps, privatedMode, positions, positionDescendants, inputs,
idVals, xPathVals, origins, keys, values, isFullStorage, aFlushId,
aIsFinal, mSessionStoreListener->GetEpoch());
idVals, xPathVals, origins, keys, values, isFullStorage,
store->GetAndClearSHistoryChanged(), aFlushId, aIsFinal,
mSessionStoreListener->GetEpoch());
return true;
}

Просмотреть файл

@ -381,6 +381,8 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
mozilla::ipc::IPCResult RecvUpdateEpoch(const uint32_t& aEpoch);
mozilla::ipc::IPCResult RecvUpdateSHistory(const bool& aImmediately);
mozilla::ipc::IPCResult RecvNativeSynthesisResponse(
const uint64_t& aObserverId, const nsCString& aResponse);

Просмотреть файл

@ -2925,7 +2925,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
const nsTArray<CollectedInputDataValue>& aXPathVals,
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
const uint32_t& aFlushId, const bool& aIsFinal, const uint32_t& aEpoch) {
const bool aNeedCollectSHistory, const uint32_t& aFlushId,
const bool& aIsFinal, const uint32_t& aEpoch) {
UpdateSessionStoreData data;
if (aDocShellCaps.isSome()) {
data.mDocShellCaps.Construct() = aDocShellCaps.value();
@ -2982,8 +2983,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
NS_ENSURE_TRUE(ok, IPC_OK());
nsresult rv = funcs->UpdateSessionStore(mFrameElement, aFlushId, aIsFinal,
aEpoch, dataVal);
nsresult rv = funcs->UpdateSessionStore(
mFrameElement, aFlushId, aIsFinal, aEpoch, dataVal, aNeedCollectSHistory);
NS_ENSURE_SUCCESS(rv, IPC_OK());
return IPC_OK();

Просмотреть файл

@ -340,7 +340,8 @@ class BrowserParent final : public PBrowserParent,
const nsTArray<CollectedInputDataValue>& aXPathVals,
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
const uint32_t& aFlushId, const bool& aIsFinal, const uint32_t& aEpoch);
const bool aNeedCollectSHistory, const uint32_t& aFlushId,
const bool& aIsFinal, const uint32_t& aEpoch);
mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow(
PBrowserParent* aOpener, const nsString& aURL, const nsString& aName,

Просмотреть файл

@ -616,12 +616,14 @@ parent:
CollectedInputDataValue[] aXPathVals,
nsCString[] aOrigins, nsString[] aKeys,
nsString[] aValues, bool aIsFullStorage,
uint32_t aFlushId, bool aIsFinal, uint32_t aEpoch);
bool aNeedCollectSHistory, uint32_t aFlushId,
bool aIsFinal, uint32_t aEpoch);
child:
async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
async FlushTabState(uint32_t aFlushId, bool aIsFinal);
async UpdateEpoch(uint32_t aEpoch);
async UpdateSHistory(bool aImmediately);
parent:

Просмотреть файл

@ -112,6 +112,11 @@ interface FrameLoader {
*/
void requestEpochUpdate(unsigned long aEpoch);
/**
* Request a session history update in native sessionStoreListeners.
*/
void requestSHistoryUpdate(boolean aImmediately);
/**
* Print the current document.
*

Просмотреть файл

@ -285,12 +285,6 @@ nsresult EditorBase::Init(Document& aDocument, Element* aRoot,
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetCaretReadOnly(false) failed, but ignored");
rvIgnored = selectionController->SetDisplaySelection(
nsISelectionController::SELECTION_ON);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetDisplaySelection(nsISelectionController::"
"SELECTION_ON) failed, but ignored");
// Show all the selection reflected to user.
rvIgnored =
selectionController->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
@ -309,7 +303,7 @@ nsresult EditorBase::Init(Document& aDocument, Element* aRoot,
// Make sure that the editor will be destroyed properly
mDidPreDestroy = false;
// Make sure that the ediotr will be created properly
// Make sure that the editor will be created properly
mDidPostCreate = false;
return NS_OK;
@ -2505,7 +2499,7 @@ nsresult EditorBase::GetPreferredIMEState(IMEState* aState) {
aState->mEnabled = IMEState::ENABLED;
aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
if (IsReadonly() || IsDisabled()) {
if (IsReadonly()) {
aState->mEnabled = IMEState::DISABLED;
return NS_OK;
}
@ -5039,7 +5033,7 @@ nsresult EditorBase::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
"HandleKeyPressEvent gets non-keypress event");
// if we are readonly or disabled, then do nothing.
if (IsReadonly() || IsDisabled()) {
if (IsReadonly()) {
// consume backspace for disabled and readonly textfields, to prevent
// back in history, which could be confusing to users
if (aKeyboardEvent->mKeyCode == NS_VK_BACK) {
@ -5134,21 +5128,13 @@ nsresult EditorBase::InitializeSelection(EventTarget* aFocusEventTarget) {
caret->SetIgnoreUserModify(targetNode->OwnerDoc()->HasFlag(NODE_IS_EDITABLE));
// Init selection
rvIgnored = selectionController->SetDisplaySelection(
nsISelectionController::SELECTION_ON);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetDisplaySelection() failed, but ignored");
rvIgnored =
selectionController->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetSelectionFlags() failed, but ignored");
rvIgnored = selectionController->RepaintSelection(
nsISelectionController::SELECTION_NORMAL);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::RepaintSelection() failed, but ignored");
selectionController->SelectionWillTakeFocus();
// If the computed selection root isn't root content, we should set it
// as selection ancestor limit. However, if that is root element, it means
@ -5192,26 +5178,6 @@ nsresult EditorBase::InitializeSelection(EventTarget* aFocusEventTarget) {
return NS_OK;
}
class RepaintSelectionRunner final : public Runnable {
public:
explicit RepaintSelectionRunner(nsISelectionController* aSelectionController)
: Runnable("RepaintSelectionRunner"),
mSelectionController(aSelectionController) {}
NS_IMETHOD Run() override {
DebugOnly<nsresult> rvIgnored = mSelectionController->RepaintSelection(
nsISelectionController::SELECTION_NORMAL);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::RepaintSelection(nsISelectionController::"
"SELECTION_NORMAL) failed, but ignored");
return NS_OK;
}
private:
nsCOMPtr<nsISelectionController> mSelectionController;
};
nsresult EditorBase::FinalizeSelection() {
nsCOMPtr<nsISelectionController> selectionController =
GetSelectionController();
@ -5243,58 +5209,11 @@ nsresult EditorBase::FinalizeSelection() {
return NS_ERROR_NOT_INITIALIZED;
}
focusManager->UpdateCaretForCaretBrowsingMode();
if (!HasIndependentSelection()) {
// If this editor doesn't have an independent selection, i.e., it must
// mean that it is an HTML editor, the selection controller is shared with
// presShell. So, even this editor loses focus, other part of the document
// may still have focus.
RefPtr<Document> doc = GetDocument();
ErrorResult ret;
if (!doc || !doc->HasFocus(ret)) {
// If the document already lost focus, mark the selection as disabled.
DebugOnly<nsresult> rvIgnored = selectionController->SetDisplaySelection(
nsISelectionController::SELECTION_DISABLED);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetDisplaySelection(nsISelectionController::"
"SELECTION_DISABLED) failed, but ignored");
} else {
// Otherwise, mark selection as normal because outside of a
// contenteditable element should be selected with normal selection
// color after here.
DebugOnly<nsresult> rvIgnored = selectionController->SetDisplaySelection(
nsISelectionController::SELECTION_ON);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetDisplaySelection(nsISelectionController::"
"SELECTION_ON) failed, but ignored");
if (nsCOMPtr<nsINode> node = do_QueryInterface(GetDOMEventTarget())) {
if (node->OwnerDoc()->GetUnretargetedFocusedContent() != node) {
selectionController->SelectionWillLoseFocus();
}
} else if (IsFormWidget() || IsPasswordEditor() || IsReadonly() ||
IsDisabled() || IsInputFiltered()) {
// In <input> or <textarea>, the independent selection should be hidden
// while this editor doesn't have focus.
DebugOnly<nsresult> rvIgnored = selectionController->SetDisplaySelection(
nsISelectionController::SELECTION_HIDDEN);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetDisplaySelection(nsISelectionController::"
"SELECTION_HIDDEN) failed, but ignored");
} else {
// Otherwise, although we're not sure how this case happens, the
// independent selection should be marked as disabled.
DebugOnly<nsresult> rvIgnored = selectionController->SetDisplaySelection(
nsISelectionController::SELECTION_DISABLED);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsISelectionController::SetDisplaySelection(nsISelectionController::"
"SELECTION_DISABLED) failed, but ignored");
}
// FinalizeSelection might be called from ContentRemoved even if selection
// isn't updated. So we need to call RepaintSelection after updated it.
nsContentUtils::AddScriptRunner(
new RepaintSelectionRunner(selectionController));
return NS_OK;
}

Просмотреть файл

@ -491,10 +491,6 @@ class EditorBase : public nsIEditor,
return (mFlags & nsIEditor::eEditorReadonlyMask) != 0;
}
bool IsDisabled() const {
return (mFlags & nsIEditor::eEditorDisabledMask) != 0;
}
bool IsInputFiltered() const {
return (mFlags & nsIEditor::eEditorFilterInputMask) != 0;
}
@ -2565,8 +2561,7 @@ class EditorBase : public nsIEditor,
// Check for password/readonly/disabled, which are not spellchecked
// regardless of DOM. Also, check to see if spell check should be skipped
// or not.
return !IsPasswordEditor() && !IsReadonly() && !IsDisabled() &&
!ShouldSkipSpellCheck();
return !IsPasswordEditor() && !IsReadonly() && !ShouldSkipSpellCheck();
}
/**

Просмотреть файл

@ -670,7 +670,7 @@ nsresult EditorEventListener::MouseClick(WidgetMouseEvent* aMouseClickEvent) {
}
// nothing to do if editor isn't editable or clicked on out of the editor.
RefPtr<TextEditor> textEditor = mEditorBase->AsTextEditor();
if (textEditor->IsReadonly() || textEditor->IsDisabled() ||
if (textEditor->IsReadonly() ||
!textEditor->IsAcceptableInputEvent(aMouseClickEvent)) {
return NS_OK;
}
@ -840,8 +840,8 @@ nsresult EditorEventListener::DragOverOrDrop(DragEvent* aDragEvent) {
return NS_OK;
}
bool notEditable = !dropParentContent->IsEditable() ||
mEditorBase->IsReadonly() || mEditorBase->IsDisabled();
bool notEditable =
!dropParentContent->IsEditable() || mEditorBase->IsReadonly();
// First of all, hide caret if we won't insert the drop data into the editor
// obviously.
@ -977,7 +977,7 @@ bool EditorEventListener::DragEventHasSupportingData(
bool EditorEventListener::CanInsertAtDropPosition(DragEvent* aDragEvent) {
MOZ_ASSERT(
!DetachedFromEditorOrDefaultPrevented(aDragEvent->WidgetEventPtr()));
MOZ_ASSERT(!mEditorBase->IsReadonly() && !mEditorBase->IsDisabled());
MOZ_ASSERT(!mEditorBase->IsReadonly());
MOZ_ASSERT(DragEventHasSupportingData(aDragEvent));
// If there is no source node, this is probably an external drag and the
@ -1086,8 +1086,8 @@ nsresult EditorEventListener::HandleChangeComposition(
return NS_OK;
}
// if we are readonly or disabled, then do nothing.
if (textEditor->IsReadonly() || textEditor->IsDisabled()) {
// if we are readonly, then do nothing.
if (textEditor->IsReadonly()) {
return NS_OK;
}
@ -1117,11 +1117,7 @@ nsresult EditorEventListener::Focus(InternalFocusEvent* aFocusEvent) {
return NS_OK;
}
// Don't turn on selection and caret when the editor is disabled.
RefPtr<EditorBase> editorBase(mEditorBase);
if (editorBase->IsDisabled()) {
return NS_OK;
}
// Spell check a textarea the first time that it is focused.
SpellCheckIfNeeded();

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше