зеркало из https://github.com/mozilla/gecko-dev.git
Bug 921942 - Broadcast scroll positions r=yoric
From 5f535195e10d6cccbedbdf607ff194450a40c4ed Mon Sep 17 00:00:00 2001
This commit is contained in:
Родитель
741546f1ca
Коммит
c9b3ca4e13
|
@ -16,12 +16,12 @@ let Cr = Components.results;
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/Timer.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
"resource:///modules/sessionstore/Utils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
|
||||
"resource:///modules/sessionstore/DocShellCapabilities.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
|
||||
"resource:///modules/sessionstore/PageStyle.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
|
||||
"resource:///modules/sessionstore/ScrollPosition.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
|
||||
"resource:///modules/sessionstore/SessionHistory.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
|
||||
|
@ -29,6 +29,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "TextAndScrollData",
|
||||
"resource:///modules/sessionstore/TextAndScrollData.jsm");
|
||||
|
||||
Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
|
||||
let gFrameTree = new FrameTree(this);
|
||||
|
||||
/**
|
||||
* Returns a lazy function that will evaluate the given
|
||||
* function |fn| only once and cache its return value.
|
||||
|
@ -78,7 +81,7 @@ let EventListener = {
|
|||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "pageshow":
|
||||
if (event.persisted)
|
||||
if (event.persisted && event.target == content.document)
|
||||
sendAsyncMessage("SessionStore:pageshow");
|
||||
break;
|
||||
case "input":
|
||||
|
@ -198,6 +201,43 @@ let ProgressListener = {
|
|||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
|
||||
/**
|
||||
* Listens for scroll position changes. Whenever the user scrolls the top-most
|
||||
* frame we update the scroll position and will restore it when requested.
|
||||
*
|
||||
* Causes a SessionStore:update message to be sent that contains the current
|
||||
* scroll positions as a tree of strings. If no frame of the whole frame tree
|
||||
* is scrolled this will return null so that we don't tack a property onto
|
||||
* the tabData object in the parent process.
|
||||
*
|
||||
* Example:
|
||||
* {scroll: "100,100", children: [null, null, {scroll: "200,200"}]}
|
||||
*/
|
||||
let ScrollPositionListener = {
|
||||
init: function () {
|
||||
addEventListener("scroll", this);
|
||||
gFrameTree.addObserver(this);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
let frame = event.target && event.target.defaultView;
|
||||
|
||||
// Don't collect scroll data for frames created at or after the load event
|
||||
// as SessionStore can't restore scroll data for those.
|
||||
if (frame && gFrameTree.contains(frame)) {
|
||||
MessageQueue.push("scroll", () => this.collect());
|
||||
}
|
||||
},
|
||||
|
||||
onFrameTreeReset: function () {
|
||||
MessageQueue.push("scroll", () => null);
|
||||
},
|
||||
|
||||
collect: function () {
|
||||
return gFrameTree.map(ScrollPosition.collect);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listens for changes to the page style. Whenever a different page style is
|
||||
* selected or author styles are enabled/disabled we send a message with the
|
||||
|
@ -489,5 +529,6 @@ SyncHandler.init();
|
|||
ProgressListener.init();
|
||||
PageStyleListener.init();
|
||||
SessionStorageListener.init();
|
||||
ScrollPositionListener.init();
|
||||
DocShellCapabilitiesListener.init();
|
||||
PrivacyListener.init();
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
/* 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";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FrameTree"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
|
||||
const EXPORTED_METHODS = ["addObserver", "contains", "map"];
|
||||
|
||||
/**
|
||||
* A FrameTree represents all frames that were reachable when the document
|
||||
* was loaded. We use this information to ignore frames when collecting
|
||||
* sessionstore data as we can't currently restore anything for frames that
|
||||
* have been created dynamically after or at the load event.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function FrameTree(chromeGlobal) {
|
||||
let internal = new FrameTreeInternal(chromeGlobal);
|
||||
let external = {};
|
||||
|
||||
for (let method of EXPORTED_METHODS) {
|
||||
external[method] = internal[method].bind(internal);
|
||||
}
|
||||
|
||||
return Object.freeze(external);
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal frame tree API that the public one points to.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function FrameTreeInternal(chromeGlobal) {
|
||||
// A WeakMap that uses frames (DOMWindows) as keys and their initial indices
|
||||
// in their parents' child lists as values. Suppose we have a root frame with
|
||||
// three subframes i.e. a page with three iframes. The WeakMap would have
|
||||
// four entries and look as follows:
|
||||
//
|
||||
// root -> 0
|
||||
// subframe1 -> 0
|
||||
// subframe2 -> 1
|
||||
// subframe3 -> 2
|
||||
//
|
||||
// Should one of the subframes disappear we will stop collecting data for it
|
||||
// as |this._frames.has(frame) == false|. All other subframes will maintain
|
||||
// their initial indices to ensure we can restore frame data appropriately.
|
||||
this._frames = new WeakMap();
|
||||
|
||||
// The Set of observers that will be notified when the frame changes.
|
||||
this._observers = new Set();
|
||||
|
||||
// The chrome global we use to retrieve the current DOMWindow.
|
||||
this._chromeGlobal = chromeGlobal;
|
||||
|
||||
// Register a web progress listener to be notified about new page loads.
|
||||
let docShell = chromeGlobal.docShell;
|
||||
let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||
}
|
||||
|
||||
FrameTreeInternal.prototype = {
|
||||
|
||||
// Returns the docShell's current global.
|
||||
get content() {
|
||||
return this._chromeGlobal.content;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a given observer |obs| to the set of observers that will be notified
|
||||
* when the frame tree is reset (when a new document starts loading) or
|
||||
* recollected (when a document finishes loading).
|
||||
*
|
||||
* @param obs (object)
|
||||
*/
|
||||
addObserver: function (obs) {
|
||||
this._observers.add(obs);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies all observers that implement the given |method|.
|
||||
*
|
||||
* @param method (string)
|
||||
*/
|
||||
notifyObservers: function (method) {
|
||||
for (let obs of this._observers) {
|
||||
if (obs.hasOwnProperty(method)) {
|
||||
obs[method]();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether a given |frame| is contained in the collected frame tree.
|
||||
* If it is not, this indicates that we should not collect data for it.
|
||||
*
|
||||
* @param frame (nsIDOMWindow)
|
||||
* @return bool
|
||||
*/
|
||||
contains: function (frame) {
|
||||
return this._frames.has(frame);
|
||||
},
|
||||
|
||||
/**
|
||||
* Recursively applies the given function |cb| to the stored frame tree. Use
|
||||
* this method to collect sessionstore data for all reachable frames stored
|
||||
* in the frame tree.
|
||||
*
|
||||
* If a given function |cb| returns a value, it must be an object. It may
|
||||
* however return "null" to indicate that there is no data to be stored for
|
||||
* the given frame.
|
||||
*
|
||||
* The object returned by |cb| cannot have any property named "children" as
|
||||
* that is used to store information about subframes in the tree returned
|
||||
* by |map()| and might be overridden.
|
||||
*
|
||||
* @param cb (function)
|
||||
* @return object
|
||||
*/
|
||||
map: function (cb) {
|
||||
let frames = this._frames;
|
||||
|
||||
function walk(frame) {
|
||||
let obj = cb(frame) || {};
|
||||
|
||||
if (frames.has(frame)) {
|
||||
let children = [];
|
||||
|
||||
Array.forEach(frame.frames, subframe => {
|
||||
// Don't collect any data if the frame is not contained in the
|
||||
// initial frame tree. It's a dynamic frame added later.
|
||||
if (!frames.has(subframe)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the frame's original position in its parent's child list.
|
||||
let index = frames.get(subframe);
|
||||
|
||||
// Recursively collect data for the current subframe.
|
||||
let result = walk(subframe, cb);
|
||||
if (result && Object.keys(result).length) {
|
||||
children[index] = result;
|
||||
}
|
||||
});
|
||||
|
||||
if (children.length) {
|
||||
obj.children = children;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(obj).length ? obj : null;
|
||||
}
|
||||
|
||||
return walk(this.content);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores a given |frame| and its children in the frame tree.
|
||||
*
|
||||
* @param frame (nsIDOMWindow)
|
||||
* @param index (int)
|
||||
* The index in the given frame's parent's child list.
|
||||
*/
|
||||
collect: function (frame, index = 0) {
|
||||
// Mark the given frame as contained in the frame tree.
|
||||
this._frames.set(frame, index);
|
||||
|
||||
// Mark the given frame's subframes as contained in the tree.
|
||||
Array.forEach(frame.frames, this.collect, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @see nsIWebProgressListener.onStateChange
|
||||
*
|
||||
* We want to be notified about:
|
||||
* - new documents that start loading to clear the current frame tree;
|
||||
* - completed document loads to recollect reachable frames.
|
||||
*/
|
||||
onStateChange: function (webProgress, request, stateFlags, status) {
|
||||
// Ignore state changes for subframes because we're only interested in the
|
||||
// top-document starting or stopping its load. We thus only care about any
|
||||
// changes to the root of the frame tree, not to any of its nodes/leafs.
|
||||
if (!webProgress.isTopLevel || webProgress.DOMWindow != this.content) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
|
||||
// Clear the list of frames until we can recollect it.
|
||||
this._frames.clear();
|
||||
|
||||
// Notify observers that the frame tree has been reset.
|
||||
this.notifyObservers("onFrameTreeReset");
|
||||
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||
// The document and its resources have finished loading.
|
||||
this.collect(webProgress.DOMWindow);
|
||||
|
||||
// Notify observers that the frame tree has been reset.
|
||||
this.notifyObservers("onFrameTreeCollected");
|
||||
}
|
||||
},
|
||||
|
||||
// Unused nsIWebProgressListener methods.
|
||||
onLocationChange: function () {},
|
||||
onProgressChange: function () {},
|
||||
onSecurityChange: function () {},
|
||||
onStatusChange: function () {},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
/* 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";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ScrollPosition"];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
/**
|
||||
* It provides methods to collect and restore scroll positions for single
|
||||
* frames and frame trees.
|
||||
*
|
||||
* This is a child process module.
|
||||
*/
|
||||
this.ScrollPosition = Object.freeze({
|
||||
/**
|
||||
* Collects scroll position data for any given |frame| in the frame hierarchy.
|
||||
*
|
||||
* @param frame (DOMWindow)
|
||||
*
|
||||
* @return {scroll: "x,y"} e.g. {scroll: "100,200"}
|
||||
* Returns null when there is no scroll data we want to store for the
|
||||
* given |frame|.
|
||||
*/
|
||||
collect: function (frame) {
|
||||
let ifreq = frame.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let scrollX = {}, scrollY = {};
|
||||
utils.getScrollXY(false /* no layout flush */, scrollX, scrollY);
|
||||
|
||||
if (scrollX.value || scrollY.value) {
|
||||
return {scroll: scrollX.value + "," + scrollY.value};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores scroll position data for any given |frame| in the frame hierarchy.
|
||||
*
|
||||
* @param frame (DOMWindow)
|
||||
* @param value (object, see collect())
|
||||
*/
|
||||
restore: function (frame, value) {
|
||||
let match;
|
||||
|
||||
if (value && (match = /(\d+),(\d+)/.exec(value))) {
|
||||
frame.scrollTo(match[1], match[2]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores scroll position data for the current frame hierarchy starting at
|
||||
* |root| using the given scroll position |data|.
|
||||
*
|
||||
* If the given |root| frame's hierarchy doesn't match that of the given
|
||||
* |data| object we will silently discard data for unreachable frames. We
|
||||
* may as well assign scroll positions to the wrong frames if some were
|
||||
* reordered or removed.
|
||||
*
|
||||
* @param root (DOMWindow)
|
||||
* @param data (object)
|
||||
* {
|
||||
* scroll: "100,200",
|
||||
* children: [
|
||||
* {scroll: "100,200"},
|
||||
* null,
|
||||
* {scroll: "200,300", children: [ ... ]}
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
restoreTree: function (root, data) {
|
||||
if (data.hasOwnProperty("scroll")) {
|
||||
this.restore(root, data.scroll);
|
||||
}
|
||||
|
||||
if (!data.hasOwnProperty("children")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let frames = root.frames;
|
||||
data.children.forEach((child, index) => {
|
||||
if (child && index < frames.length) {
|
||||
this.restoreTree(frames[index], child);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -110,6 +110,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
|||
"resource:///modules/RecentWindow.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
|
||||
"resource:///modules/devtools/scratchpad-manager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
|
||||
"resource:///modules/sessionstore/ScrollPosition.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
|
||||
"resource:///modules/sessionstore/SessionSaver.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
|
||||
|
@ -630,14 +632,17 @@ let SessionStoreInternal = {
|
|||
let browser;
|
||||
switch (aEvent.type) {
|
||||
case "load":
|
||||
// If __SS_restore_data is set, then we need to restore the document
|
||||
// (form data, scrolling, etc.). This will only happen when a tab is
|
||||
// first restored.
|
||||
browser = aEvent.currentTarget;
|
||||
TabStateCache.delete(browser);
|
||||
if (browser.__SS_restore_data)
|
||||
this.restoreDocument(win, browser, aEvent);
|
||||
this.onTabLoad(win, browser);
|
||||
// Ignore load events from subframes.
|
||||
if (aEvent.target == browser.contentDocument) {
|
||||
// If __SS_restore_data is set, then we need to restore the document
|
||||
// (form data, scrolling, etc.). This will only happen when a tab is
|
||||
// first restored.
|
||||
TabStateCache.delete(browser);
|
||||
if (browser.__SS_restore_data)
|
||||
this.restoreDocument(win, browser, aEvent);
|
||||
this.onTabLoad(win, browser);
|
||||
}
|
||||
break;
|
||||
case "SwapDocShells":
|
||||
browser = aEvent.currentTarget;
|
||||
|
@ -2654,6 +2659,7 @@ let SessionStoreInternal = {
|
|||
|
||||
// Update the persistent tab state cache with |tabData| information.
|
||||
TabStateCache.updatePersistent(browser, {
|
||||
scroll: tabData.scroll || null,
|
||||
storage: tabData.storage || null,
|
||||
disallow: tabData.disallow || null,
|
||||
pageStyle: tabData.pageStyle || null
|
||||
|
@ -2821,8 +2827,15 @@ let SessionStoreInternal = {
|
|||
// restore those aspects of the currently active documents which are not
|
||||
// preserved in the plain history entries (mainly scroll state and text data)
|
||||
browser.__SS_restore_data = tabData.entries[activeIndex] || {};
|
||||
browser.__SS_restore_pageStyle = tabData.pageStyle || "";
|
||||
browser.__SS_restore_tab = aTab;
|
||||
|
||||
if (tabData.pageStyle) {
|
||||
RestoreData.set(browser, "pageStyle", tabData.pageStyle);
|
||||
}
|
||||
if (tabData.scroll) {
|
||||
RestoreData.set(browser, "scroll", tabData.scroll);
|
||||
}
|
||||
|
||||
didStartLoad = true;
|
||||
try {
|
||||
// In order to work around certain issues in session history, we need to
|
||||
|
@ -2837,7 +2850,6 @@ let SessionStoreInternal = {
|
|||
}
|
||||
} else {
|
||||
browser.__SS_restore_data = {};
|
||||
browser.__SS_restore_pageStyle = "";
|
||||
browser.__SS_restore_tab = aTab;
|
||||
browser.loadURIWithFlags("about:blank",
|
||||
Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
|
||||
|
@ -2949,7 +2961,11 @@ let SessionStoreInternal = {
|
|||
}
|
||||
|
||||
let frameList = this.getFramesToRestore(aBrowser);
|
||||
PageStyle.restore(aBrowser.docShell, frameList, aBrowser.__SS_restore_pageStyle);
|
||||
let pageStyle = RestoreData.get(aBrowser, "pageStyle") || "";
|
||||
let scrollPositions = RestoreData.get(aBrowser, "scroll") || {};
|
||||
|
||||
PageStyle.restore(aBrowser.docShell, frameList, pageStyle);
|
||||
ScrollPosition.restoreTree(aBrowser.contentWindow, scrollPositions);
|
||||
TextAndScrollData.restore(frameList);
|
||||
|
||||
let tab = aBrowser.__SS_restore_tab;
|
||||
|
@ -2958,8 +2974,8 @@ let SessionStoreInternal = {
|
|||
// done with that now.
|
||||
delete aBrowser.__SS_data;
|
||||
delete aBrowser.__SS_restore_data;
|
||||
delete aBrowser.__SS_restore_pageStyle;
|
||||
delete aBrowser.__SS_restore_tab;
|
||||
RestoreData.clear(aBrowser);
|
||||
|
||||
// Notify the tabbrowser that this document has been completely
|
||||
// restored. Do so after restoration is completely finished and
|
||||
|
@ -4127,4 +4143,32 @@ let GlobalState = {
|
|||
setFromState: function (aState) {
|
||||
this.state = (aState && aState.global) || {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Keeps track of data that needs to be restored after the tab's document
|
||||
* has been loaded. This includes scroll positions, form data, and page style.
|
||||
*/
|
||||
let RestoreData = {
|
||||
_data: new WeakMap(),
|
||||
|
||||
get: function (browser, key) {
|
||||
if (!this._data.has(browser)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._data.get(browser).get(key);
|
||||
},
|
||||
|
||||
set: function (browser, key, value) {
|
||||
if (!this._data.has(browser)) {
|
||||
this._data.set(browser, new Map());
|
||||
}
|
||||
|
||||
this._data.get(browser).set(key, value);
|
||||
},
|
||||
|
||||
clear: function (browser) {
|
||||
this._data.delete(browser);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,6 +16,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
|
|||
"resource:///modules/sessionstore/DocumentUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
|
||||
"resource:///modules/sessionstore/PrivacyLevel.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
|
||||
"resource:///modules/sessionstore/ScrollPosition.jsm");
|
||||
|
||||
/**
|
||||
* The external API exported by this module.
|
||||
|
@ -78,14 +80,6 @@ let TextAndScrollDataInternal = {
|
|||
entry.innerHTML = content.document.body.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// get scroll position from nsIDOMWindowUtils, since it allows avoiding a
|
||||
// flush of layout
|
||||
let domWindowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let scrollX = {}, scrollY = {};
|
||||
domWindowUtils.getScrollXY(false, scrollX, scrollY);
|
||||
entry.scroll = scrollX.value + "," + scrollY.value;
|
||||
},
|
||||
|
||||
isAboutSessionRestore: function (url) {
|
||||
|
@ -146,9 +140,6 @@ let TextAndScrollDataInternal = {
|
|||
}, 0);
|
||||
}
|
||||
|
||||
let match;
|
||||
if (data.scroll && (match = /(\d+),(\d+)/.exec(data.scroll)) != null) {
|
||||
content.scrollTo(match[1], match[2]);
|
||||
}
|
||||
ScrollPosition.restore(content, data.scroll || "");
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,10 +15,12 @@ JS_MODULES_PATH = 'modules/sessionstore'
|
|||
EXTRA_JS_MODULES = [
|
||||
'DocShellCapabilities.jsm',
|
||||
'DocumentUtils.jsm',
|
||||
'FrameTree.jsm',
|
||||
'Messenger.jsm',
|
||||
'PageStyle.jsm',
|
||||
'PrivacyLevel.jsm',
|
||||
'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
|
||||
'ScrollPosition.jsm',
|
||||
'SessionCookies.jsm',
|
||||
'SessionFile.jsm',
|
||||
'SessionHistory.jsm',
|
||||
|
|
Загрузка…
Ссылка в новой задаче