зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
026c5990d5
|
@ -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();
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче