This commit is contained in:
Ryan VanderMeulen 2013-12-18 21:19:33 -05:00
Родитель 4b42450d1d d1c69daf69
Коммит d0e2988561
120 изменённых файлов: 3717 добавлений и 872 удалений

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

@ -1121,6 +1121,7 @@ pref("devtools.debugger.pause-on-exceptions", false);
pref("devtools.debugger.ignore-caught-exceptions", true);
pref("devtools.debugger.source-maps-enabled", true);
pref("devtools.debugger.pretty-print-enabled", true);
pref("devtools.debugger.tracer", false);
// The default Debugger UI settings
pref("devtools.debugger.ui.panes-sources-width", 200);
@ -1304,8 +1305,6 @@ pref("social.manifest.facebook", "{\"origin\":\"https://www.facebook.com\",\"nam
pref("social.sidebar.open", true);
pref("social.sidebar.unload_timeout_ms", 10000);
pref("social.allowMultipleWorkers", true);
pref("dom.identity.enabled", false);
// Turn on the CSP 1.0 parser for Content Security Policy headers

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

@ -1031,14 +1031,10 @@ let SocialStatusWidgetListener = {
SocialStatus = {
populateToolbarPalette: function() {
if (!Social.allowMultipleWorkers)
return;
this._toolbarHelper.populatePalette();
},
removeProvider: function(origin) {
if (!Social.allowMultipleWorkers)
return;
this._removeFrame(origin);
this._toolbarHelper.removeProviderButton(origin);
},
@ -1124,8 +1120,6 @@ SocialStatus = {
},
updateButton: function(origin) {
if (!Social.allowMultipleWorkers)
return;
let id = this._toolbarHelper.idFromOrigin(origin);
let widget = CustomizableUI.getWidget(id);
if (!widget)
@ -1165,8 +1159,6 @@ SocialStatus = {
},
showPopup: function(aToolbarButton) {
if (!Social.allowMultipleWorkers)
return;
// attach our notification panel if necessary
let origin = aToolbarButton.getAttribute("origin");
let provider = Social._getProviderFromOrigin(origin);

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

@ -747,18 +747,6 @@ toolbarpaletteitem[place="palette"] > toolbarbutton[type="badged"] > .toolbarbut
max-height: 32px;
}
@media (min-resolution: 2dppx) {
toolbarbutton[type="badged"] > .toolbarbutton-badge-container > .toolbarbutton-icon,
toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
max-width: 32px;
max-height: 32px;
}
toolbarpaletteitem[place="palette"] > toolbarbutton[type="badged"] > .toolbarbutton-badge-container > .toolbarbutton-icon {
max-width: 64px;
max-height: 64px;
}
}
panelview > .social-panel-frame {
width: auto;
height: auto;

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

@ -2474,8 +2474,15 @@ function _checkDefaultAndSwitchToMetro() {
getService(Components.interfaces.nsIAppStartup);
Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true);
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
Components.interfaces.nsIAppStartup.eRestartTouchEnvironment);
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
.createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
if (!cancelQuit.data) {
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
Components.interfaces.nsIAppStartup.eRestartTouchEnvironment);
}
return true;
}
return false;

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

@ -63,15 +63,12 @@ function test() {
ok(chats.children.length == 0, "no chatty children left behind");
cb();
};
// always run chat tests with multiple workers.
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
runSocialTestWithProvider(manifests, function (finishcb) {
ok(Social.enabled, "Social is enabled");
ok(Social.providers[0].getWorkerPort(), "provider 0 has port");
ok(Social.providers[1].getWorkerPort(), "provider 1 has port");
ok(Social.providers[2].getWorkerPort(), "provider 2 has port");
runSocialTests(tests, undefined, postSubTest, function() {
Services.prefs.clearUserPref("social.allowMultipleWorkers");
window.moveTo(oldleft, window.screenY)
window.resizeTo(oldwidth, window.outerHeight);
finishcb();

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

@ -55,14 +55,12 @@ function openWindowAndWaitForInit(callback) {
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
let toolbar = document.getElementById("nav-bar");
let currentsetAtStart = toolbar.currentSet;
runSocialTestWithProvider(manifest, function () {
runSocialTests(tests, undefined, undefined, function () {
Services.prefs.clearUserPref("social.remote-install.enabled");
// just in case the tests failed, clear these here as well
Services.prefs.clearUserPref("social.allowMultipleWorkers");
Services.prefs.clearUserPref("social.whitelist");
ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish");
CustomizableUI.reset();

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

@ -5,11 +5,9 @@
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
runSocialTestWithProvider(gProviders, function (finishcb) {
Social.enabled = true;
runSocialTests(tests, undefined, undefined, function() {
Services.prefs.clearUserPref("social.allowMultipleWorkers");
finishcb();
});
});
@ -58,6 +56,7 @@ var tests = {
waitForCondition(function() messageReceived == Social.providers.length,
next, "received messages from all workers");
},
testWorkerDisabling: function(next) {
Social.enabled = false;
is(Social.providers.length, gProviders.length, "providers still available");
@ -66,37 +65,5 @@ var tests = {
ok(!p.getWorkerPort(), "worker disabled");
}
next();
},
testSingleWorkerEnabling: function(next) {
// test that only one worker is enabled when we limit workers
Services.prefs.setBoolPref("social.allowMultipleWorkers", false);
Social.enabled = true;
for (let p of Social.providers) {
if (p == Social.provider) {
ok(p.enabled, "primary provider enabled");
let port = p.getWorkerPort();
ok(port, "primary worker enabled");
port.close();
} else {
ok(!p.enabled, "secondary provider is not enabled");
ok(!p.getWorkerPort(), "secondary worker disabled");
}
}
next();
},
testMultipleWorkerEnabling: function(next) {
// test that all workers are enabled when we allow multiple workers
Social.enabled = false;
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
Social.enabled = true;
for (let p of Social.providers) {
ok(p.enabled, "provider enabled");
let port = p.getWorkerPort();
ok(port, "worker enabled");
port.close();
}
next();
}
}

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

@ -40,12 +40,10 @@ function openWindowAndWaitForInit(callback) {
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, undefined, function () {
Services.prefs.clearUserPref("social.remote-install.enabled");
// just in case the tests failed, clear these here as well
Services.prefs.clearUserPref("social.allowMultipleWorkers");
Services.prefs.clearUserPref("social.whitelist");
ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish");
CustomizableUI.reset();

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

@ -13,7 +13,6 @@ let {getFrameWorkerHandle} = Cu.import("resource://gre/modules/FrameWorker.jsm",
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
// We need to ensure all our workers are in the same content process.
Services.prefs.setIntPref("dom.ipc.processCount", 1);
@ -22,7 +21,6 @@ function test() {
runSocialTests(tests, undefined, undefined, function() {
Services.prefs.clearUserPref("dom.ipc.processCount");
Services.prefs.clearUserPref("social.sidebar.open");
Services.prefs.clearUserPref("social.allowMultipleWorkers");
finishcb();
});
});

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

@ -883,7 +883,7 @@ let CustomizableUIInternal = {
}
let nextNode = null;
if (aNextNodeId) {
nextNode = aAreaNode.customizationTarget.querySelector(idToSelector(aNextNodeId));
nextNode = aAreaNode.customizationTarget.getElementsByAttribute("id", aNextNodeId)[0];
}
return [aAreaNode.customizationTarget, nextNode];
},
@ -1004,7 +1004,7 @@ let CustomizableUIInternal = {
if (toolbox.palette) {
// Attempt to locate a node with a matching ID within
// the palette.
let node = toolbox.palette.querySelector(idToSelector(aId));
let node = toolbox.palette.getElementsByAttribute("id", aId)[0];
if (node) {
// Normalize the removable attribute. For backwards compat, this
// is optional if the widget is located in the toolbox palette,
@ -1881,7 +1881,7 @@ let CustomizableUIInternal = {
windowCache.delete(aWidgetId);
}
let widgetNode = window.document.getElementById(aWidgetId) ||
window.gNavToolbox.palette.querySelector(idToSelector(aWidgetId));
window.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
if (widgetNode) {
widgetNode.remove();
}
@ -2014,7 +2014,7 @@ let CustomizableUIInternal = {
if (!container.length) {
return false;
}
let existingNode = container[0].querySelector(idToSelector(aWidgetId));
let existingNode = container[0].getElementsByAttribute("id", aWidgetId)[0];
if (existingNode) {
return true;
}
@ -2058,7 +2058,7 @@ let CustomizableUIInternal = {
// Clone the array so we don't modify the actual placements...
currentPlacements = [...currentPlacements];
currentPlacements = currentPlacements.filter((item) => {
let itemNode = container.querySelector(idToSelector(item));
let itemNode = container.getElementsByAttribute("id", item)[0];
return itemNode && removableOrDefault(itemNode || item);
});
}
@ -2972,7 +2972,7 @@ function XULWidgetGroupWrapper(aWidgetId) {
if (!instance) {
// Toolbar palettes aren't part of the document, so elements in there
// won't be found via document.getElementById().
instance = aWindow.gNavToolbox.palette.querySelector(idToSelector(aWidgetId));
instance = aWindow.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
}
let wrapper = new XULWidgetSingleWrapper(aWidgetId, instance);
@ -3039,6 +3039,12 @@ function OverflowableToolbar(aToolbarNode) {
this._enabled = true;
this._toolbar.setAttribute("overflowable", "true");
let doc = this._toolbar.ownerDocument;
this._target = this._toolbar.customizationTarget;
this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
this._list.toolbox = this._toolbar.toolbox;
this._list.customizationTarget = this._list;
Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
}
@ -3055,12 +3061,7 @@ OverflowableToolbar.prototype = {
},
init: function() {
this._target = this._toolbar.customizationTarget;
let doc = this._toolbar.ownerDocument;
this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
this._list.toolbox = this._toolbar.toolbox;
this._list.customizationTarget = this._list;
let window = doc.defaultView;
window.addEventListener("resize", this);
window.gNavToolbox.addEventListener("customizationstarting", this);
@ -3221,7 +3222,7 @@ OverflowableToolbar.prototype = {
}
let inserted = false;
for (; beforeNodeIndex < placements.length; beforeNodeIndex++) {
let beforeNode = this._target.querySelector(idToSelector(placements[beforeNodeIndex]));
let beforeNode = this._target.getElementsByAttribute("id", placements[beforeNodeIndex])[0];
if (beforeNode) {
this._target.insertBefore(child, beforeNode);
inserted = true;
@ -3354,7 +3355,7 @@ OverflowableToolbar.prototype = {
return [this._list, null];
}
let nextNode = this._list.querySelector(idToSelector(aNextNodeId));
let nextNode = this._list.getElementsByAttribute("id", aNextNodeId)[0];
// If this is the first item, we can actually just append the node
// to the end of the toolbar. If it results in an overflow event, we'll move
// the new node to the overflow target.
@ -3372,9 +3373,4 @@ OverflowableToolbar.prototype = {
},
};
// When IDs contain special characters, we need to escape them for use with querySelector:
function idToSelector(aId) {
return "#" + aId.replace(/[ !"'#$%&\(\)*+\-,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&');
}
CustomizableUIInternal.initialize();

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

@ -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
@ -308,6 +348,30 @@ let SessionStorageListener = {
Ci.nsISupportsWeakReference])
};
/**
* Listen for changes to the privacy status of the tab.
* By definition, tabs start in non-private mode.
*
* Causes a SessionStore:update message to be sent for
* field "isPrivate". This message contains
* |true| if the tab is now private
* |null| if the tab is now public - the field is therefore
* not saved.
*/
let PrivacyListener = {
init: function() {
docShell.addWeakPrivacyTransitionObserver(this);
},
// Ci.nsIPrivacyTransitionObserver
privateModeChanged: function(enabled) {
MessageQueue.push("isPrivate", () => enabled || null);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivacyTransitionObserver,
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
@ -399,6 +463,8 @@ let MessageQueue = {
// request.
let sendMessage = sync ? sendRpcMessage : sendAsyncMessage;
let durationMs = Date.now();
let data = {};
for (let [key, id] of this._lastUpdated) {
// There is no data for the given key anymore because
@ -418,8 +484,17 @@ let MessageQueue = {
data[key] = this._data.get(key)();
}
durationMs = Date.now() - durationMs;
let telemetry = {
FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS: durationMs
}
// Send all data to the parent process.
sendMessage("SessionStore:update", {id: this._id, data: data});
sendMessage("SessionStore:update", {
id: this._id,
data: data,
telemetry: telemetry
});
// Increase our unique message ID.
this._id++;
@ -465,4 +540,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);
}
});
}
});

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

@ -72,7 +72,7 @@ this.SessionSaver = Object.freeze({
* Immediately saves the current session to disk.
*/
run: function () {
SessionSaverInternal.run();
return SessionSaverInternal.run();
},
/**
@ -129,7 +129,7 @@ let SessionSaverInternal = {
* Immediately saves the current session to disk.
*/
run: function () {
this._saveState(true /* force-update all windows */);
return this._saveState(true /* force-update all windows */);
},
/**
@ -192,12 +192,24 @@ let SessionSaverInternal = {
stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
let state = SessionStore.getCurrentState(forceUpdateAllWindows);
// Forget about private windows.
// Forget about private windows and tabs.
for (let i = state.windows.length - 1; i >= 0; i--) {
if (state.windows[i].isPrivate) {
state.windows.splice(i, 1);
if (state.selectedWindow >= i) {
state.selectedWindow--;
let win = state.windows[i];
if (win.isPrivate || false) { // The whole window is private, remove it
state.windows.splice(i, 1);
if (state.selectedWindow >= i) {
state.selectedWindow--;
}
continue;
}
// The window is not private, but its tabs still might
for (let j = win.tabs.length - 1; j >= 0 ; --j) {
let tab = win.tabs[j];
if (tab.isPrivate || false) {
win.tabs.splice(j, 1);
if (win.selected >= j) {
win.selected--;
}
}
}
}
@ -209,6 +221,10 @@ let SessionSaverInternal = {
}
}
// Note that closed private tabs are never stored (see
// SessionStoreInternal.onTabClose), so we do not need to remove
// them.
// Make sure that we keep the previous session if we started with a single
// private window and no non-private windows have been opened, yet.
if (state.deferredInitialState) {
@ -235,7 +251,7 @@ let SessionSaverInternal = {
#endif
stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
this._writeState(state);
return this._writeState(state);
},
/**
@ -278,7 +294,7 @@ let SessionSaverInternal = {
// Don't touch the file if an observer has deleted all state data.
if (!data) {
return;
return Promise.resolve();
}
// We update the time stamp before writing so that we don't write again
@ -290,7 +306,7 @@ let SessionSaverInternal = {
// Write (atomically) to a session file, using a tmp file. Once the session
// file is successfully updated, save the time stamp of the last save and
// notify the observers.
SessionFile.write(data).then(() => {
return SessionFile.write(data).then(() => {
this.updateLastSaveTime();
notify(null, "sessionstore-state-write-complete");
}, Cu.reportError);

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

@ -99,6 +99,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
"@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
"@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
"@mozilla.org/base/telemetry;1", "nsITelemetry");
XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
"resource:///modules/sessionstore/DocShellCapabilities.jsm");
@ -110,6 +112,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",
@ -606,6 +610,7 @@ let SessionStoreInternal = {
TabState.setSyncHandler(browser, aMessage.objects.handler);
break;
case "SessionStore:update":
this.recordTelemetry(aMessage.data.telemetry);
TabState.update(browser, aMessage.data);
this.saveStateDelayed(win);
break;
@ -617,6 +622,18 @@ let SessionStoreInternal = {
this._clearRestoringWindows();
},
/**
* Record telemetry measurements stored in an object.
* @param telemetry
* {histogramID: value, ...} An object mapping histogramIDs to the
* value to be recorded for that ID,
*/
recordTelemetry: function (telemetry) {
for (let histogramId in telemetry){
Telemetry.getHistogramById(histogramId).add(telemetry[histogramId]);
}
},
/* ........ Window Event Handlers .............. */
/**
@ -630,14 +647,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;
@ -1311,6 +1331,11 @@ let SessionStoreInternal = {
// Get the latest data for this tab (generally, from the cache)
let tabState = TabState.collectSync(aTab);
// Don't save private tabs
if (tabState.isPrivate || false) {
return;
}
// store closed-tab data for undo
if (this._shouldSaveTabState(tabState)) {
let tabTitle = aTab.label;
@ -2649,6 +2674,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
@ -2816,8 +2842,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
@ -2832,7 +2865,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,
@ -2944,7 +2976,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;
@ -2953,8 +2989,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
@ -4122,4 +4158,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',

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

@ -11,11 +11,15 @@
support-files =
head.js
content.js
browser_frametree_sample.html
browser_frametree_sample_frameset.html
browser_form_restore_events_sample.html
browser_formdata_format_sample.html
browser_input_sample.html
browser_pageStyle_sample.html
browser_pageStyle_sample_nested.html
browser_scrollPositions_sample.html
browser_scrollPositions_sample_frameset.html
browser_248970_b_sample.html
browser_339445_sample.html
browser_346337_sample.html
@ -54,11 +58,14 @@ support-files =
[browser_dying_cache.js]
[browser_form_restore_events.js]
[browser_formdata_format.js]
[browser_frametree.js]
[browser_global_store.js]
[browser_input.js]
[browser_merge_closed_tabs.js]
[browser_pageshow.js]
[browser_pageStyle.js]
[browser_privatetabs.js]
[browser_scrollPositions.js]
[browser_sessionStorage.js]
[browser_swapDocShells.js]
[browser_tabStateCache.js]
@ -95,7 +102,6 @@ skip-if = true
[browser_477657.js]
[browser_480148.js]
[browser_480893.js]
[browser_483330.js]
[browser_485482.js]
[browser_485563.js]
[browser_490040.js]

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

@ -34,9 +34,7 @@ function test() {
EventUtils.sendMouseEvent({type: "click"}, chk);
let browser = newWin.gBrowser.selectedBrowser;
promiseContentMessage(browser, "SessionStore:input").then(result => {
ok(result, "received message for input changes");
promiseContentMessage(browser, "SessionStore:input").then(() => {
newWin.close();
// Now give it time to close

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

@ -1,40 +0,0 @@
function test() {
/** Test for Bug 483330 **/
waitForExplicitFinish();
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
browser.addEventListener("load", function loadListener(e) {
browser.removeEventListener("load", arguments.callee, true);
// Scroll the content document
browser.contentWindow.scrollTo(1100, 1200);
is(browser.contentWindow.scrollX, 1100, "scrolled horizontally");
is(browser.contentWindow.scrollY, 1200, "scrolled vertically");
gBrowser.removeTab(tab);
let newTab = ss.undoCloseTab(window, 0);
newTab.addEventListener("SSTabRestored", function tabRestored(e) {
newTab.removeEventListener("SSTabRestored", arguments.callee, true);
let newBrowser = newTab.linkedBrowser;
// check that the scroll position was restored
is(newBrowser.contentWindow.scrollX, 1100, "still scrolled horizontally");
is(newBrowser.contentWindow.scrollY, 1200, "still scrolled vertically");
gBrowser.removeTab(newTab);
// Call stopPropagation on the event so we won't fire the
// tabbrowser's SSTabRestored listeners.
e.stopPropagation();
finish();
}, true);
}, true);
browser.loadURI("data:text/html;charset=utf-8,<body style='width: 100000px; height: 100000px;'><p>top</p></body>");
}

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

@ -56,8 +56,7 @@ function test() {
// Start a load and interrupt it by closing the tab
tab.linkedBrowser.loadURI(URI_TO_LOAD);
let loaded = yield waitForLoadStarted(tab);
ok(loaded, "Load started");
yield waitForLoadStarted(tab);
let tabClosing = waitForTabClosed();
gBrowser.removeTab(tab);

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

@ -0,0 +1,130 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const ROOT = getRootDirectory(gTestPath);
const URL = ROOT + "browser_frametree_sample.html";
const URL_FRAMESET = ROOT + "browser_frametree_sample_frameset.html";
/**
* This ensures that loading a page normally, aborting a page load, reloading
* a page, navigating using the bfcache, and ignoring frames that were
* created dynamically work as expect. We expect the frame tree to be reset
* when a page starts loading and we also expect a valid frame tree to exist
* when it has stopped loading.
*/
add_task(function test_frametree() {
const FRAME_TREE_SINGLE = { href: URL };
const FRAME_TREE_FRAMESET = {
href: URL_FRAMESET,
children: [{href: URL}, {href: URL}, {href: URL}]
};
// Create a tab with a single frame.
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseNewFrameTree(browser);
yield checkFrameTree(browser, FRAME_TREE_SINGLE,
"loading a page resets and creates the frame tree correctly");
// Load the frameset and create two frames dynamically, the first on
// DOMContentLoaded and the second on load.
yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
browser.loadURI(URL_FRAMESET);
yield promiseNewFrameTree(browser);
yield checkFrameTree(browser, FRAME_TREE_FRAMESET,
"dynamic frames created on or after the load event are ignored");
// Go back to the previous single-frame page. There will be no load event as
// the page is still in the bfcache. We thus make sure this type of navigation
// resets the frame tree.
browser.goBack();
yield promiseNewFrameTree(browser);
yield checkFrameTree(browser, FRAME_TREE_SINGLE,
"loading from bfache resets and creates the frame tree correctly");
// Load the frameset again but abort the load early.
// The frame tree should still be reset and created.
browser.loadURI(URL_FRAMESET);
executeSoon(() => browser.stop());
yield promiseNewFrameTree(browser);
// Load the frameset and check the tree again.
yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
browser.loadURI(URL_FRAMESET);
yield promiseNewFrameTree(browser);
yield checkFrameTree(browser, FRAME_TREE_FRAMESET,
"reloading a page resets and creates the frame tree correctly");
// Cleanup.
gBrowser.removeTab(tab);
});
/**
* This test ensures that we ignore frames that were created dynamically at or
* after the load event. SessionStore can't handle these and will not restore
* or collect any data for them.
*/
add_task(function test_frametree_dynamic() {
// The frame tree as expected. The first two frames are static
// and the third one was created on DOMContentLoaded.
const FRAME_TREE = {
href: URL_FRAMESET,
children: [{href: URL}, {href: URL}, {href: URL}]
};
const FRAME_TREE_REMOVED = {
href: URL_FRAMESET,
children: [{href: URL}, {href: URL}]
};
// Add an empty tab for a start.
let tab = gBrowser.addTab("about:blank");
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Create dynamic frames on "DOMContentLoaded" and on "load".
yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
browser.loadURI(URL_FRAMESET);
yield promiseNewFrameTree(browser);
// Check that the frame tree does not contain the frame created on "load".
// The two static frames and the one created on DOMContentLoaded must be in
// the tree.
yield checkFrameTree(browser, FRAME_TREE,
"frame tree contains first four frames");
// Remove the last frame in the frameset.
yield sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"});
// Check that the frame tree didn't change.
yield checkFrameTree(browser, FRAME_TREE,
"frame tree contains first four frames");
// Remove the last frame in the frameset.
yield sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"});
// Check that the frame tree excludes the removed frame.
yield checkFrameTree(browser, FRAME_TREE_REMOVED,
"frame tree contains first three frames");
// Cleanup.
gBrowser.removeTab(tab);
});
/**
* Checks whether the current frame hierarchy of a given |browser| matches the
* |expected| frame hierarchy.
*/
function checkFrameTree(browser, expected, msg) {
return sendMessage(browser, "ss-test:mapFrameTree").then(tree => {
is(JSON.stringify(tree), JSON.stringify(expected), msg);
});
}
/**
* Returns a promise that will be resolved when the given |browser| has loaded
* and we received messages saying that its frame tree has been reset and
* recollected.
*/
function promiseNewFrameTree(browser) {
let reset = promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
let collect = promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
return Promise.all([reset, collect]);
}

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

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>browser_frametree_sample.html</title>
</head>
<body style='width: 100000px; height: 100000px;'>top</body>
</html>

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

@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<html lang="en">
<head>
<meta charset="utf-8">
<title>browser_frametree_sample_frameset.html</title>
</head>
<frameset id="frames" rows="50%, 50%">
<frame src="browser_frametree_sample.html">
<frame src="browser_frametree_sample.html">
</frameset>
</html>

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

@ -65,7 +65,7 @@ add_task(function nested_page_style() {
});
function getStyleSheets(browser) {
return sendMessage(browser, "ss-test:getStyleSheets").then(({data}) => data);
return sendMessage(browser, "ss-test:getStyleSheets");
}
function enableStyleSheetsForSet(browser, name) {
@ -79,8 +79,7 @@ function enableSubDocumentStyleSheetsForSet(browser, name) {
}
function getAuthorStyleDisabled(browser) {
return sendMessage(browser, "ss-test:getAuthorStyleDisabled")
.then(({data}) => data);
return sendMessage(browser, "ss-test:getAuthorStyleDisabled");
}
function setAuthorStyleDisabled(browser, val) {

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

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let Imports = {};
Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", Imports);
let {SessionSaver} = Imports;
add_task(function cleanup() {
info("Forgetting closed tabs");
while (ss.getClosedTabCount(window)) {
ss.forgetClosedTab(window, 0);
}
});
add_task(function() {
let URL_PUBLIC = "http://example.com/public/" + Math.random();
let URL_PRIVATE = "http://example.com/private/" + Math.random();
let tab1, tab2;
try {
// Setup a public tab and a private tab
info("Setting up public tab");
tab1 = gBrowser.addTab(URL_PUBLIC);
yield promiseBrowserLoaded(tab1.linkedBrowser);
info("Setting up private tab");
tab2 = gBrowser.addTab();
yield promiseBrowserLoaded(tab2.linkedBrowser);
yield setUsePrivateBrowsing(tab2.linkedBrowser, true);
tab2.linkedBrowser.loadURI(URL_PRIVATE);
yield promiseBrowserLoaded(tab2.linkedBrowser);
info("Flush to make sure chrome received all data.");
SyncHandlers.get(tab2.linkedBrowser).flush();
info("Checking out state");
yield SessionSaver.run();
let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
let data = yield OS.File.read(path);
let state = new TextDecoder().decode(data);
info("State: " + state);
// Ensure that sessionstore.js only knows about the public tab
ok(state.indexOf(URL_PUBLIC) != -1, "State contains public tab");
ok(state.indexOf(URL_PRIVATE) == -1, "State does not contain private tab");
// Ensure that we can close and undo close the public tab but not the private tab
gBrowser.removeTab(tab2);
tab2 = null;
gBrowser.removeTab(tab1);
tab1 = null;
tab1 = ss.undoCloseTab(window, 0);
ok(true, "Public tab supports undo close");
is(ss.getClosedTabCount(window), 0, "Private tab does not support undo close");
} finally {
if (tab1) {
gBrowser.removeTab(tab1);
}
if (tab2) {
gBrowser.removeTab(tab2);
}
}
});
function setUsePrivateBrowsing(browser, val) {
return sendMessage(browser, "ss-test:setUsePrivateBrowsing", val);
}

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

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ROOT = getRootDirectory(gTestPath);
const URL = ROOT + "browser_scrollPositions_sample.html";
const URL_FRAMESET = ROOT + "browser_scrollPositions_sample_frameset.html";
// Randomized set of scroll positions we will use in this test.
const SCROLL_X = Math.round(100 * (1 + Math.random()));
const SCROLL_Y = Math.round(200 * (1 + Math.random()));
const SCROLL_STR = SCROLL_X + "," + SCROLL_Y;
const SCROLL2_X = Math.round(300 * (1 + Math.random()));
const SCROLL2_Y = Math.round(400 * (1 + Math.random()));
const SCROLL2_STR = SCROLL2_X + "," + SCROLL2_Y;
/**
* This test ensures that we properly serialize and restore scroll positions
* for an average page without any frames.
*/
add_task(function test_scroll() {
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Scroll down a little.
yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL_X, y: SCROLL_Y});
checkScroll(tab, {scroll: SCROLL_STR}, "scroll is fine");
// Duplicate and check that the scroll position is restored.
let tab2 = ss.duplicateTab(window, tab);
let browser2 = tab2.linkedBrowser;
yield promiseTabRestored(tab2);
let scroll = yield sendMessage(browser2, "ss-test:getScrollPosition");
is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}),
"scroll position has been duplicated correctly");
// Check that reloading retains the scroll positions.
browser2.reload();
yield promiseBrowserLoaded(browser2);
checkScroll(tab2, {scroll: SCROLL_STR}, "reloading retains scroll positions");
// Check that a force-reload resets scroll positions.
browser2.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
yield promiseBrowserLoaded(browser2);
checkScroll(tab2, null, "force-reload resets scroll positions");
// Scroll back to the top and check that the position has been reset. We
// expect the scroll position to be "null" here because there is no data to
// be stored if the frame is in its default scroll position.
yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0});
checkScroll(tab, null, "no scroll stored");
// Cleanup.
gBrowser.removeTab(tab);
gBrowser.removeTab(tab2);
});
/**
* This tests ensures that we properly serialize and restore scroll positions
* for multiple frames of pages with framesets.
*/
add_task(function test_scroll_nested() {
let tab = gBrowser.addTab(URL_FRAMESET);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Scroll the first child frame down a little.
yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL_X, y: SCROLL_Y, frame: 0});
checkScroll(tab, {children: [{scroll: SCROLL_STR}]}, "scroll is fine");
// Scroll the second child frame down a little.
yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL2_X, y: SCROLL2_Y, frame: 1});
checkScroll(tab, {children: [{scroll: SCROLL_STR}, {scroll: SCROLL2_STR}]}, "scroll is fine");
// Duplicate and check that the scroll position is restored.
let tab2 = ss.duplicateTab(window, tab);
let browser2 = tab2.linkedBrowser;
yield promiseTabRestored(tab2);
let scroll = yield sendMessage(browser2, "ss-test:getScrollPosition", {frame: 0});
is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}),
"scroll position #1 has been duplicated correctly");
scroll = yield sendMessage(browser2, "ss-test:getScrollPosition", {frame: 1});
is(JSON.stringify(scroll), JSON.stringify({x: SCROLL2_X, y: SCROLL2_Y}),
"scroll position #2 has been duplicated correctly");
// Check that resetting one frame's scroll position removes it from the
// serialized value.
yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0, frame: 0});
checkScroll(tab, {children: [null, {scroll: SCROLL2_STR}]}, "scroll is fine");
// Check the resetting all frames' scroll positions nulls the stored value.
yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0, frame: 1});
checkScroll(tab, null, "no scroll stored");
// Cleanup.
gBrowser.removeTab(tab);
gBrowser.removeTab(tab2);
});
/**
* This test ensures that by moving scroll positions out of tabData.entries[]
* we still support the old scroll data format stored per shistory entry.
*/
add_task(function test_scroll_old_format() {
const TAB_STATE = { entries: [{url: URL, scroll: SCROLL_STR}] };
// Add a blank tab.
let tab = gBrowser.addTab("about:blank");
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Apply the tab state with the old format.
ss.setTabState(tab, JSON.stringify(TAB_STATE));
yield promiseTabRestored(tab);
// Check that the scroll positions has been applied.
let scroll = yield sendMessage(browser, "ss-test:getScrollPosition");
is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}),
"scroll position has been restored correctly");
// Cleanup.
gBrowser.removeTab(tab);
});
function checkScroll(tab, expected, msg) {
let browser = tab.linkedBrowser;
SyncHandlers.get(browser).flush();
let scroll = JSON.parse(ss.getTabState(tab)).scroll || null;
is(JSON.stringify(scroll), JSON.stringify(expected), msg);
}

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

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>browser_scrollPositions_sample.html</title>
</head>
<body style='width: 100000px; height: 100000px;'>top</body>
</html>

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

@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<html lang="en">
<head>
<meta charset="utf-8">
<title>browser_scrollPositions_sample_frameset.html</title>
</head>
<frameset id="frames" rows="50%, 50%">
<frame src="browser_scrollPositions_sample.html">
<frame src="browser_scrollPositions_sample.html">
</frameset>
</html>

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

@ -2,8 +2,22 @@
* 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/. */
let Cu = Components.utils;
let Ci = Components.interfaces;
Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
let gFrameTree = new FrameTree(this);
gFrameTree.addObserver({
onFrameTreeReset: function () {
sendAsyncMessage("ss-test:onFrameTreeReset");
},
onFrameTreeCollected: function () {
sendAsyncMessage("ss-test:onFrameTreeCollected");
}
});
/**
* This frame script is only loaded for sessionstore mochitests. It enables us
* to modify and query docShell data when running with multiple processes.
@ -52,3 +66,77 @@ addMessageListener("ss-test:setAuthorStyleDisabled", function (msg) {
markupDocumentViewer.authorStyleDisabled = msg.data;
sendSyncMessage("ss-test:setAuthorStyleDisabled");
});
addMessageListener("ss-test:setUsePrivateBrowsing", function (msg) {
let loadContext =
docShell.QueryInterface(Ci.nsILoadContext);
loadContext.usePrivateBrowsing = msg.data;
sendAsyncMessage("ss-test:setUsePrivateBrowsing");
});
addMessageListener("ss-test:getScrollPosition", function (msg) {
let frame = content;
if (msg.data.hasOwnProperty("frame")) {
frame = content.frames[msg.data.frame];
}
let {scrollX: x, scrollY: y} = frame;
sendAsyncMessage("ss-test:getScrollPosition", {x: x, y: y});
});
addMessageListener("ss-test:setScrollPosition", function (msg) {
let frame = content;
let {x, y} = msg.data;
if (msg.data.hasOwnProperty("frame")) {
frame = content.frames[msg.data.frame];
}
frame.scrollTo(x, y);
frame.addEventListener("scroll", function onScroll(event) {
if (frame.document == event.target) {
frame.removeEventListener("scroll", onScroll);
sendAsyncMessage("ss-test:setScrollPosition");
}
});
});
addMessageListener("ss-test:createDynamicFrames", function ({data}) {
function createIFrame(rows) {
let frames = content.document.getElementById(data.id);
frames.setAttribute("rows", rows);
let frame = content.document.createElement("frame");
frame.setAttribute("src", data.url);
frames.appendChild(frame);
}
addEventListener("DOMContentLoaded", function onContentLoaded(event) {
if (content.document == event.target) {
removeEventListener("DOMContentLoaded", onContentLoaded, true);
// DOMContentLoaded is fired right after we finished parsing the document.
createIFrame("33%, 33%, 33%");
}
}, true);
addEventListener("load", function onLoad(event) {
if (content.document == event.target) {
removeEventListener("load", onLoad, true);
// Creating this frame on the same tick as the load event
// means that it must not be included in the frame tree.
createIFrame("25%, 25%, 25%, 25%");
}
}, true);
sendAsyncMessage("ss-test:createDynamicFrames");
});
addMessageListener("ss-test:removeLastFrame", function ({data}) {
let frames = content.document.getElementById(data.id);
frames.lastElementChild.remove();
sendAsyncMessage("ss-test:removeLastFrame");
});
addMessageListener("ss-test:mapFrameTree", function (msg) {
let result = gFrameTree.map(frame => ({href: frame.location.href}));
sendAsyncMessage("ss-test:mapFrameTree", result);
});

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

@ -193,7 +193,7 @@ function promiseContentMessage(browser, name) {
function listener(msg) {
removeListener();
deferred.resolve(msg);
deferred.resolve(msg.data);
}
mm.addMessageListener(name, listener);
@ -280,9 +280,11 @@ function forceSaveState() {
}
function whenBrowserLoaded(aBrowser, aCallback = next) {
aBrowser.addEventListener("load", function onLoad() {
aBrowser.removeEventListener("load", onLoad, true);
executeSoon(aCallback);
aBrowser.addEventListener("load", function onLoad(event) {
if (event.target == aBrowser.contentDocument) {
aBrowser.removeEventListener("load", onLoad, true);
executeSoon(aCallback);
}
}, true);
}
function promiseBrowserLoaded(aBrowser) {

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

@ -99,6 +99,7 @@ const promise = require("sdk/core/promise");
const Editor = require("devtools/sourceeditor/editor");
const DebuggerEditor = require("devtools/sourceeditor/debugger.js");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
const FastListWidget = require("devtools/shared/widgets/FastListWidget");
XPCOMUtils.defineLazyModuleGetter(this, "Parser",
"resource:///modules/devtools/Parser.jsm");
@ -192,6 +193,7 @@ let DebuggerController = {
this.SourceScripts.disconnect();
this.StackFrames.disconnect();
this.ThreadState.disconnect();
this.Tracer.disconnect();
this.disconnect();
// Chrome debugging needs to close its parent process on shutdown.
@ -218,39 +220,44 @@ let DebuggerController = {
return this._connection;
}
let deferred = promise.defer();
this._connection = deferred.promise;
let startedDebugging = promise.defer();
this._connection = startedDebugging.promise;
if (!window._isChromeDebugger) {
let target = this._target;
let { client, form: { chromeDebugger }, threadActor } = target;
let { client, form: { chromeDebugger, traceActor }, threadActor } = target;
target.on("close", this._onTabDetached);
target.on("navigate", this._onTabNavigated);
target.on("will-navigate", this._onTabNavigated);
this.client = client;
if (target.chrome) {
this._startChromeDebugging(client, chromeDebugger, deferred.resolve);
this._startChromeDebugging(chromeDebugger, startedDebugging.resolve);
} else {
this._startDebuggingTab(client, threadActor, deferred.resolve);
this._startDebuggingTab(threadActor, startedDebugging.resolve);
const startedTracing = promise.defer();
this._startTracingTab(traceActor, startedTracing.resolve);
return promise.all([startedDebugging.promise, startedTracing.promise]);
}
return deferred.promise;
return startedDebugging.promise;
}
// Chrome debugging needs to make its own connection to the debuggee.
let transport = debuggerSocketConnect(
Prefs.chromeDebuggingHost, Prefs.chromeDebuggingPort);
let client = new DebuggerClient(transport);
let client = this.client = new DebuggerClient(transport);
client.addListener("tabNavigated", this._onTabNavigated);
client.addListener("tabDetached", this._onTabDetached);
client.connect(() => {
client.listTabs(aResponse => {
this._startChromeDebugging(client, aResponse.chromeDebugger, deferred.resolve);
this._startChromeDebugging(aResponse.chromeDebugger, startedDebugging.resolve);
});
});
return deferred.promise;
return startedDebugging.promise;
},
/**
@ -331,21 +338,13 @@ let DebuggerController = {
/**
* Sets up a debugging session.
*
* @param DebuggerClient aClient
* The debugger client.
* @param string aThreadActor
* The remote protocol grip of the tab.
* @param function aCallback
* A function to invoke once the client attached to the active thread.
* A function to invoke once the client attaches to the active thread.
*/
_startDebuggingTab: function(aClient, aThreadActor, aCallback) {
if (!aClient) {
Cu.reportError("No client found!");
return;
}
this.client = aClient;
aClient.attachThread(aThreadActor, (aResponse, aThreadClient) => {
_startDebuggingTab: function(aThreadActor, aCallback) {
this.client.attachThread(aThreadActor, (aResponse, aThreadClient) => {
if (!aThreadClient) {
Cu.reportError("Couldn't attach to thread: " + aResponse.error);
return;
@ -366,21 +365,13 @@ let DebuggerController = {
/**
* Sets up a chrome debugging session.
*
* @param DebuggerClient aClient
* The debugger client.
* @param object aChromeDebugger
* The remote protocol grip of the chrome debugger.
* @param function aCallback
* A function to invoke once the client attached to the active thread.
* A function to invoke once the client attaches to the active thread.
*/
_startChromeDebugging: function(aClient, aChromeDebugger, aCallback) {
if (!aClient) {
Cu.reportError("No client found!");
return;
}
this.client = aClient;
aClient.attachThread(aChromeDebugger, (aResponse, aThreadClient) => {
_startChromeDebugging: function(aChromeDebugger, aCallback) {
this.client.attachThread(aChromeDebugger, (aResponse, aThreadClient) => {
if (!aThreadClient) {
Cu.reportError("Couldn't attach to thread: " + aResponse.error);
return;
@ -398,6 +389,30 @@ let DebuggerController = {
}, { useSourceMaps: Prefs.sourceMapsEnabled });
},
/**
* Sets up an execution tracing session.
*
* @param object aTraceActor
* The remote protocol grip of the trace actor.
* @param function aCallback
* A function to invoke once the client attaches to the tracer.
*/
_startTracingTab: function(aTraceActor, aCallback) {
this.client.attachTracer(aTraceActor, (response, traceClient) => {
if (!traceClient) {
DevToolsUtils.reportError(new Error("Failed to attach to tracing actor."));
return;
}
this.traceClient = traceClient;
this.Tracer.connect();
if (aCallback) {
aCallback();
}
});
},
/**
* Detach and reattach to the thread actor with useSourceMaps true, blow
* away old sources and get them again.
@ -1411,6 +1426,218 @@ SourceScripts.prototype = {
}
};
/**
* Tracer update the UI according to the messages exchanged with the tracer
* actor.
*/
function Tracer() {
this._trace = null;
this._idCounter = 0;
this.onTraces = this.onTraces.bind(this);
}
Tracer.prototype = {
get client() {
return DebuggerController.client;
},
get traceClient() {
return DebuggerController.traceClient;
},
get tracing() {
return !!this._trace;
},
/**
* Hooks up the debugger controller with the tracer client.
*/
connect: function() {
this._stack = [];
this.client.addListener("traces", this.onTraces);
},
/**
* Disconnects the debugger controller from the tracer client. Any further
* communcation with the tracer actor will not have any effect on the UI.
*/
disconnect: function() {
this._stack = null;
this.client.removeListener("traces", this.onTraces);
},
/**
* Instructs the tracer actor to start tracing.
*/
startTracing: function(aCallback = () => {}) {
DebuggerView.Tracer.selectTab();
if (this.tracing) {
return;
}
this._trace = "dbg.trace" + Math.random();
this.traceClient.startTrace([
"name",
"location",
"parameterNames",
"depth",
"arguments",
"return",
"throw",
"yield"
], this._trace, (aResponse) => {
const { error } = aResponse;
if (error) {
DevToolsUtils.reportException(error);
this._trace = null;
}
aCallback(aResponse);
});
},
/**
* Instructs the tracer actor to stop tracing.
*/
stopTracing: function(aCallback = () => {}) {
if (!this.tracing) {
return;
}
this.traceClient.stopTrace(this._trace, aResponse => {
const { error } = aResponse;
if (error) {
DevToolsUtils.reportException(error);
}
this._trace = null;
aCallback(aResponse);
});
},
onTraces: function (aEvent, { traces }) {
const tracesLength = traces.length;
let tracesToShow;
if (tracesLength > TracerView.MAX_TRACES) {
tracesToShow = traces.slice(tracesLength - TracerView.MAX_TRACES,
tracesLength);
DebuggerView.Tracer.empty();
this._stack.splice(0, this._stack.length);
} else {
tracesToShow = traces;
}
for (let t of tracesToShow) {
if (t.type == "enteredFrame") {
this._onCall(t);
} else {
this._onReturn(t);
}
}
DebuggerView.Tracer.commit();
},
/**
* Callback for handling a new call frame.
*/
_onCall: function({ name, location, parameterNames, depth, arguments: args }) {
const item = {
name: name,
location: location,
id: this._idCounter++
};
this._stack.push(item);
DebuggerView.Tracer.addTrace({
type: "call",
name: name,
location: location,
depth: depth,
parameterNames: parameterNames,
arguments: args,
frameId: item.id
});
},
/**
* Callback for handling an exited frame.
*/
_onReturn: function(aPacket) {
if (!this._stack.length) {
return;
}
const { name, id, location } = this._stack.pop();
DebuggerView.Tracer.addTrace({
type: aPacket.why,
name: name,
location: location,
depth: aPacket.depth,
frameId: id,
returnVal: aPacket.return || aPacket.throw || aPacket.yield
});
},
/**
* Create an object which has the same interface as a normal object client,
* but since we already have all the information for an object that we will
* ever get (the server doesn't create actors when tracing, just firehoses
* data and forgets about it) just return the data immdiately.
*
* @param Object aObject
* The tracer object "grip" (more like a limited snapshot).
* @returns Object
* The synchronous client object.
*/
syncGripClient: function(aObject) {
return {
get isFrozen() { return aObject.frozen; },
get isSealed() { return aObject.sealed; },
get isExtensible() { return aObject.extensible; },
get ownProperties() { return aObject.ownProperties; },
get prototype() { return null; },
getParameterNames: callback => callback(aObject),
getPrototypeAndProperties: callback => callback(aObject),
getPrototype: callback => callback(aObject),
getOwnPropertyNames: (callback) => {
callback({
ownPropertyNames: aObject.ownProperties
? Object.keys(aObject.ownProperties)
: []
});
},
getProperty: (property, callback) => {
callback({
descriptor: aObject.ownProperties
? aObject.ownProperties[property]
: null
});
},
getDisplayString: callback => callback("[object " + aObject.class + "]"),
getScope: callback => callback({
error: "scopeNotAvailable",
message: "Cannot get scopes for traced objects"
})
};
},
/**
* Wraps object snapshots received from the tracer server so that we can
* differentiate them from long living object grips from the debugger server
* in the variables view.
*
* @param Object aObject
* The object snapshot from the tracer actor.
*/
WrappedObject: function(aObject) {
this.object = aObject;
}
};
/**
* Handles breaking on event listeners in the currently debugged target.
*/
@ -1955,6 +2182,7 @@ let Prefs = new ViewHelpers.Prefs("devtools", {
ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"],
sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"],
prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"],
tracerEnabled: ["Bool", "debugger.tracer"],
editorTabSize: ["Int", "editor.tabsize"]
});
@ -1982,6 +2210,7 @@ DebuggerController.StackFrames = new StackFrames();
DebuggerController.SourceScripts = new SourceScripts();
DebuggerController.Breakpoints = new Breakpoints();
DebuggerController.Breakpoints.DOM = new EventListeners();
DebuggerController.Tracer = new Tracer();
/**
* Export some properties to the global scope for easier access.

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

@ -1056,6 +1056,376 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
_conditionalPopupVisible: false
});
/**
* Functions handling the traces UI.
*/
function TracerView() {
this._selectedItem = null;
this._matchingItems = null;
this.widget = null;
this._highlightItem = this._highlightItem.bind(this);
this._isNotSelectedItem = this._isNotSelectedItem.bind(this);
this._unhighlightMatchingItems =
DevToolsUtils.makeInfallible(this._unhighlightMatchingItems.bind(this));
this._onToggleTracing =
DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this));
this._onStartTracing =
DevToolsUtils.makeInfallible(this._onStartTracing.bind(this));
this._onClear = DevToolsUtils.makeInfallible(this._onClear.bind(this));
this._onSelect = DevToolsUtils.makeInfallible(this._onSelect.bind(this));
this._onMouseOver =
DevToolsUtils.makeInfallible(this._onMouseOver.bind(this));
this._onSearch = DevToolsUtils.makeInfallible(this._onSearch.bind(this));
}
TracerView.MAX_TRACES = 200;
TracerView.prototype = Heritage.extend(WidgetMethods, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function() {
dumpn("Initializing the TracerView");
this._traceButton = document.getElementById("trace");
this._tracerTab = document.getElementById("tracer-tab");
// Remove tracer related elements from the dom and tear everything down if
// the tracer isn't enabled.
if (!Prefs.tracerEnabled) {
this._traceButton.remove();
this._traceButton = null;
this._tracerTab.remove();
this._tracerTab = null;
document.getElementById("tracer-tabpanel").remove();
this.widget = null;
return;
}
this.widget = new FastListWidget(document.getElementById("tracer-traces"));
this._traceButton.removeAttribute("hidden");
this._tracerTab.removeAttribute("hidden");
this._tracerDeck = document.getElementById("tracer-deck");
this._search = document.getElementById("tracer-search");
this._template = document.getElementsByClassName("trace-item-template")[0];
this._templateItem = this._template.getElementsByClassName("trace-item")[0];
this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0];
this._templateNameNode = this._template.getElementsByClassName("trace-name")[0];
this.widget.addEventListener("select", this._onSelect, false);
this.widget.addEventListener("mouseover", this._onMouseOver, false);
this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false);
this._search.addEventListener("input", this._onSearch, false);
this._startTooltip = L10N.getStr("startTracingTooltip");
this._stopTooltip = L10N.getStr("stopTracingTooltip");
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function() {
dumpn("Destroying the TracerView");
if (!this.widget) {
return;
}
this.widget.removeEventListener("select", this._onSelect, false);
this.widget.removeEventListener("mouseover", this._onMouseOver, false);
this.widget.removeEventListener("mouseout", this._unhighlightMatchingItems, false);
this._search.removeEventListener("input", this._onSearch, false);
},
/**
* Function invoked by the "toggleTracing" command to switch the tracer state.
*/
_onToggleTracing: function() {
if (DebuggerController.Tracer.tracing) {
this._onStopTracing();
} else {
this._onStartTracing();
}
},
/**
* Function invoked either by the "startTracing" command or by
* _onToggleTracing to start execution tracing in the backend.
*/
_onStartTracing: function() {
this._tracerDeck.selectedIndex = 0;
this._traceButton.setAttribute("checked", true);
this._traceButton.setAttribute("tooltiptext", this._stopTooltip);
this.empty();
DebuggerController.Tracer.startTracing();
},
/**
* Function invoked by _onToggleTracing to stop execution tracing in the
* backend.
*/
_onStopTracing: function() {
this._traceButton.removeAttribute("checked");
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
DebuggerController.Tracer.stopTracing();
},
/**
* Function invoked by the "clearTraces" command to empty the traces pane.
*/
_onClear: function() {
this.empty();
},
/**
* Populate the given parent scope with the variable with the provided name
* and value.
*
* @param String aName
* The name of the variable.
* @param Object aParent
* The parent scope.
* @param Object aValue
* The value of the variable.
*/
_populateVariable: function(aName, aParent, aValue) {
let item = aParent.addItem(aName, { value: aValue });
if (aValue) {
DebuggerView.Variables.controller.populate(
item, new DebuggerController.Tracer.WrappedObject(aValue));
item.expand();
item.twisty = false;
}
},
/**
* Handler for the widget's "select" event. Displays parameters, exception, or
* return value depending on whether the selected trace is a call, throw, or
* return respectively.
*
* @param Object traceItem
* The selected trace item.
*/
_onSelect: function _onSelect({ detail: traceItem }) {
if (!traceItem) {
return;
}
const data = traceItem.attachment.trace;
const { location: { url, line } } = data;
DebuggerView.setEditorLocation(url, line, { noDebug: true });
DebuggerView.Variables.empty();
const scope = DebuggerView.Variables.addScope();
if (data.type == "call") {
const params = DevToolsUtils.zip(data.parameterNames, data.arguments);
for (let [name, val] of params) {
if (val === undefined) {
scope.addItem(name, { value: "<value not available>" });
} else {
this._populateVariable(name, scope, val);
}
}
} else {
const varName = "<" +
(data.type == "throw" ? "exception" : data.type) +
">";
this._populateVariable(varName, scope, data.returnVal);
}
scope.expand();
DebuggerView.showInstrumentsPane();
},
/**
* Add the hover frame enter/exit highlighting to a given item.
*/
_highlightItem: function(aItem) {
aItem.target.querySelector(".trace-item")
.classList.add("selected-matching");
},
/**
* Remove the hover frame enter/exit highlighting to a given item.
*/
_unhighlightItem: function(aItem) {
if (!aItem || !aItem.target) {
return;
}
const match = aItem.target.querySelector(".selected-matching");
if (match) {
match.classList.remove("selected-matching");
}
},
/**
* Remove the frame enter/exit pair highlighting we do when hovering.
*/
_unhighlightMatchingItems: function() {
if (this._matchingItems) {
this._matchingItems.forEach(this._unhighlightItem);
this._matchingItems = null;
}
},
/**
* Returns true if the given item is not the selected item.
*/
_isNotSelectedItem: function(aItem) {
return aItem !== this.selectedItem;
},
/**
* Highlight the frame enter/exit pair of items for the given item.
*/
_highlightMatchingItems: function(aItem) {
this._unhighlightMatchingItems();
this._matchingItems = this.items.filter(t => t.value == aItem.value);
this._matchingItems
.filter(this._isNotSelectedItem)
.forEach(this._highlightItem);
},
/**
* Listener for the mouseover event.
*/
_onMouseOver: function({ target }) {
const traceItem = this.getItemForElement(target);
if (traceItem) {
this._highlightMatchingItems(traceItem);
}
},
/**
* Listener for typing in the search box.
*/
_onSearch: function() {
const query = this._search.value.trim().toLowerCase();
this.filterContents(item =>
item.attachment.trace.name.toLowerCase().contains(query));
},
/**
* Select the traces tab in the sidebar.
*/
selectTab: function() {
const tabs = this._tracerTab.parentElement;
tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab);
this._tracerDeck.selectedIndex = 0;
},
/**
* Commit all staged items to the widget. Overridden so that we can call
* |FastListWidget.prototype.flush|.
*/
commit: function() {
WidgetMethods.commit.call(this);
// TODO: Accessing non-standard widget properties. Figure out what's the
// best way to expose such things. Bug 895514.
this.widget.flush();
},
/**
* Adds the trace record provided as an argument to the view.
*
* @param object aTrace
* The trace record coming from the tracer actor.
*/
addTrace: function(aTrace) {
const { type, frameId } = aTrace;
// Create the element node for the trace item.
let view = this._createView(aTrace);
// Append a source item to this container.
this.push([view, aTrace.frameId, ""], {
staged: true,
attachment: {
trace: aTrace
}
});
},
/**
* Customization function for creating an item's UI.
*
* @return nsIDOMNode
* The network request view.
*/
_createView: function({ type, name, frameId, parameterNames, returnVal,
location, depth, arguments: args }) {
let fragment = document.createDocumentFragment();
this._templateItem.setAttribute("tooltiptext", SourceUtils.trimUrl(location.url));
this._templateItem.style.MozPaddingStart = depth + "em";
const TYPES = ["call", "yield", "return", "throw"];
for (let t of TYPES) {
this._templateTypeIcon.classList.toggle("trace-" + t, t == type);
}
this._templateTypeIcon.setAttribute("value", {
call: "\u2192",
yield: "Y",
return: "\u2190",
throw: "E",
terminated: "TERMINATED"
}[type]);
this._templateNameNode.setAttribute("value", name);
// All extra syntax and parameter nodes added.
const addedNodes = [];
if (parameterNames) {
const syntax = (p) => {
const el = document.createElement("label");
el.setAttribute("value", p);
el.classList.add("trace-syntax");
el.classList.add("plain");
addedNodes.push(el);
return el;
};
this._templateItem.appendChild(syntax("("));
for (let i = 0, n = parameterNames.length; i < n; i++) {
let param = document.createElement("label");
param.setAttribute("value", parameterNames[i]);
param.classList.add("trace-param");
param.classList.add("plain");
addedNodes.push(param);
this._templateItem.appendChild(param);
if (i + 1 !== n) {
this._templateItem.appendChild(syntax(", "));
}
}
this._templateItem.appendChild(syntax(")"));
}
// Flatten the DOM by removing one redundant box (the template container).
for (let node of this._template.childNodes) {
fragment.appendChild(node.cloneNode(true));
}
// Remove any added nodes from the template.
for (let node of addedNodes) {
this._templateItem.removeChild(node);
}
return fragment;
}
});
/**
* Utility functions for handling sources.
*/
@ -2789,6 +3159,7 @@ LineResults.size = function() {
*/
DebuggerView.Sources = new SourcesView();
DebuggerView.VariableBubble = new VariableBubbleView();
DebuggerView.Tracer = new TracerView();
DebuggerView.WatchExpressions = new WatchExpressionsView();
DebuggerView.EventListeners = new EventListenersView();
DebuggerView.GlobalSearch = new GlobalSearchView();

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

@ -60,6 +60,7 @@ let DebuggerView = {
this.StackFramesClassicList.initialize();
this.Sources.initialize();
this.VariableBubble.initialize();
this.Tracer.initialize();
this.WatchExpressions.initialize();
this.EventListeners.initialize();
this.GlobalSearch.initialize();
@ -95,6 +96,7 @@ let DebuggerView = {
this.StackFramesClassicList.destroy();
this.Sources.destroy();
this.VariableBubble.destroy();
this.Tracer.destroy();
this.WatchExpressions.destroy();
this.EventListeners.destroy();
this.GlobalSearch.destroy();
@ -169,7 +171,11 @@ let DebuggerView = {
// Attach a controller that handles interfacing with the debugger protocol.
VariablesViewController.attach(this.Variables, {
getEnvironmentClient: aObject => gThreadClient.environment(aObject),
getObjectClient: aObject => gThreadClient.pauseGrip(aObject)
getObjectClient: aObject => {
return aObject instanceof DebuggerController.Tracer.WrappedObject
? DebuggerController.Tracer.syncGripClient(aObject.object)
: gThreadClient.pauseGrip(aObject)
}
});
// Relay events from the VariablesView.
@ -637,6 +643,7 @@ let DebuggerView = {
ChromeGlobals: null,
StackFrames: null,
Sources: null,
Tracer: null,
Variables: null,
VariableBubble: null,
WatchExpressions: null,

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

@ -86,6 +86,12 @@
oncommand="DebuggerView.Options._toggleShowVariablesFilterBox()"/>
<command id="toggleShowOriginalSource"
oncommand="DebuggerView.Options._toggleShowOriginalSource()"/>
<command id="toggleTracing"
oncommand="DebuggerView.Tracer._onToggleTracing()"/>
<command id="startTracing"
oncommand="DebuggerView.Tracer._onStartTracing()"/>
<command id="clearTraces"
oncommand="DebuggerView.Tracer._onClear()"/>
</commandset>
<popupset id="debuggerPopupset">
@ -304,6 +310,13 @@
class="devtools-toolbarbutton"
tabindex="0"/>
</hbox>
<hbox>
<toolbarbutton id="trace"
class="devtools-toolbarbutton"
command="toggleTracing"
tabindex="0"
hidden="true"/>
</hbox>
<menulist id="chrome-globals"
class="devtools-menulist"
sizetopopup="none" hidden="true"/>
@ -328,6 +341,7 @@
<tabs>
<tab id="sources-tab" label="&debuggerUI.tabs.sources;"/>
<tab id="callstack-tab" label="&debuggerUI.tabs.callstack;"/>
<tab id="tracer-tab" label="&debuggerUI.tabs.traces;" hidden="true"/>
</tabs>
<tabpanels flex="1">
<tabpanel id="sources-tabpanel">
@ -354,6 +368,41 @@
<tabpanel id="callstack-tabpanel">
<vbox id="callstack-list" flex="1"/>
</tabpanel>
<tabpanel id="tracer-tabpanel" flex="1">
<deck id="tracer-deck" selectedIndex="1" flex="1">
<vbox flex="1">
<vbox id="tracer-traces" flex="1">
<hbox class="trace-item-template" hidden="true">
<hbox class="trace-item" align="center" flex="1" crop="end">
<label class="trace-type plain"/>
<label class="trace-name plain" crop="end"/>
</hbox>
</hbox>
</vbox>
<toolbar id="tracer-toolbar" class="devtools-toolbar">
<toolbarbutton id="clear-tracer"
label="&debuggerUI.clearButton;"
tooltiptext="&debuggerUI.clearButton.tooltip;"
command="clearTraces"
class="devtools-toolbarbutton"/>
<textbox id="tracer-search"
class="devtools-searchinput"
flex="1"
type="search"/>
</toolbar>
</vbox>
<vbox id="tracer-message"
flex="1"
align="center"
pack="center">
<description value="&debuggerUI.tracingNotStarted.label;" />
<button id="start-tracing"
class="devtools-toolbarbutton"
command="startTracing"
label="&debuggerUI.startTracing;"/>
</vbox>
</deck>
</tabpanel>
</tabpanels>
</tabbox>
<splitter id="sources-and-editor-splitter"

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

@ -9,6 +9,8 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
const promise = require("sdk/core/promise");
const EventEmitter = require("devtools/shared/event-emitter");
const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
function DebuggerPanel(iframeWindow, toolbox) {
this.panelWin = iframeWindow;
this._toolbox = toolbox;
@ -57,8 +59,7 @@ DebuggerPanel.prototype = {
return this;
})
.then(null, function onError(aReason) {
Cu.reportError("DebuggerPanel open failed. " +
aReason.error + ": " + aReason.message);
DevToolsUtils.reportException("DebuggerPane.prototype.open", aReason);
});
},

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

@ -20,6 +20,7 @@ support-files =
code_script-switching-01.js
code_script-switching-02.js
code_test-editor-mode
code_tracing-01.js
code_ugly.js
code_ugly-2.js
code_ugly-3.js
@ -56,6 +57,7 @@ support-files =
doc_script-switching-01.html
doc_script-switching-02.html
doc_step-out.html
doc_tracing-01.html
doc_watch-expressions.html
doc_with-frame.html
head.js
@ -188,6 +190,10 @@ support-files =
[browser_dbg_step-out.js]
[browser_dbg_tabactor-01.js]
[browser_dbg_tabactor-02.js]
[browser_dbg_tracing-01.js]
[browser_dbg_tracing-02.js]
[browser_dbg_tracing-03.js]
[browser_dbg_tracing-04.js]
[browser_dbg_variables-view-01.js]
[browser_dbg_variables-view-02.js]
[browser_dbg_variables-view-03.js]

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

@ -0,0 +1,109 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we get the expected frame enter/exit logs in the tracer view.
*/
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
function test() {
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
waitForSourceShown(gPanel, "code_tracing-01.js")
.then(() => startTracing(gPanel))
.then(clickButton)
.then(() => waitForClientEvents(aPanel, "traces"))
.then(testTraceLogs)
.then(() => stopTracing(gPanel))
.then(() => {
const deferred = promise.defer();
SpecialPowers.popPrefEnv(deferred.resolve);
return deferred.promise;
})
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
});
}
function clickButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee);
}
function testTraceLogs() {
const onclickLogs = filterTraces(gPanel,
t => t.querySelector(".trace-name[value=onclick]"));
is(onclickLogs.length, 2, "Should have two logs from 'onclick'");
ok(onclickLogs[0].querySelector(".trace-call"),
"The first 'onclick' log should be a call.");
ok(onclickLogs[1].querySelector(".trace-return"),
"The second 'onclick' log should be a return.");
for (let t of onclickLogs) {
ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
.contains("doc_tracing-01.html"));
}
const nonOnclickLogs = filterTraces(gPanel,
t => !t.querySelector(".trace-name[value=onclick]"));
for (let t of nonOnclickLogs) {
ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
.contains("code_tracing-01.js"));
}
const mainLogs = filterTraces(gPanel,
t => t.querySelector(".trace-name[value=main]"));
is(mainLogs.length, 2, "Should have an enter and an exit for 'main'");
ok(mainLogs[0].querySelector(".trace-call"),
"The first 'main' log should be a call.");
ok(mainLogs[1].querySelector(".trace-return"),
"The second 'main' log should be a return.");
const factorialLogs = filterTraces(gPanel,
t => t.querySelector(".trace-name[value=factorial]"));
is(factorialLogs.length, 10, "Should have 5 enter, and 5 exit frames for 'factorial'");
ok(factorialLogs.slice(0, 5).every(t => t.querySelector(".trace-call")),
"The first five 'factorial' logs should be calls.");
ok(factorialLogs.slice(5).every(t => t.querySelector(".trace-return")),
"The second five 'factorial' logs should be returns.")
// Test that the depth affects padding so that calls are indented properly.
let lastDepth = -Infinity;
for (let t of factorialLogs.slice(0, 5)) {
let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
ok(depth > lastDepth, "The depth should be increasing");
lastDepth = depth;
}
lastDepth = Infinity;
for (let t of factorialLogs.slice(5)) {
let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
ok(depth < lastDepth, "The depth should be decreasing");
lastDepth = depth;
}
const throwerLogs = filterTraces(gPanel,
t => t.querySelector(".trace-name[value=thrower]"));
is(throwerLogs.length, 2, "Should have an enter and an exit for 'thrower'");
ok(throwerLogs[0].querySelector(".trace-call"),
"The first 'thrower' log should be a call.");
ok(throwerLogs[1].querySelector(".trace-throw",
"The second 'thrower' log should be a throw."));
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
});

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

@ -0,0 +1,78 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we highlight matching calls and returns on hover.
*/
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
function test() {
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
waitForSourceShown(gPanel, "code_tracing-01.js")
.then(() => startTracing(gPanel))
.then(clickButton)
.then(() => waitForClientEvents(aPanel, "traces"))
.then(highlightCall)
.then(testReturnHighlighted)
.then(unhighlightCall)
.then(testNoneHighlighted)
.then(() => stopTracing(gPanel))
.then(() => {
const deferred = promise.defer();
SpecialPowers.popPrefEnv(deferred.resolve);
return deferred.promise;
})
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
});
}
function clickButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee);
}
function highlightCall() {
const callTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0];
EventUtils.sendMouseEvent({ type: "mouseover" },
callTrace,
gDebugger);
}
function testReturnHighlighted() {
const returnTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[1];
ok(Array.indexOf(returnTrace.querySelector(".trace-item").classList, "selected-matching") >= 0,
"The corresponding return log should be highlighted.");
}
function unhighlightCall() {
const callTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0];
EventUtils.sendMouseEvent({ type: "mouseout" },
callTrace,
gDebugger);
}
function testNoneHighlighted() {
const highlightedTraces = filterTraces(gPanel, t => t.querySelector(".selected-matching"));
is(highlightedTraces.length, 0, "Shouldn't have any highlighted traces");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
});

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

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we can jump to function definitions by clicking on logs.
*/
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
function test() {
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
waitForSourceShown(gPanel, "code_tracing-01.js")
.then(() => startTracing(gPanel))
.then(clickButton)
.then(() => waitForClientEvents(aPanel, "traces"))
.then(() => {
// Switch away from the JS file so we can make sure that clicking on a
// log will switch us back to the correct JS file.
aPanel.panelWin.DebuggerView.Sources.selectedValue = TAB_URL;
return ensureSourceIs(aPanel, TAB_URL, true);
})
.then(() => {
const finished = waitForSourceShown(gPanel, "code_tracing-01.js");
clickTraceLog();
return finished;
})
.then(testCorrectLine)
.then(() => stopTracing(gPanel))
.then(() => {
const deferred = promise.defer();
SpecialPowers.popPrefEnv(deferred.resolve);
return deferred.promise;
})
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
});
}
function clickButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee);
}
function clickTraceLog() {
filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0].click();
}
function testCorrectLine() {
is(gDebugger.DebuggerView.editor.getCursor().line, 19,
"The editor should have the function definition site's line selected.");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
});

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

@ -0,0 +1,86 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that when we click on logs, we get the parameters/return value in the variables view.
*/
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
let gTab, gDebuggee, gPanel, gDebugger, gVariables;
function test() {
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gVariables = gDebugger.DebuggerView.Variables;
waitForSourceShown(gPanel, "code_tracing-01.js")
.then(() => startTracing(gPanel))
.then(clickButton)
.then(() => waitForClientEvents(aPanel, "traces"))
.then(clickTraceCall)
.then(testParams)
.then(clickTraceReturn)
.then(testReturn)
.then(() => stopTracing(gPanel))
.then(() => {
const deferred = promise.defer();
SpecialPowers.popPrefEnv(deferred.resolve);
return deferred.promise;
})
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
DevToolsUtils.reportException("browser_dbg_tracing-04.js", aError);
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
});
}
function clickButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee);
}
function clickTraceCall() {
filterTraces(gPanel, t => t.querySelector(".trace-name[value=factorial]"))[0]
.click();
}
function testParams() {
const name = gDebugger.document.querySelector(".variables-view-variable .name");
ok(name, "Should have a variable name");
is(name.getAttribute("value"), "n", "The variable name should be n");
const value = gDebugger.document.querySelector(".variables-view-variable .value.token-number");
ok(value, "Should have a variable value");
is(value.getAttribute("value"), "5", "The variable value should be 5");
}
function clickTraceReturn() {
filterTraces(gPanel, t => t.querySelector(".trace-name[value=factorial]"))
.pop().click();
}
function testReturn() {
const name = gDebugger.document.querySelector(".variables-view-variable .name");
ok(name, "Should have a variable name");
is(name.getAttribute("value"), "<return>", "The variable name should be <return>");
const value = gDebugger.document.querySelector(".variables-view-variable .value.token-number");
ok(value, "Should have a variable value");
is(value.getAttribute("value"), "120", "The variable value should be 120");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gVariables = null;
});

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

@ -17,15 +17,35 @@ function test() {
ok(globalScope, "The globalScope hasn't been created correctly.");
ok(localScope, "The localScope hasn't been created correctly.");
is(globalScope.target.querySelector(".separator"), null,
"No separator string should be created for scopes (1).");
is(localScope.target.querySelector(".separator"), null,
"No separator string should be created for scopes (2).");
let windowVar = globalScope.addItem("window");
let documentVar = globalScope.addItem("document");
ok(windowVar, "The windowVar hasn't been created correctly.");
ok(documentVar, "The documentVar hasn't been created correctly.");
ok(windowVar.target.querySelector(".separator").hidden,
"No separator string should be shown for variables without a grip (1).");
ok(documentVar.target.querySelector(".separator").hidden,
"No separator string should be shown for variables without a grip (2).");
windowVar.setGrip({ type: "object", class: "Window" });
documentVar.setGrip({ type: "object", class: "HTMLDocument" });
is(windowVar.target.querySelector(".separator").hidden, false,
"A separator string should now be shown after setting the grip (1).");
is(documentVar.target.querySelector(".separator").hidden, false,
"A separator string should now be shown after setting the grip (2).");
is(windowVar.target.querySelector(".separator").getAttribute("value"), ": ",
"The separator string label is correct (1).");
is(documentVar.target.querySelector(".separator").getAttribute("value"), ": ",
"The separator string label is correct (2).");
let localVar0 = localScope.addItem("localVar0");
let localVar1 = localScope.addItem("localVar1");
let localVar2 = localScope.addItem("localVar2");

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

@ -61,7 +61,7 @@ function performTest() {
gVariablesView.switch = function() {};
gVariablesView.delete = function() {};
gVariablesView.rawObject = test;
gVariablesView.pageSize = 5;
gVariablesView.scrollPageSize = 5;
return Task.spawn(function() {
yield waitForTick();

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

@ -9,24 +9,21 @@
const TAB_URL = EXAMPLE_URL + "doc_large-array-buffer.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gVariables;
let gVariables, gEllipsis;
function test() {
// This is a very, very stressful test.
// Thankfully, after bug 830344 none of this will be necessary anymore.
requestLongerTimeout(10);
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gVariables = gDebugger.DebuggerView.Variables;
gEllipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
gDebugger.DebuggerView.Variables.lazyAppend = true;
waitForSourceAndCaretAndScopes(gPanel, ".html", 18)
.then(() => performTest())
waitForSourceAndCaretAndScopes(gPanel, ".html", 23)
.then(() => initialChecks())
.then(() => verifyFirstLevel())
.then(() => verifyNextLevels())
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
@ -38,23 +35,15 @@ function test() {
});
}
function performTest() {
let deferred = promise.defer();
function initialChecks() {
let localScope = gVariables.getScopeAtIndex(0);
is(localScope.expanded, true,
"The local scope should be expanded by default.");
let localEnums = localScope.target.querySelector(".variables-view-element-details.enum").childNodes;
let localNonEnums = localScope.target.querySelector(".variables-view-element-details.nonenum").childNodes;
is(localEnums.length, 5,
"The local scope should contain all the created enumerable elements.");
is(localNonEnums.length, 0,
"The local scope should contain all the created non-enumerable elements.");
let bufferVar = localScope.get("buffer");
let zVar = localScope.get("z");
let arrayVar = localScope.get("largeArray");
let objectVar = localScope.get("largeObject");
ok(bufferVar, "There should be a 'buffer' variable present in the scope.");
ok(arrayVar, "There should be a 'largeArray' variable present in the scope.");
ok(objectVar, "There should be a 'largeObject' variable present in the scope.");
is(bufferVar.target.querySelector(".name").getAttribute("value"), "buffer",
"Should have the right property name for 'buffer'.");
@ -63,163 +52,185 @@ function performTest() {
ok(bufferVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'buffer'.");
is(zVar.target.querySelector(".name").getAttribute("value"), "z",
"Should have the right property name for 'z'.");
is(zVar.target.querySelector(".value").getAttribute("value"), "Int8Array",
"Should have the right property value for 'z'.");
ok(zVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'z'.");
is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray",
"Should have the right property name for 'largeArray'.");
is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array",
"Should have the right property value for 'largeArray'.");
ok(arrayVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'largeArray'.");
EventUtils.sendMouseEvent({ type: "mousedown" },
bufferVar.target.querySelector(".arrow"),
gDebugger);
is(objectVar.target.querySelector(".name").getAttribute("value"), "largeObject",
"Should have the right property name for 'largeObject'.");
is(objectVar.target.querySelector(".value").getAttribute("value"), "Object",
"Should have the right property value for 'largeObject'.");
ok(objectVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'largeObject'.");
EventUtils.sendMouseEvent({ type: "mousedown" },
zVar.target.querySelector(".arrow"),
gDebugger);
is(bufferVar.expanded, false,
"The 'buffer' variable shouldn't be expanded.");
is(arrayVar.expanded, false,
"The 'largeArray' variable shouldn't be expanded.");
is(objectVar.expanded, false,
"The 'largeObject' variable shouldn't be expanded.");
// Need to wait for 0 enumerable and 2 non-enumerable properties in bufferVar,
// and 10000 enumerable and 5 non-enumerable properties in zVar.
let total = 0 + 2 + 10000 + 5;
let loaded = 0;
let paints = 0;
// Make sure the variables view doesn't scroll while adding the properties.
let [oldX, oldY] = getScroll();
info("Initial scroll position: " + oldX + ", " + oldY);
waitForProperties(total, {
onLoading: function(aLoaded) {
ok(aLoaded >= loaded,
"Should have loaded more properties.");
let [newX, newY] = getScroll();
info("Current scroll position: " + newX + " " + newY);
is(oldX, newX, "The variables view hasn't scrolled horizontally.");
is(oldY, newY, "The variables view hasn't scrolled vertically.");
info("Displayed " + aLoaded + " properties, not finished yet.");
loaded = aLoaded;
paints++;
},
onFinished: function(aLoaded) {
ok(aLoaded == total,
"Displayed all the properties.");
isnot(paints, 0,
"Debugger was unresponsive, sad panda.");
let [newX, newY] = getScroll();
info("Current scroll position: " + newX + ", " + newY);
is(oldX, newX, "The variables view hasn't scrolled horizontally.");
is(oldY, newY, "The variables view hasn't scrolled vertically.");
is(bufferVar._enum.childNodes.length, 0,
"The bufferVar should contain all the created enumerable elements.");
is(bufferVar._nonenum.childNodes.length, 2,
"The bufferVar should contain all the created non-enumerable elements.");
let bufferVarByteLengthProp = bufferVar.get("byteLength");
let bufferVarProtoProp = bufferVar.get("__proto__");
is(bufferVarByteLengthProp.target.querySelector(".name").getAttribute("value"), "byteLength",
"Should have the right property name for 'byteLength'.");
is(bufferVarByteLengthProp.target.querySelector(".value").getAttribute("value"), "10000",
"Should have the right property value for 'byteLength'.");
ok(bufferVarByteLengthProp.target.querySelector(".value").className.contains("token-number"),
"Should have the right token class for 'byteLength'.");
is(bufferVarProtoProp.target.querySelector(".name").getAttribute("value"), "__proto__",
"Should have the right property name for '__proto__'.");
is(bufferVarProtoProp.target.querySelector(".value").getAttribute("value"), "ArrayBufferPrototype",
"Should have the right property value for '__proto__'.");
ok(bufferVarProtoProp.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for '__proto__'.");
is(zVar._enum.childNodes.length, 10000,
"The zVar should contain all the created enumerable elements.");
is(zVar._nonenum.childNodes.length, 5,
"The zVar should contain all the created non-enumerable elements.");
let zVarByteLengthProp = zVar.get("byteLength");
let zVarByteOffsetProp = zVar.get("byteOffset");
let zVarProtoProp = zVar.get("__proto__");
is(zVarByteLengthProp.target.querySelector(".name").getAttribute("value"), "byteLength",
"Should have the right property name for 'byteLength'.");
is(zVarByteLengthProp.target.querySelector(".value").getAttribute("value"), "10000",
"Should have the right property value for 'byteLength'.");
ok(zVarByteLengthProp.target.querySelector(".value").className.contains("token-number"),
"Should have the right token class for 'byteLength'.");
is(zVarByteOffsetProp.target.querySelector(".name").getAttribute("value"), "byteOffset",
"Should have the right property name for 'byteOffset'.");
is(zVarByteOffsetProp.target.querySelector(".value").getAttribute("value"), "0",
"Should have the right property value for 'byteOffset'.");
ok(zVarByteOffsetProp.target.querySelector(".value").className.contains("token-number"),
"Should have the right token class for 'byteOffset'.");
is(zVarProtoProp.target.querySelector(".name").getAttribute("value"), "__proto__",
"Should have the right property name for '__proto__'.");
is(zVarProtoProp.target.querySelector(".value").getAttribute("value"), "Int8ArrayPrototype",
"Should have the right property value for '__proto__'.");
ok(zVarProtoProp.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for '__proto__'.");
let arrayElements = zVar._enum.childNodes;
for (let i = 0, len = arrayElements.length; i < len; i++) {
let node = arrayElements[i];
let name = node.querySelector(".name").getAttribute("value");
let value = node.querySelector(".value").getAttribute("value");
if (name !== i + "" || value !== "0") {
ok(false, "The array items aren't in the correct order.");
}
}
deferred.resolve();
},
onTimeout: function() {
ok(false, "Timed out while polling for the properties.");
deferred.resolve();
}
});
function getScroll() {
let scrollX = {};
let scrollY = {};
gVariables.boxObject.getPosition(scrollX, scrollY);
return [scrollX.value, scrollY.value];
}
return deferred.promise;
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2);
arrayVar.expand();
objectVar.expand();
return finished;
}
function waitForProperties(aTotal, aCallbacks, aInterval = 10) {
function verifyFirstLevel() {
let localScope = gVariables.getScopeAtIndex(0);
let bufferEnum = localScope.get("buffer")._enum.childNodes;
let bufferNonEnum = localScope.get("buffer")._nonenum.childNodes;
let zEnum = localScope.get("z")._enum.childNodes;
let zNonEnum = localScope.get("z")._nonenum.childNodes;
let arrayVar = localScope.get("largeArray");
let objectVar = localScope.get("largeObject");
// Poll every few milliseconds until the properties are retrieved.
let count = 0;
let intervalId = window.setInterval(() => {
// Make sure we don't wait for too long.
if (++count > 1000) {
window.clearInterval(intervalId);
aCallbacks.onTimeout();
return;
}
// Check if we need to wait for a few more properties to be fetched.
let loaded = bufferEnum.length + bufferNonEnum.length + zEnum.length + zNonEnum.length;
if (loaded < aTotal) {
aCallbacks.onLoading(loaded);
return;
}
// We got all the properties, it's safe to callback.
window.clearInterval(intervalId);
aCallbacks.onFinished(loaded);
}, aInterval);
let arrayEnums = arrayVar.target.querySelector(".variables-view-element-details.enum").childNodes;
let arrayNonEnums = arrayVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
is(arrayEnums.length, 0,
"The 'largeArray' shouldn't contain any enumerable elements.");
is(arrayNonEnums.length, 9,
"The 'largeArray' should contain all the created non-enumerable elements.");
let objectEnums = objectVar.target.querySelector(".variables-view-element-details.enum").childNodes;
let objectNonEnums = objectVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
is(objectEnums.length, 0,
"The 'largeObject' shouldn't contain any enumerable elements.");
is(objectNonEnums.length, 5,
"The 'largeObject' should contain all the created non-enumerable elements.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
0 + gEllipsis + 1999, "The first page in the 'largeArray' is named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
"", "The first page in the 'largeArray' should not have a corresponding value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
2000 + gEllipsis + 3999, "The second page in the 'largeArray' is named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
"", "The second page in the 'largeArray' should not have a corresponding value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
4000 + gEllipsis + 5999, "The third page in the 'largeArray' is named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"),
"", "The third page in the 'largeArray' should not have a corresponding value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
6000 + gEllipsis + 9999, "The fourth page in the 'largeArray' is named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"),
"", "The fourth page in the 'largeArray' should not have a corresponding value.");
is(objectVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
0 + gEllipsis + 1999, "The first page in the 'largeObject' is named correctly.");
is(objectVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
"", "The first page in the 'largeObject' should not have a corresponding value.");
is(objectVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
2000 + gEllipsis + 3999, "The second page in the 'largeObject' is named correctly.");
is(objectVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
"", "The second page in the 'largeObject' should not have a corresponding value.");
is(objectVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
4000 + gEllipsis + 5999, "The thrid page in the 'largeObject' is named correctly.");
is(objectVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"),
"", "The thrid page in the 'largeObject' should not have a corresponding value.");
is(objectVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
6000 + gEllipsis + 9999, "The fourth page in the 'largeObject' is named correctly.");
is(objectVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"),
"", "The fourth page in the 'largeObject' should not have a corresponding value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"),
"length", "The other properties 'largeArray' are named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"),
"10000", "The other properties 'largeArray' have the correct value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[5].getAttribute("value"),
"buffer", "The other properties 'largeArray' are named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[5].getAttribute("value"),
"ArrayBuffer", "The other properties 'largeArray' have the correct value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[6].getAttribute("value"),
"byteLength", "The other properties 'largeArray' are named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[6].getAttribute("value"),
"10000", "The other properties 'largeArray' have the correct value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[7].getAttribute("value"),
"byteOffset", "The other properties 'largeArray' are named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[7].getAttribute("value"),
"0", "The other properties 'largeArray' have the correct value.");
is(arrayVar.target.querySelectorAll(".variables-view-property .name")[8].getAttribute("value"),
"__proto__", "The other properties 'largeArray' are named correctly.");
is(arrayVar.target.querySelectorAll(".variables-view-property .value")[8].getAttribute("value"),
"Int8ArrayPrototype", "The other properties 'largeArray' have the correct value.");
is(objectVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"),
"__proto__", "The other properties 'largeObject' are named correctly.");
is(objectVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"),
"Object", "The other properties 'largeObject' have the correct value.");
}
function verifyNextLevels() {
let localScope = gVariables.getScopeAtIndex(0);
let objectVar = localScope.get("largeObject");
let lastPage1 = objectVar.get(6000 + gEllipsis + 9999);
ok(lastPage1, "The last page in the first level was retrieved successfully.");
lastPage1.expand();
let pageEnums1 = lastPage1.target.querySelector(".variables-view-element-details.enum").childNodes;
let pageNonEnums1 = lastPage1.target.querySelector(".variables-view-element-details.nonenum").childNodes;
is(pageEnums1.length, 0,
"The last page in the first level shouldn't contain any enumerable elements.");
is(pageNonEnums1.length, 4,
"The last page in the first level should contain all the created non-enumerable elements.");
is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
6000 + gEllipsis + 6999, "The first page in this level named correctly (1).");
is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
7000 + gEllipsis + 7999, "The second page in this level named correctly (1).");
is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
8000 + gEllipsis + 8999, "The third page in this level named correctly (1).");
is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
9000 + gEllipsis + 9999, "The fourth page in this level named correctly (1).");
let lastPage2 = lastPage1.get(9000 + gEllipsis + 9999);
ok(lastPage2, "The last page in the second level was retrieved successfully.");
lastPage2.expand();
let pageEnums2 = lastPage2.target.querySelector(".variables-view-element-details.enum").childNodes;
let pageNonEnums2 = lastPage2.target.querySelector(".variables-view-element-details.nonenum").childNodes;
is(pageEnums2.length, 0,
"The last page in the second level shouldn't contain any enumerable elements.");
is(pageNonEnums2.length, 4,
"The last page in the second level should contain all the created non-enumerable elements.");
is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
9000 + gEllipsis + 9199, "The first page in this level named correctly (2).");
is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
9200 + gEllipsis + 9399, "The second page in this level named correctly (2).");
is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
9400 + gEllipsis + 9599, "The third page in this level named correctly (2).");
is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
9600 + gEllipsis + 9999, "The fourth page in this level named correctly (2).");
let lastPage3 = lastPage2.get(9600 + gEllipsis + 9999);
ok(lastPage3, "The last page in the third level was retrieved successfully.");
lastPage3.expand();
let pageEnums3 = lastPage3.target.querySelector(".variables-view-element-details.enum").childNodes;
let pageNonEnums3 = lastPage3.target.querySelector(".variables-view-element-details.nonenum").childNodes;
is(pageEnums3.length, 400,
"The last page in the third level should contain all the created enumerable elements.");
is(pageNonEnums3.length, 0,
"The last page in the third level shouldn't contain any non-enumerable elements.");
is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
9600, "The properties in this level are named correctly (3).");
is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
9601, "The properties in this level are named correctly (3).");
is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[398].getAttribute("value"),
9998, "The properties in this level are named correctly (3).");
is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[399].getAttribute("value"),
9999, "The properties in this level are named correctly (3).");
is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
399, "The properties in this level have the correct value (3).");
is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
398, "The properties in this level have the correct value (3).");
is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[398].getAttribute("value"),
1, "The properties in this level have the correct value (3).");
is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[399].getAttribute("value"),
0, "The properties in this level have the correct value (3).");
}
registerCleanupFunction(function() {
@ -228,4 +239,5 @@ registerCleanupFunction(function() {
gPanel = null;
gDebugger = null;
gVariables = null;
gEllipsis = null;
});

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

@ -0,0 +1,29 @@
function factorial(n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
function* yielder(n) {
while (n-- >= 0) {
yield { value: n, squared: n * n };
}
}
function thrower() {
throw new Error("Curse your sudden but inevitable betrayal!");
}
function main() {
factorial(5);
// XXX bug 923729: Can't test yielding yet.
// for (let x of yielder(5)) {}
try {
thrower();
} catch (e) {
}
}

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

@ -14,7 +14,12 @@
<script type="text/javascript">
function test(aNumber) {
var buffer = new ArrayBuffer(aNumber);
var z = new Int8Array(buffer);
var largeArray = new Int8Array(buffer);
var largeObject = {};
for (var i = 0; i < aNumber; i++) {
largeObject[i] = aNumber - i - 1;
}
debugger;
}
</script>

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

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Debugger Tracer test page</title>
</head>
<body>
<script src="code_tracing-01.js"></script>
<button onclick="main()">Click me!</button>
</body>
</html>

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

@ -375,6 +375,26 @@ function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) {
return deferred.promise;
}
function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) {
info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
let deferred = promise.defer();
let client = aPanel.panelWin.gClient;
let count = 0;
client.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
if (count == aEventRepeat) {
ok(true, "Enough '" + aEventName + "' thread events have been fired.");
client.removeListener(aEventName, onEvent);
deferred.resolve.apply(deferred, aArgs);
}
});
return deferred.promise;
}
function ensureThreadClientState(aPanel, aState) {
let thread = aPanel.panelWin.gThreadClient;
let state = thread.state;
@ -488,8 +508,6 @@ function prepareDebugger(aDebugger) {
if ("target" in aDebugger) {
let variables = aDebugger.panelWin.DebuggerView.Variables;
variables.lazyEmpty = false;
variables.lazyAppend = false;
variables.lazyExpand = false;
variables.lazySearch = false;
} else {
// Nothing to do here yet.
@ -600,3 +618,38 @@ function hideVarPopupByScrollingEditor(aPanel) {
function reopenVarPopup(...aArgs) {
return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs));
}
// Tracing helpers
function startTracing(aPanel) {
const deferred = promise.defer();
aPanel.panelWin.DebuggerController.Tracer.startTracing(aResponse => {
if (aResponse.error) {
deferred.reject(aResponse);
} else {
deferred.resolve(aResponse);
}
});
return deferred.promise;
}
function stopTracing(aPanel) {
const deferred = promise.defer();
aPanel.panelWin.DebuggerController.Tracer.stopTracing(aResponse => {
if (aResponse.error) {
deferred.reject(aResponse);
} else {
deferred.resolve(aResponse);
}
});
return deferred.promise;
}
function filterTraces(aPanel, f) {
const traces = aPanel.panelWin.document
.getElementById("tracer-traces")
.querySelector("scrollbox")
.children;
return Array.filter(traces, f);
}

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

@ -515,7 +515,11 @@ InspectorPanel.prototype = {
if (this.walker) {
this.walker.off("new-root", this.onNewRoot);
this._destroyPromise = this.walker.release()
.then(() => this._inspector.destroy())
.then(() => this._inspector.destroy(),
(e) => {
console.error("Walker.release() failed: " + e);
return this._inspector.destroy();
})
.then(() => {
this._inspector = null;
}, console.error);

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

@ -1750,8 +1750,7 @@ NetworkDetailsView.prototype = {
return promise.all(aResponse.headers.map(header => {
let headerVar = headersScope.addItem(header.name, {}, true);
return gNetwork.getString(header.value)
.then(aString => headerVar.setGrip(aString));
return gNetwork.getString(header.value).then(aString => headerVar.setGrip(aString));
}));
},

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

@ -61,14 +61,14 @@ function test() {
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
"There should be 1 json scope displayed in this tabpanel.");
is(tabpanel.querySelectorAll(".variables-view-property").length, 6057,
"There should be 6057 json properties displayed in this tabpanel.");
is(tabpanel.querySelectorAll(".variables-view-property").length, 6143,
"There should be 6143 json properties displayed in this tabpanel.");
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
"The empty notice should not be displayed in this tabpanel.");
let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
let names = ".variables-view-property .name";
let values = ".variables-view-property .value";
let names = ".variables-view-property > .title > .name";
let values = ".variables-view-property > .title > .value";
is(jsonScope.querySelector(".name").getAttribute("value"),
L10N.getStr("jsonScopeName"),
@ -83,11 +83,6 @@ function test() {
"greeting", "The second json property name was incorrect.");
is(jsonScope.querySelectorAll(values)[1].getAttribute("value"),
"\"Hello long string JSON!\"", "The second json property value was incorrect.");
is(Array.slice(jsonScope.querySelectorAll(names), -1).shift().getAttribute("value"),
"__proto__", "The last json property name was incorrect.");
is(Array.slice(jsonScope.querySelectorAll(values), -1).shift().getAttribute("value"),
"Object", "The last json property value was incorrect.");
}
});

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

@ -0,0 +1,210 @@
const EventEmitter = require("devtools/shared/event-emitter");
const { Cu, Ci } = require("chrome");
const { ViewHelpers } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
/**
* A list menu widget that attempts to be very fast.
*
* Note: this widget should be used in tandem with the WidgetMethods in
* ViewHelpers.jsm.
*
* Note: this widget also reuses SideMenuWidget CSS class names.
*
* @param nsIDOMNode aNode
* The element associated with the widget.
*/
const FastListWidget = module.exports = function FastListWidget(aNode) {
this.document = aNode.ownerDocument;
this.window = this.document.defaultView;
this._parent = aNode;
this._fragment = this.document.createDocumentFragment();
// This is a prototype element that each item added to the list clones.
this._templateElement = this.document.createElement("hbox");
this._templateElement.className = "side-menu-widget-item side-menu-widget-item-contents";
// Create an internal scrollbox container.
this._list = this.document.createElement("scrollbox");
this._list.className = "side-menu-widget-container";
this._list.setAttribute("flex", "1");
this._list.setAttribute("orient", "vertical");
this._list.setAttribute("theme", "dark");
this._list.setAttribute("tabindex", "0");
this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
this._parent.appendChild(this._list);
this._orderedMenuElementsArray = [];
this._itemsByElement = new Map();
// This widget emits events that can be handled in a MenuContainer.
EventEmitter.decorate(this);
// Delegate some of the associated node's methods to satisfy the interface
// required by MenuContainer instances.
ViewHelpers.delegateWidgetEventMethods(this, aNode);
}
FastListWidget.prototype = {
/**
* Inserts an item in this container at the specified index, optionally
* grouping by name.
*
* @param number aIndex
* The position in the container intended for this item.
* @param nsIDOMNode aContents
* The node to be displayed in the container.
* @param Object aAttachment [optional]
* Extra data for the user.
* @return nsIDOMNode
* The element associated with the displayed item.
*/
insertItemAt: function(aIndex, aContents, aAttachment={}) {
let element = this._templateElement.cloneNode();
element.appendChild(aContents);
if (aIndex >= 0) {
throw new Error("FastListWidget only supports appending items.");
}
this._fragment.appendChild(element);
this._orderedMenuElementsArray.push(element);
this._itemsByElement.set(element, this);
return element;
},
/**
* This is a non-standard widget implementation method. When appending items,
* they are queued in a document fragment. This method appends the document
* fragment to the dom.
*/
flush: function() {
this._list.appendChild(this._fragment);
},
/**
* Removes all of the child nodes from this container.
*/
removeAllItems: function() {
let parent = this._parent;
let list = this._list;
while (list.hasChildNodes()) {
list.firstChild.remove();
}
this._selectedItem = null;
this._orderedMenuElementsArray.length = 0;
this._itemsByElement.clear();
},
/**
* Remove the given item.
*/
removeChild: function(child) {
throw new Error("Not yet implemented");
},
/**
* Gets the currently selected child node in this container.
* @return nsIDOMNode
*/
get selectedItem() this._selectedItem,
/**
* Sets the currently selected child node in this container.
* @param nsIDOMNode child
*/
set selectedItem(child) {
let menuArray = this._orderedMenuElementsArray;
if (!child) {
this._selectedItem = null;
}
for (let node of menuArray) {
if (node == child) {
node.classList.add("selected");
node.parentNode.classList.add("selected");
this._selectedItem = node;
} else {
node.classList.remove("selected");
node.parentNode.classList.remove("selected");
}
}
this.ensureElementIsVisible(this.selectedItem);
},
/**
* Returns the child node in this container situated at the specified index.
*
* @param number index
* The position in the container intended for this item.
* @return nsIDOMNode
* The element associated with the displayed item.
*/
getItemAtIndex: function(index) {
return this._orderedMenuElementsArray[index];
},
/**
* Returns the value of the named attribute on this container.
*
* @param string name
* The name of the attribute.
* @return string
* The current attribute value.
*/
getAttribute: function(name) {
return this._parent.getAttribute(name);
},
/**
* Adds a new attribute or changes an existing attribute on this container.
*
* @param string name
* The name of the attribute.
* @param string value
* The desired attribute value.
*/
setAttribute: function(name, value) {
this._parent.setAttribute(name, value);
},
/**
* Removes an attribute on this container.
*
* @param string name
* The name of the attribute.
*/
removeAttribute: function(name) {
this._parent.removeAttribute(name);
},
/**
* Ensures the specified element is visible.
*
* @param nsIDOMNode element
* The element to make visible.
*/
ensureElementIsVisible: function(element) {
if (!element) {
return;
}
// Ensure the element is visible but not scrolled horizontally.
let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
boxObject.ensureElementIsVisible(element);
boxObject.scrollBy(-element.clientWidth, 0);
},
window: null,
document: null,
_parent: null,
_list: null,
_selectedItem: null,
_orderedMenuElementsArray: null,
_itemsByElement: null
};

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

@ -177,6 +177,7 @@ SideMenuWidget.prototype = {
this._orderedMenuElementsArray.splice(
this._orderedMenuElementsArray.indexOf(aChild), 1);
this._itemsByElement.delete(aChild);
if (this._selectedItem == aChild) {

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

@ -11,8 +11,8 @@ const Cu = Components.utils;
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const LAZY_EMPTY_DELAY = 150; // ms
const LAZY_EXPAND_DELAY = 50; // ms
const LAZY_APPEND_DELAY = 100; // ms
const LAZY_APPEND_BATCH = 100; // nodes
const SCROLL_PAGE_SIZE_DEFAULT = 0;
const APPEND_PAGE_SIZE_DEFAULT = 500;
const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100;
const PAGE_SIZE_MAX_JUMPS = 30;
const SEARCH_ACTION_MAX_DELAY = 300; // ms
@ -225,23 +225,24 @@ VariablesView.prototype = {
*/
lazyEmpty: false,
/**
* Specifies if nodes in this view may be added lazily.
* @see Scope.prototype._lazyAppend
*/
lazyAppend: true,
/**
* Specifies if nodes in this view may be expanded lazily.
* @see Scope.prototype.expand
*/
lazyExpand: true,
/**
* Specifies if nodes in this view may be searched lazily.
*/
lazySearch: true,
/**
* The number of elements in this container to jump when Page Up or Page Down
* keys are pressed. If falsy, then the page size will be based on the
* container height.
*/
scrollPageSize: SCROLL_PAGE_SIZE_DEFAULT,
/**
* The maximum number of elements allowed in a scope, variable or property
* that allows pagination when appending children.
*/
appendPageSize: APPEND_PAGE_SIZE_DEFAULT,
/**
* Function called each time a variable or property's value is changed via
* user interaction. If null, then value changes are disabled.
@ -821,14 +822,14 @@ VariablesView.prototype = {
case e.DOM_VK_PAGE_UP:
// Rewind a certain number of elements based on the container height.
this.focusItemAtDelta(-(this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
this.focusItemAtDelta(-(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight /
PAGE_SIZE_SCROLL_HEIGHT_RATIO),
PAGE_SIZE_MAX_JUMPS)));
return;
case e.DOM_VK_PAGE_DOWN:
// Advance a certain number of elements based on the container height.
this.focusItemAtDelta(+(this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
this.focusItemAtDelta(+(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight /
PAGE_SIZE_SCROLL_HEIGHT_RATIO),
PAGE_SIZE_MAX_JUMPS)));
return;
@ -882,13 +883,6 @@ VariablesView.prototype = {
}
},
/**
* The number of elements in this container to jump when Page Up or Page Down
* keys are pressed. If falsy, then the page size will be based on the
* container height.
*/
pageSize: 0,
/**
* Sets the text displayed in this container when there are no available items.
* @param string aValue
@ -1201,10 +1195,11 @@ function Scope(aView, aName, aFlags = {}) {
this._onClick = this._onClick.bind(this);
this._openEnum = this._openEnum.bind(this);
this._openNonEnum = this._openNonEnum.bind(this);
this._batchAppend = this._batchAppend.bind(this);
// Inherit properties and flags from the parent view. You can override
// each of these directly onto any scope, variable or property instance.
this.scrollPageSize = aView.scrollPageSize;
this.appendPageSize = aView.appendPageSize;
this.eval = aView.eval;
this.switch = aView.switch;
this.delete = aView.delete;
@ -1227,6 +1222,16 @@ Scope.prototype = {
*/
shouldPrefetch: true,
/**
* Whether this Scope should paginate its contents.
*/
allowPaginate: false,
/**
* The class name applied to this scope's target element.
*/
targetClassName: "variables-view-scope",
/**
* Create a new Variable that is a child of this Scope.
*
@ -1259,8 +1264,9 @@ Scope.prototype = {
* - { value: { type: "object", class: "Object" } }
* - { get: { type: "object", class: "Function" },
* set: { type: "undefined" } }
* @param boolean aRelaxed
* True if name duplicates should be allowed.
* @param boolean aRelaxed [optional]
* Pass true if name duplicates should be allowed.
* You probably shouldn't do it. Use this with caution.
* @return Variable
* The newly created Variable instance, null if it already exists.
*/
@ -1298,14 +1304,84 @@ Scope.prototype = {
* Additional options for adding the properties. Supported options:
* - sorted: true to sort all the properties before adding them
* - callback: function invoked after each item is added
* @param string aKeysType [optional]
* Helper argument in the case of paginated items. Can be either
* "just-strings" or "just-numbers". Humans shouldn't use this argument.
*/
addItems: function(aItems, aOptions = {}) {
addItems: function(aItems, aOptions = {}, aKeysType = "") {
let names = Object.keys(aItems);
// Building the view when inspecting an object with a very large number of
// properties may take a long time. To avoid blocking the UI, group
// the items into several lazily populated pseudo-items.
let exceedsThreshold = names.length >= this.appendPageSize;
let shouldPaginate = exceedsThreshold && aKeysType != "just-strings";
if (shouldPaginate && this.allowPaginate) {
// Group the items to append into two separate arrays, one containing
// number-like keys, the other one containing string keys.
if (aKeysType == "just-numbers") {
var numberKeys = names;
var stringKeys = [];
} else {
var numberKeys = [];
var stringKeys = [];
for (let name of names) {
// Be very careful. Avoid Infinity, NaN and non Natural number keys.
let coerced = +name;
if (Number.isInteger(coerced) && coerced > -1) {
numberKeys.push(name);
} else {
stringKeys.push(name);
}
}
}
// This object contains a very large number of properties, but they're
// almost all strings that can't be coerced to numbers. Don't paginate.
if (numberKeys.length < this.appendPageSize) {
this.addItems(aItems, aOptions, "just-strings");
return;
}
// Slices a section of the { name: descriptor } data properties.
let paginate = (aArray, aBegin = 0, aEnd = aArray.length) => {
let store = {}
for (let i = aBegin; i < aEnd; i++) {
let name = aArray[i];
store[name] = aItems[name];
}
return store;
};
// Creates a pseudo-item that populates itself with the data properties
// from the corresponding page range.
let createRangeExpander = (aArray, aBegin, aEnd, aOptions, aKeyTypes) => {
let rangeVar = this.addItem(aArray[aBegin] + Scope.ellipsis + aArray[aEnd - 1]);
rangeVar.onexpand = () => {
let pageItems = paginate(aArray, aBegin, aEnd);
rangeVar.addItems(pageItems, aOptions, aKeyTypes);
}
rangeVar.showArrow();
rangeVar.target.setAttribute("pseudo-item", "");
};
// Divide the number keys into quarters.
let page = +Math.round(numberKeys.length / 4).toPrecision(1);
createRangeExpander(numberKeys, 0, page, aOptions, "just-numbers");
createRangeExpander(numberKeys, page, page * 2, aOptions, "just-numbers");
createRangeExpander(numberKeys, page * 2, page * 3, aOptions, "just-numbers");
createRangeExpander(numberKeys, page * 3, numberKeys.length, aOptions, "just-numbers");
// Append all the string keys together.
this.addItems(paginate(stringKeys), aOptions, "just-strings");
return;
}
// Sort all of the properties before adding them, if preferred.
if (aOptions.sorted) {
if (aOptions.sorted && aKeysType != "just-numbers") {
names.sort();
}
// Add the properties to the current scope.
for (let name of names) {
let descriptor = aItems[name];
@ -1432,32 +1508,15 @@ Scope.prototype = {
* Expands the scope, showing all the added details.
*/
expand: function() {
if (this._isExpanded || this._locked) {
if (this._isExpanded || this._isLocked) {
return;
}
// If there's a large number of enumerable or non-enumerable items
// contained in this scope, painting them may take several seconds,
// even if they were already displayed before. In this case, show a throbber
// to suggest that this scope is expanding.
if (!this._isExpanding &&
this._variablesView.lazyExpand &&
this._store.size > LAZY_APPEND_BATCH) {
this._isExpanding = true;
// Start spinning a throbber in this scope's title and allow a few
// milliseconds for it to be painted.
this._startThrobber();
this.window.setTimeout(this.expand.bind(this), LAZY_EXPAND_DELAY);
return;
}
if (this._variablesView._enumVisible) {
this._openEnum();
}
if (this._variablesView._nonEnumVisible) {
Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0);
}
this._isExpanding = false;
this._isExpanded = true;
if (this.onexpand) {
@ -1469,7 +1528,7 @@ Scope.prototype = {
* Collapses the scope, hiding all the added details.
*/
collapse: function() {
if (!this._isExpanded || this._locked) {
if (!this._isExpanded || this._isLocked) {
return;
}
this._arrow.removeAttribute("open");
@ -1576,7 +1635,7 @@ Scope.prototype = {
* Gets the expand lock state.
* @return boolean
*/
get locked() this._locked,
get locked() this._isLocked,
/**
* Sets the visibility state.
@ -1606,7 +1665,7 @@ Scope.prototype = {
* Sets the expand lock state.
* @param boolean aFlag
*/
set locked(aFlag) this._locked = aFlag,
set locked(aFlag) this._isLocked = aFlag,
/**
* Specifies if this target node may be focused.
@ -1699,7 +1758,7 @@ Scope.prototype = {
*/
_init: function(aName, aFlags) {
this._idString = generateId(this._nameString = aName);
this._displayScope(aName, "variables-view-scope", "devtools-toolbar");
this._displayScope(aName, this.targetClassName, "devtools-toolbar");
this._addEventListeners();
this.parentNode.appendChild(this._target);
},
@ -1709,17 +1768,17 @@ Scope.prototype = {
*
* @param string aName
* The scope's name.
* @param string aClassName
* A custom class name for this scope.
* @param string aTargetClassName
* A custom class name for this scope's target element.
* @param string aTitleClassName [optional]
* A custom class name for this scope's title.
* A custom class name for this scope's title element.
*/
_displayScope: function(aName, aClassName, aTitleClassName) {
_displayScope: function(aName, aTargetClassName, aTitleClassName = "") {
let document = this.document;
let element = this._target = document.createElement("vbox");
element.id = this._idString;
element.className = aClassName;
element.className = aTargetClassName;
let arrow = this._arrow = document.createElement("hbox");
arrow.className = "arrow";
@ -1729,7 +1788,7 @@ Scope.prototype = {
name.setAttribute("value", aName);
let title = this._title = document.createElement("hbox");
title.className = "title " + (aTitleClassName || "");
title.className = "title " + aTitleClassName;
title.setAttribute("align", "center");
let enumerable = this._enum = document.createElement("vbox");
@ -1766,99 +1825,12 @@ Scope.prototype = {
this.focus();
},
/**
* Lazily appends a node to this scope's enumerable or non-enumerable
* container. Once a certain number of nodes have been batched, they
* will be appended.
*
* @param boolean aImmediateFlag
* Set to false if append calls should be dispatched synchronously
* on the current thread, to allow for a paint flush.
* @param boolean aEnumerableFlag
* Specifies if the node to append is enumerable or non-enumerable.
* @param nsIDOMNode aChild
* The child node to append.
*/
_lazyAppend: function(aImmediateFlag, aEnumerableFlag, aChild) {
// Append immediately, don't stage items and don't allow for a paint flush.
if (aImmediateFlag || !this._variablesView.lazyAppend) {
if (aEnumerableFlag) {
this._enum.appendChild(aChild);
} else {
this._nonenum.appendChild(aChild);
}
return;
}
let window = this.window;
let batchItems = this._batchItems;
window.clearTimeout(this._batchTimeout);
batchItems.push({ enumerableFlag: aEnumerableFlag, child: aChild });
// If a certain number of nodes have been batched, append all the
// staged items now.
if (batchItems.length > LAZY_APPEND_BATCH) {
// Allow for a paint flush.
Services.tm.currentThread.dispatch({ run: this._batchAppend }, 1);
return;
}
// Postpone appending the staged items for later, to allow batching
// more nodes.
this._batchTimeout = window.setTimeout(this._batchAppend, LAZY_APPEND_DELAY);
},
/**
* Appends all the batched nodes to this scope's enumerable and non-enumerable
* containers.
*/
_batchAppend: function() {
let document = this.document;
let batchItems = this._batchItems;
// Create two document fragments, one for enumerable nodes, and one
// for non-enumerable nodes.
let frags = [document.createDocumentFragment(), document.createDocumentFragment()];
for (let item of batchItems) {
frags[~~item.enumerableFlag].appendChild(item.child);
}
batchItems.length = 0;
this._enum.appendChild(frags[1]);
this._nonenum.appendChild(frags[0]);
},
/**
* Starts spinning a throbber in this scope's title.
*/
_startThrobber: function() {
if (this._throbber) {
this._throbber.hidden = false;
return;
}
let throbber = this._throbber = this.document.createElement("hbox");
throbber.className = "variables-view-throbber";
throbber.setAttribute("optional-visibility", "");
this._title.insertBefore(throbber, this._spacer);
},
/**
* Stops spinning the throbber in this scope's title.
*/
_stopThrobber: function() {
if (!this._throbber) {
return;
}
this._throbber.hidden = true;
},
/**
* Opens the enumerable items container.
*/
_openEnum: function() {
this._arrow.setAttribute("open", "");
this._enum.setAttribute("open", "");
this._stopThrobber();
},
/**
@ -1866,7 +1838,6 @@ Scope.prototype = {
*/
_openNonEnum: function() {
this._nonenum.setAttribute("open", "");
this._stopThrobber();
},
/**
@ -2108,10 +2079,7 @@ Scope.prototype = {
_fetched: false,
_retrieved: false,
_committed: false,
_batchItems: null,
_batchTimeout: null,
_locked: false,
_isExpanding: false,
_isLocked: false,
_isExpanded: false,
_isContentVisible: true,
_isHeaderVisible: true,
@ -2125,7 +2093,6 @@ Scope.prototype = {
_title: null,
_enum: null,
_nonenum: null,
_throbber: null
};
// Creating maps and arrays thousands of times for variables or properties
@ -2134,7 +2101,10 @@ Scope.prototype = {
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", Map);
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array);
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array);
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_batchItems", Array);
// An ellipsis symbol (usually "…") used for localization.
XPCOMUtils.defineLazyGetter(Scope, "ellipsis", () =>
Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data);
/**
* A Variable is a Scope holding Property instances.
@ -2167,12 +2137,24 @@ function Variable(aScope, aName, aDescriptor) {
Variable.prototype = Heritage.extend(Scope.prototype, {
/**
* Whether this Scope should be prefetched when it is remoted.
* Whether this Variable should be prefetched when it is remoted.
*/
get shouldPrefetch(){
get shouldPrefetch() {
return this.name == "window" || this.name == "this";
},
/**
* Whether this Variable should paginate its contents.
*/
get allowPaginate() {
return this.name != "window" && this.name != "this";
},
/**
* The class name applied to this variable's target element.
*/
targetClassName: "variables-view-variable variable-or-property",
/**
* Create a new Property that is a child of Variable.
*
@ -2371,6 +2353,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this._valueLabel.classList.add(this._valueClassName);
this._valueLabel.setAttribute("value", this._valueString);
this._separatorLabel.hidden = false;
},
/**
@ -2414,33 +2397,21 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
*/
_init: function(aName, aDescriptor) {
this._idString = generateId(this._nameString = aName);
this._displayScope(aName, "variables-view-variable variable-or-property");
this._displayScope(aName, this.targetClassName);
this._displayVariable();
this._customizeVariable();
this._prepareTooltips();
this._setAttributes();
this._addEventListeners();
this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
},
/**
* Called when this variable has finished initializing, and is ready to
* be attached to the owner view.
*
* @param boolean aImmediateFlag
* @see Scope.prototype._lazyAppend
*/
_onInit: function(aImmediateFlag) {
if (this._initialDescriptor.enumerable ||
this._nameString == "this" ||
this._nameString == "<return>" ||
this._nameString == "<exception>") {
this.ownerView._lazyAppend(aImmediateFlag, true, this._target);
this.ownerView._enum.appendChild(this._target);
this.ownerView._enumItems.push(this);
} else {
this.ownerView._lazyAppend(aImmediateFlag, false, this._target);
this.ownerView._nonenum.appendChild(this._target);
this.ownerView._nonEnumItems.push(this);
}
},
@ -2454,7 +2425,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
let separatorLabel = this._separatorLabel = document.createElement("label");
separatorLabel.className = "plain separator";
separatorLabel.setAttribute("value", this.ownerView.separatorStr + " ");
separatorLabel.setAttribute("value", this.separatorStr + " ");
let valueLabel = this._valueLabel = document.createElement("label");
valueLabel.className = "plain value";
@ -2867,42 +2838,9 @@ function Property(aVar, aName, aDescriptor) {
Property.prototype = Heritage.extend(Variable.prototype, {
/**
* Initializes this property's id, view and binds event listeners.
*
* @param string aName
* The property's name.
* @param object aDescriptor
* The property's descriptor.
* The class name applied to this property's target element.
*/
_init: function(aName = "", aDescriptor) {
this._idString = generateId(this._nameString = aName);
this._displayScope(aName, "variables-view-property variable-or-property");
this._displayVariable();
this._customizeVariable();
this._prepareTooltips();
this._setAttributes();
this._addEventListeners();
this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
},
/**
* Called when this property has finished initializing, and is ready to
* be attached to the owner view.
*
* @param boolean aImmediateFlag
* @see Scope.prototype._lazyAppend
*/
_onInit: function(aImmediateFlag) {
if (this._initialDescriptor.enumerable) {
this.ownerView._lazyAppend(aImmediateFlag, true, this._target);
this.ownerView._enumItems.push(this);
} else {
this.ownerView._lazyAppend(aImmediateFlag, false, this._target);
this.ownerView._nonEnumItems.push(this);
}
}
targetClassName: "variables-view-property variable-or-property"
});
/**
@ -3272,7 +3210,6 @@ let generateId = (function() {
};
})();
/**
* An Editable encapsulates the UI of an edit box that overlays a label,
* allowing the user to edit the value.
@ -3364,7 +3301,6 @@ Editable.prototype = {
this._variable.collapse();
this._variable.hideArrow();
this._variable.locked = true;
this._variable._stopThrobber();
},
/**
@ -3382,7 +3318,6 @@ Editable.prototype = {
this._variable.locked = false;
this._variable.twisty = this._prevExpandable;
this._variable.expanded = this._prevExpanded;
this._variable._stopThrobber();
},
/**
@ -3498,7 +3433,7 @@ EditableNameAndValue.create = Editable.create;
EditableNameAndValue.prototype = Heritage.extend(EditableName.prototype, {
_reset: function(e) {
// Hide the Varible or Property if the user presses escape.
// Hide the Variable or Property if the user presses escape.
this._variable.remove();
this.deactivate();
},

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

@ -64,7 +64,14 @@
display: none;
}
.variable-or-property[pseudo-item] > tooltip {
.variable-or-property[pseudo-item] > tooltip,
.variable-or-property[pseudo-item] > .title > .variables-view-edit,
.variable-or-property[pseudo-item] > .title > .variables-view-delete,
.variable-or-property[pseudo-item] > .title > .variables-view-add-property,
.variable-or-property[pseudo-item] > .title > .variable-or-property-frozen-label,
.variable-or-property[pseudo-item] > .title > .variable-or-property-sealed-label,
.variable-or-property[pseudo-item] > .title > .variable-or-property-non-extensible-label,
.variable-or-property[pseudo-item] > .title > .variable-or-property-non-writable-icon {
display: none;
}

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

@ -51,7 +51,7 @@
.ruleview-header {
vertical-align: middle;
height: 1.5em;
min-height: 1.5em;
line-height: 1.5em;
}

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

@ -796,7 +796,6 @@ function openDebugger(aOptions = {})
let panelWin = panel.panelWin;
panel._view.Variables.lazyEmpty = false;
panel._view.Variables.lazyAppend = false;
let resolveObject = {
target: target,

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

@ -3431,7 +3431,6 @@ JSTerm.prototype = {
view.emptyText = l10n.getStr("emptyPropertiesList");
view.searchEnabled = !aOptions.hideFilterInput;
view.lazyEmpty = this._lazyVariablesView;
view.lazyAppend = this._lazyVariablesView;
VariablesViewController.attach(view, {
getEnvironmentClient: aGrip => {

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

@ -379,6 +379,8 @@
@BINPATH@/components/Downloads.manifest
@BINPATH@/components/DownloadLegacy.js
@BINPATH@/components/BrowserPageThumbs.manifest
@BINPATH@/components/crashmonitor.manifest
@BINPATH@/components/nsCrashMonitor.js
@BINPATH@/components/SiteSpecificUserAgent.js
@BINPATH@/components/SiteSpecificUserAgent.manifest
@BINPATH@/components/toolkitsearch.manifest

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

@ -23,7 +23,7 @@
<!-- LOCALIZATION NOTE (remoteHelp, remoteDocumentation, remoteHelpSuffix):
these strings will be concatenated in a single label, remoteDocumentation will
be used as text for a link to MDN. -->
<!ENTITY remoteHelp "Firefox Developer Tools can debug remote devices (Firefox for Android and Firefox OS, for example). Make sure that you have turned on the 'Remote debugging' option in the remote device. For more, see the">
<!ENTITY remoteHelp "Firefox Developer Tools can debug remote devices (Firefox for Android and Firefox OS, for example). Make sure that you have turned on the 'Remote debugging' option in the remote device. For more, see the ">
<!ENTITY remoteDocumentation "documentation">
<!ENTITY remoteHelpSuffix ".">

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

@ -45,6 +45,22 @@
- button that toggles all breakpoints for all sources. -->
<!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
<!-- LOCALIZATION NOTE (debuggerUI.tracingNotStarted.label): This is the text
- displayed when tracing hasn't started in the debugger UI. -->
<!ENTITY debuggerUI.tracingNotStarted.label "Tracing has not started.">
<!-- LOCALIZATION NOTE (debuggerUI.startTracing): This is the text displayed in
- the button to start execution tracing. -->
<!ENTITY debuggerUI.startTracing "Start Tracing">
<!-- LOCALIZATION NOTE (debuggerUI.clearButton): This is the label for
- the button that clears the collected tracing data in the tracing tab. -->
<!ENTITY debuggerUI.clearButton "Clear">
<!-- LOCALIZATION NOTE (debuggerUI.clearButton.tooltip): This is the tooltip for
- the button that clears the collected tracing data in the tracing tab. -->
<!ENTITY debuggerUI.clearButton.tooltip "Clear the collected traces">
<!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
- checkbox that toggles pausing on exceptions. -->
<!ENTITY debuggerUI.pauseExceptions "Pause on exceptions">
@ -136,6 +152,7 @@
<!-- LOCALIZATION NOTE (debuggerUI.tabs.*): This is the text that
- appears in the debugger's side pane tabs. -->
<!ENTITY debuggerUI.tabs.sources "Sources">
<!ENTITY debuggerUI.tabs.traces "Traces">
<!ENTITY debuggerUI.tabs.callstack "Call Stack">
<!ENTITY debuggerUI.tabs.variables "Variables">
<!ENTITY debuggerUI.tabs.events "Events">

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

@ -49,6 +49,14 @@ pauseButtonTooltip=Click to pause (%S)
# button when the debugger is in a paused state.
resumeButtonTooltip=Click to resume (%S)
# LOCALIZATION NOTE (startTracingTooltip): The label that is displayed on the trace
# button when execution tracing is stopped.
startTracingTooltip=Click to start tracing
# LOCALIZATION NOTE (stopTracingTooltip): The label that is displayed on the trace
# button when execution tracing is started.
stopTracingTooltip=Click to stop tracing
# LOCALIZATION NOTE (stepOverTooltip): The label that is displayed on the
# button that steps over a function call.
stepOverTooltip=Step Over (%S)

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

@ -15,6 +15,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
"@mozilla.org/xre/app-info;1", "nsICrashReporter");
#endif
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
"@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
Cu.import("resource://gre/modules/NetUtil.jsm");
return NetUtil;
@ -39,6 +42,8 @@ SessionStore.prototype = {
Ci.nsISupportsWeakReference]),
_windows: {},
_selectedWindow: 1,
_orderedWindows: [],
_lastSaveTime: 0,
_lastSessionTime: 0,
_interval: 10000,
@ -284,8 +289,8 @@ SessionStore.prototype = {
if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState == STATE_QUITTING)
return;
// Assign it a unique identifier (timestamp) and create its data object
aWindow.__SSID = "window" + Date.now();
// Assign it a unique identifier and create its data object
aWindow.__SSID = "window" + gUUIDGenerator.generateUUID().toString();
this._windows[aWindow.__SSID] = { tabs: [], selected: 0, _closedTabs: [] };
// Perform additional initialization when the first window is loading
@ -472,9 +477,9 @@ SessionStore.prototype = {
});
let data = { windows: [] };
let index;
for (index in this._windows)
data.windows.push(this._windows[index]);
for (let i = 0; i < this._orderedWindows.length; i++)
data.windows.push(this._windows[this._orderedWindows[i]]);
data.selectedWindow = this._selectedWindow;
return data;
},
@ -724,11 +729,24 @@ SessionStore.prototype = {
let window = Services.wm.getMostRecentWindow("navigator:browser");
let tabs = data.windows[0].tabs;
let selected = data.windows[0].selected;
this._selectedWindow = data.selectedWindow;
let windowIndex = this._selectedWindow - 1;
let tabs = data.windows[windowIndex].tabs;
let selected = data.windows[windowIndex].selected;
if (data.windows[0]._closedTabs)
this._windows[window.__SSID]._closedTabs = data.windows[0]._closedTabs;
// Move all window data from sessionstore.js to this._windows.
for (let i = 0; i < data.windows.length; i++) {
let SSID;
if (i != windowIndex) {
SSID = "window" + gUUIDGenerator.generateUUID().toString();
this._windows[SSID] = data.windows[i];
} else {
SSID = window.__SSID;
this._windows[SSID]._closedTabs =
this._windows[SSID]._closedTabs.concat(data.windows[windowIndex]._closedTabs);
}
this._orderedWindows.push(SSID);
}
if (selected > tabs.length) // Clamp the selected index if it's bogus
selected = 1;

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

@ -6,7 +6,7 @@
this.EXPORTED_SYMBOLS = ["BrowserUITelemetry"];
const Cu = Components.utils;
const {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -18,6 +18,88 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyGetter(this, "DEFAULT_TOOLBAR_PLACEMENTS", function() {
let result = {
"PanelUI-contents": [
"edit-controls",
"zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
],
"nav-bar": [
"urlbar-container",
"search-container",
"webrtc-status-button",
"bookmarks-menu-button",
"downloads-button",
"home-button",
"social-share-button",
],
// It's true that toolbar-menubar is not visible
// on OS X, but the XUL node is definitely present
// in the document.
"toolbar-menubar": [
"menubar-items",
],
"TabsToolbar": [
"tabbrowser-tabs",
"new-tab-button",
"alltabs-button",
"tabs-closebutton",
],
"PersonalToolbar": [
"personal-bookmarks",
],
};
let showCharacterEncoding = Services.prefs.getComplexValue(
"browser.menu.showCharacterEncoding",
Ci.nsIPrefLocalizedString
).data;
if (showCharacterEncoding == "true") {
result["PanelUI-contents"].push("characterencoding-button");
}
if (Services.sysinfo.getProperty("hasWindowsTouchInterface")) {
result["PanelUI-contents"].push("switch-to-metro-button");
}
return result;
});
XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() {
let result = [
"open-file-button",
"developer-button",
"feed-button",
"email-link-button",
"sync-button",
"tabview-button",
];
let panelPlacements = DEFAULT_TOOLBAR_PLACEMENTS["PanelUI-contents"];
if (panelPlacements.indexOf("characterencoding-button") == -1) {
result.push("characterencoding-button");
}
return result;
});
XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() {
let result = [];
for (let [, buttons] of Iterator(DEFAULT_TOOLBAR_PLACEMENTS)) {
result = result.concat(buttons);
}
return result;
});
const ALL_BUILTIN_ITEMS = [
"fullscreen-button",
"switch-to-metro-button",
@ -249,6 +331,48 @@ this.BrowserUITelemetry = {
let bookmarksBar = document.getElementById("PersonalToolbar");
result.bookmarksBarEnabled = bookmarksBar && !bookmarksBar.collapsed;
// Examine all customizable areas and see what default items
// are present and missing.
let defaultKept = [];
let defaultMoved = [];
let nondefaultAdded = [];
for (let areaID of CustomizableUI.areas) {
let items = CustomizableUI.getWidgetIdsInArea(areaID);
for (let item of items) {
// Is this a default item?
if (DEFAULT_ITEMS.indexOf(item) != -1) {
// Ok, it's a default item - but is it in its default
// toolbar? We use Array.isArray instead of checking for
// toolbarID in DEFAULT_TOOLBAR_PLACEMENTS because an add-on might
// be clever and give itself the id of "toString" or something.
if (Array.isArray(DEFAULT_TOOLBAR_PLACEMENTS[areaID]) &&
DEFAULT_TOOLBAR_PLACEMENTS[areaID].indexOf(item) != -1) {
// The item is in its default toolbar
defaultKept.push(item);
} else {
defaultMoved.push(item);
}
} else if (PALETTE_ITEMS.indexOf(item) != -1) {
// It's a palette item that's been moved into a toolbar
nondefaultAdded.push(item);
}
// else, it's provided by an add-on, and we won't record it.
}
}
// Now go through the items in the palette to see what default
// items are in there.
let paletteItems =
CustomizableUI.getUnusedWidgets(win.gNavToolbox.palette);
let defaultRemoved = [item.id for (item of paletteItems)
if (DEFAULT_ITEMS.indexOf(item.id) != -1)];
result.defaultKept = defaultKept;
result.defaultMoved = defaultMoved;
result.nondefaultAdded = nondefaultAdded;
result.defaultRemoved = defaultRemoved;
result.countableEvents = this._countableEvents;
return result;

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

@ -26,7 +26,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "unescapeService",
"@mozilla.org/feed-unescapehtml;1",
@ -98,10 +98,6 @@ this.Social = {
providers: [],
_disabledForSafeMode: false,
get allowMultipleWorkers() {
return Services.prefs.getBoolPref("social.allowMultipleWorkers");
},
get _currentProviderPref() {
try {
return Services.prefs.getComplexValue("social.provider.current",
@ -130,11 +126,6 @@ this.Social = {
if (this._provider == provider)
return;
// Disable the previous provider, if we are not allowing multiple workers,
// since we want only one provider to be enabled at once.
if (this._provider && !Social.allowMultipleWorkers)
this._provider.enabled = false;
this._provider = provider;
if (this._provider) {
@ -208,10 +199,6 @@ this.Social = {
},
_updateWorkerState: function(enable) {
// ensure that our providers are all disabled, and enabled if we allow
// multiple workers
if (enable && !Social.allowMultipleWorkers)
return;
[p.enabled = enable for (p of Social.providers) if (p.enabled != enable)];
},

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

@ -4,10 +4,6 @@
function run_test() {
// we are testing worker startup specifically
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
do_register_cleanup(function() {
Services.prefs.clearUserPref("social.allowMultipleWorkers");
});
do_test_pending();
add_test(testStartupEnabled);
add_test(testDisableAfterStartup);

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

@ -4,10 +4,6 @@
function run_test() {
// we are testing worker startup specifically
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
do_register_cleanup(function() {
Services.prefs.clearUserPref("social.allowMultipleWorkers");
});
do_test_pending();
add_test(testStartupDisabled);
add_test(testEnableAfterStartup);

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

@ -93,6 +93,119 @@
padding: .25em;
}
/* Tracer */
#trace {
list-style-image: url(tracer-icon.png);
-moz-image-region: rect(0px,16px,16px,0px);
}
#trace[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}
#start-tracing {
padding: 4px;
margin: 4px;
}
#clear-tracer {
min-width: 22px !important;
}
#tracer-search {
min-width: 72px !important;
}
#tracer-message {
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
}
.trace-name {
-moz-padding-start: 4px !important;
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
}
.theme-dark #tracer-traces > scrollbox {
background-color: #181d20 !important; /* Content background sidebar */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
.trace-item.selected-matching {
background-color: #1d4f73; /* Select highlight blue */
}
.theme-dark .trace-call {
color: #46afe3; /* highlight blue */
}
.theme-dark .trace-return,
.theme-dark .trace-yield {
color: #70bf53; /* highlight green */
}
.theme-dark .trace-throw {
color: #eb5368; /* highlight red */
}
.theme-dark .trace-param {
color: #8fa1b2; /* Content text grey */
}
.theme-dark .trace-syntax {
color: #5e88b0; /* highlight blue-grey */
}
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
}
.theme-light #tracer-traces > scrollbox {
background-color: #f7f7f7 !important; /* Content background sidebar */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}
.trace-item.selected-matching {
background-color: #4c9ed9; /* Select highlight blue */
}
.theme-light .trace-call {
color: #0088cc; /* highlight blue */
}
.theme-light .trace-return,
.theme-light .trace-yield {
color: #2cbb0f; /* highlight green */
}
.theme-light .trace-throw {
color: #ed2655; /* highlight red */
}
.theme-light .trace-param {
color: #8fa1b2; /* Content text grey */
}
.theme-light .trace-syntax {
color: #5f88b0; /* highlight blue-grey */
}
/* ListWidget items */
.list-widget-item {
@ -226,11 +339,6 @@
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
min-height: 25px !important;
padding: 0 !important;
}
#instruments-pane .side-menu-widget-container,
#instruments-pane .side-menu-widget-empty-notice-container {
box-shadow: none !important;

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

@ -22,6 +22,7 @@
padding: 1px 4px;
margin-top: 4px;
-moz-user-select: none;
word-wrap: break-word;
}
.ruleview-rule-source:hover {
@ -115,6 +116,7 @@
.ruleview-property {
border-left: 2px solid transparent;
clear: right;
}
.ruleview-property > * {
@ -135,6 +137,10 @@
border-bottom-color: hsl(0,0%,50%);
}
.ruleview-selector {
word-wrap: break-word;
}
.ruleview-selector-separator, .ruleview-selector-unmatched {
color: #888;
}

Двоичные данные
browser/themes/linux/devtools/tracer-icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 709 B

Двоичные данные
browser/themes/linux/devtools/tracer-icon@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

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

@ -451,18 +451,8 @@
color: #fff;
}
.variables-view-scope > .variables-view-element-details:not(:empty) {
-moz-margin-start: 2px;
-moz-margin-end: 1px;
}
/* Generic variables traits */
.variables-view-variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid rgba(128, 128, 128, .15);
}
@ -481,8 +471,12 @@
-moz-box-flex: 1;
}
.variable-or-property > .title > .arrow {
-moz-margin-start: 3px;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
-moz-margin-start: 7px;
}
/* Traits applied when variables or properties are changed or overridden */

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 556 B

Двоичные данные
browser/themes/linux/downloads/download-glow.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 723 B

После

Ширина:  |  Высота:  |  Размер: 3.1 KiB

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

@ -77,7 +77,6 @@ browser.jar:
skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png)
skin/classic/browser/downloads/download-glow-small.png (downloads/download-glow-small.png)
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
@ -222,6 +221,8 @@ browser.jar:
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/browser/devtools/tracer-icon.png (devtools/tracer-icon.png)
skin/classic/browser/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)

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

@ -95,6 +95,119 @@
padding: .25em;
}
/* Tracer */
#trace {
list-style-image: url(tracer-icon.png);
-moz-image-region: rect(0px,16px,16px,0px);
}
#trace[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}
#start-tracing {
padding: 4px;
margin: 4px;
}
#clear-tracer {
min-width: 22px !important;
}
#tracer-search {
min-width: 72px !important;
}
#tracer-message {
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
}
.trace-name {
-moz-padding-start: 4px !important;
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
}
.theme-dark #tracer-traces > scrollbox {
background-color: #181d20 !important; /* Content background sidebar */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
.trace-item.selected-matching {
background-color: #1d4f73; /* Select highlight blue */
}
.theme-dark .trace-call {
color: #46afe3; /* highlight blue */
}
.theme-dark .trace-return,
.theme-dark .trace-yield {
color: #70bf53; /* highlight green */
}
.theme-dark .trace-throw {
color: #eb5368; /* highlight red */
}
.theme-dark .trace-param {
color: #8fa1b2; /* Content text grey */
}
.theme-dark .trace-syntax {
color: #5e88b0; /* highlight blue-grey */
}
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
}
.theme-light #tracer-traces > scrollbox {
background-color: #f7f7f7 !important; /* Content background sidebar */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}
.trace-item.selected-matching {
background-color: #4c9ed9; /* Select highlight blue */
}
.theme-light .trace-call {
color: #0088cc; /* highlight blue */
}
.theme-light .trace-return,
.theme-light .trace-yield {
color: #2cbb0f; /* highlight green */
}
.theme-light .trace-throw {
color: #ed2655; /* highlight red */
}
.theme-light .trace-param {
color: #8fa1b2; /* Content text grey */
}
.theme-light .trace-syntax {
color: #5f88b0; /* highlight blue-grey */
}
/* ListWidget items */
.list-widget-item {
@ -228,11 +341,6 @@
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
min-height: 1em !important;
padding: 0 !important;
}
#instruments-pane .side-menu-widget-container,
#instruments-pane .side-menu-widget-empty-notice-container {
box-shadow: none !important;

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

@ -21,6 +21,7 @@
border-bottom-style: solid;
padding: 1px 4px;
-moz-user-select: none;
word-wrap: break-word;
}
.ruleview-rule-pseudo-element {
@ -119,6 +120,7 @@
.ruleview-property {
border-left: 2px solid transparent;
clear: right;
}
.ruleview-property > * {
@ -139,7 +141,10 @@
border-bottom-color: hsl(0,0%,50%);
}
.ruleview-selector {
word-wrap: break-word;
}
.ruleview-selector-separator, .ruleview-selector-unmatched {
color: #888;
}

Двоичные данные
browser/themes/osx/devtools/tracer-icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 709 B

Двоичные данные
browser/themes/osx/devtools/tracer-icon@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

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

@ -445,18 +445,8 @@
color: #fff;
}
.variables-view-scope > .variables-view-element-details:not(:empty) {
-moz-margin-start: 2px;
-moz-margin-end: 1px;
}
/* Generic variables traits */
.variables-view-variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid rgba(128, 128, 128, .15);
}
@ -475,8 +465,12 @@
-moz-box-flex: 1;
}
.variable-or-property > .title > .arrow {
-moz-margin-start: 3px;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
-moz-margin-start: 7px;
}
/* Traits applied when variables or properties are changed or overridden */

Двоичные данные
browser/themes/osx/downloads/download-glow.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 570 B

После

Ширина:  |  Высота:  |  Размер: 676 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.1 KiB

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

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

@ -324,6 +324,8 @@ browser.jar:
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/browser/devtools/tracer-icon.png (devtools/tracer-icon.png)
skin/classic/browser/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)

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

@ -850,7 +850,7 @@ menuitem.bookmark-item {
background-color: rgba(255,255,255,.9);
}
#urlbar:-moz-lwtheme[focused],
#urlbar:-moz-lwtheme[focused]:not([readonly]),
.searchbar-textbox:-moz-lwtheme[focused] {
background-color: white;
}

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

@ -93,6 +93,119 @@
padding: .25em;
}
/* Tracer */
#trace {
list-style-image: url(tracer-icon.png);
-moz-image-region: rect(0px,16px,16px,0px);
}
#trace[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}
#start-tracing {
padding: 4px;
margin: 4px;
}
#clear-tracer {
min-width: 22px !important;
}
#tracer-search {
min-width: 72px !important;
}
#tracer-message {
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
}
.trace-name {
-moz-padding-start: 4px !important;
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
}
.theme-dark #tracer-traces > scrollbox {
background-color: #181d20 !important; /* Content background sidebar */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
.trace-item.selected-matching {
background-color: #1d4f73; /* Select highlight blue */
}
.theme-dark .trace-call {
color: #46afe3; /* highlight blue */
}
.theme-dark .trace-return,
.theme-dark .trace-yield {
color: #70bf53; /* highlight green */
}
.theme-dark .trace-throw {
color: #eb5368; /* highlight red */
}
.theme-dark .trace-param {
color: #8fa1b2; /* Content text grey */
}
.theme-dark .trace-syntax {
color: #5e88b0; /* highlight blue-grey */
}
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
}
.theme-light #tracer-traces > scrollbox {
background-color: #f7f7f7 !important; /* Content background sidebar */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}
.trace-item.selected-matching {
background-color: #4c9ed9; /* Select highlight blue */
}
.theme-light .trace-call {
color: #0088cc; /* highlight blue */
}
.theme-light .trace-return,
.theme-light .trace-yield {
color: #2cbb0f; /* highlight green */
}
.theme-light .trace-throw {
color: #ed2655; /* highlight red */
}
.theme-light .trace-param {
color: #8fa1b2; /* Content text grey */
}
.theme-light .trace-syntax {
color: #5f88b0; /* highlight blue-grey */
}
/* ListWidget items */
.list-widget-item {
@ -226,11 +339,6 @@
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
min-height: 25px !important;
padding: 0 !important;
}
#instruments-pane .side-menu-widget-container,
#instruments-pane .side-menu-widget-empty-notice-container {
box-shadow: none !important;

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

@ -22,6 +22,7 @@
padding: 1px 4px;
margin-top: 4px;
-moz-user-select: none;
word-wrap: break-word;
}
.ruleview-rule-source:hover {
@ -115,6 +116,7 @@
.ruleview-property {
border-left: 2px solid transparent;
clear: right;
}
.ruleview-property > * {
@ -135,6 +137,10 @@
border-bottom-color: hsl(0,0%,50%);
}
.ruleview-selector {
word-wrap: break-word;
}
.ruleview-selector-separator, .ruleview-selector-unmatched {
color: #888;
}

Двоичные данные
browser/themes/windows/devtools/tracer-icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 709 B

Двоичные данные
browser/themes/windows/devtools/tracer-icon@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

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

@ -448,18 +448,8 @@
color: #fff;
}
.variables-view-scope > .variables-view-element-details:not(:empty) {
-moz-margin-start: 2px;
-moz-margin-end: 1px;
}
/* Generic variables traits */
.variables-view-variable {
-moz-margin-start: 1px;
-moz-margin-end: 1px;
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid rgba(128, 128, 128, .15);
}
@ -478,8 +468,12 @@
-moz-box-flex: 1;
}
.variable-or-property > .title > .arrow {
-moz-margin-start: 3px;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
-moz-margin-start: 7px;
}
/* Traits applied when variables or properties are changed or overridden */

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

@ -249,6 +249,8 @@ browser.jar:
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/browser/devtools/tracer-icon.png (devtools/tracer-icon.png)
skin/classic/browser/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
@ -557,6 +559,8 @@ browser.jar:
skin/classic/aero/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/aero/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/aero/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/aero/devtools/tracer-icon.png (devtools/tracer-icon.png)
skin/classic/aero/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
skin/classic/aero/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/aero/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)

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

@ -21,8 +21,8 @@ public class SiteIdentity {
// levels in site_security_level.xml
public enum SecurityMode {
UNKNOWN("unknown"),
VERIFIED("verified"),
IDENTIFIED("identified"),
VERIFIED("verified"),
MIXED_CONTENT_BLOCKED("mixed_content_blocked"),
MIXED_CONTENT_LOADED("mixed_content_loaded");

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

@ -103,9 +103,8 @@ public final class WaitHelper {
}
}, CHANGE_WAIT_MS);
if (hasTimedOut) {
sContext.dumpLog(verifier.getClass().getName() + " timed out.");
}
sContext.dumpLog(verifier.getLogTag() +
(hasTimedOut ? "timed out." : "was satisfied."));
}
}
@ -115,6 +114,8 @@ public final class WaitHelper {
* returned from hasStateChanged, indicating this change of status.
*/
private static interface ChangeVerifier {
public String getLogTag();
/**
* Stores the initial state of the system. This system state is used to diff against
* the end state to determine if the system has changed. Since this is just a diff
@ -126,14 +127,23 @@ public final class WaitHelper {
}
private static class ToolbarTitleTextChangeVerifier implements ChangeVerifier {
private static final String LOGTAG =
ToolbarTitleTextChangeVerifier.class.getSimpleName() + ": ";
// A regex that matches the page title that shows up while the page is loading.
private static final String LOADING_REGEX = "^[A-Za-z]{3,9}://";
private CharSequence oldTitleText;
private CharSequence mOldTitleText;
@Override
public String getLogTag() {
return LOGTAG;
}
@Override
public void storeState() {
oldTitleText = sToolbar.getPotentiallyInconsistentTitle();
mOldTitleText = sToolbar.getPotentiallyInconsistentTitle();
sContext.dumpLog(LOGTAG + "stored title, \"" + mOldTitleText + "\".");
}
@Override
@ -148,7 +158,12 @@ public final class WaitHelper {
// loaded from the server and set as the final page title; we ignore the
// intermediate URL loading state here.
final boolean isLoading = title.toString().matches(LOADING_REGEX);
return !isLoading && !oldTitleText.equals(title);
final boolean hasStateChanged = !isLoading && !mOldTitleText.equals(title);
if (hasStateChanged) {
sContext.dumpLog(LOGTAG + "state changed to title, \"" + title + "\".");
}
return hasStateChanged;
}
}
}

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

@ -23,15 +23,17 @@ XPCOMUtils.defineLazyGetter(window, "gChromeWin", function()
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow));
document.addEventListener("DOMContentLoaded", onLoad, false);
var AppsUI = {
uninstall: null,
shortcut: null
};
function openLink(aElement) {
function openLink(aEvent) {
try {
let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
let url = formatter.formatURLPref(aElement.getAttribute("pref"));
let url = formatter.formatURLPref(aEvent.target.getAttribute("pref"));
let BrowserApp = gChromeWin.BrowserApp;
BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id });
} catch (ex) {}
@ -41,7 +43,9 @@ var ContextMenus = {
target: null,
init: function() {
document.addEventListener("contextmenu", ContextMenus, false);
document.addEventListener("contextmenu", this, false);
document.getElementById("addToHomescreenLabel").addEventListener("click", this.addToHomescreen, false);
document.getElementById("uninstallLabel").addEventListener("click", this.uninstall, false);
},
handleEvent: function(event) {
@ -82,6 +86,11 @@ function onLoad(aEvent) {
link.setAttribute("href", url);
} catch (e) {}
let elmts = document.querySelectorAll("[pref]");
for (let i = 0; i < elmts.length; i++) {
elmts[i].addEventListener("click", openLink, false);
}
navigator.mozApps.mgmt.oninstall = onInstall;
navigator.mozApps.mgmt.onuninstall = onUninstall;
updateList();
@ -155,3 +164,4 @@ function onUninstall(aEvent) {
document.getElementById("main-container").classList.add("hidden");
}
}

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

@ -27,16 +27,16 @@
<script type="text/javascript;version=1.8" src="chrome://browser/content/aboutApps.js"></script>
</head>
<body dir="&locale.dir;" onload="onLoad(event)">
<body dir="&locale.dir;">
<menu type="context" id="appmenu">
<menuitem label="&aboutApps.addToHomescreen;" onclick="ContextMenus.addToHomescreen();"></menuitem>
<menuitem label="&aboutApps.uninstall;" onclick="ContextMenus.uninstall();"></menuitem>
<menuitem id="addToHomescreenLabel" label="&aboutApps.addToHomescreen;"></menuitem>
<menuitem id="uninstallLabel" label="&aboutApps.uninstall;"></menuitem>
</menu>
<div class="header">
<div>&aboutApps.header;</div>
<div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL" onclick="openLink(this);"/>
<div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL"/>
</div>
<div id="main-container" class="hidden">
@ -47,7 +47,7 @@
</div>
</div>
<div class="list-item" role="button" pref="app.marketplaceURL" onclick="openLink(this);">
<div class="list-item" role="button" pref="app.marketplaceURL">
<img class="icon" src="chrome://browser/skin/images/marketplace-logo.png" />
<div class="inner">
<div id="browse-title" class="title">&aboutApps.browseMarketplace;</div>

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

@ -144,7 +144,7 @@ InternalMethods.prototype = {
}
if (!this.whenKeysReadyPromise) {
this.whenKeysReadyPromise = Promise.defer();
this.fetchAndUnwrapKeys(data.keyFetchToken)
return this.fetchAndUnwrapKeys(data.keyFetchToken)
.then((data) => {
if (this.whenKeysReadyPromise) {
this.whenKeysReadyPromise.resolve(data);

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

@ -262,7 +262,8 @@ add_test(function test_polling_timeout() {
});
});
add_task(function test_getKeys() {
add_test(function test_getKeys() {
do_test_pending();
let fxa = new MockFxAccounts();
let user = getTestUser("eusebius");
@ -272,19 +273,21 @@ add_task(function test_getKeys() {
fxa.setSignedInUser(user).then(() => {
fxa.getSignedInUser().then((user) => {
// Before getKeys, we have no keys
do_check_eq(!!data.kA, false);
do_check_eq(!!data.kB, false);
do_check_eq(!!user.kA, false);
do_check_eq(!!user.kB, false);
// And we still have a key-fetch token to use
do_check_eq(!!data.keyFetchToken, true);
do_check_eq(!!user.keyFetchToken, true);
fxa.internal.getKeys().then(() => {
fxa.getSignedInUser().then((user) => {
// Now we should have keys
do_check_eq(fxa.internal.isUserEmailVerified(data), true);
do_check_eq(!!data.isVerified, true);
do_check_eq(data.kA, expandHex("11"));
do_check_eq(data.kB, expandHex("66"));
do_check_eq(data.keyFetchToken, undefined);
do_check_eq(fxa.internal.isUserEmailVerified(user), true);
do_check_eq(!!user.isVerified, true);
do_check_eq(user.kA, expandHex("11"));
do_check_eq(user.kB, expandHex("66"));
do_check_eq(user.keyFetchToken, undefined);
do_test_finished();
run_next_test();
});
});
});

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

@ -0,0 +1,208 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* Crash Monitor
*
* Monitors execution of a program to detect possible crashes. After
* program termination, the monitor can be queried during the next run
* to determine whether the last run exited cleanly or not.
*
* The monitoring is done by registering and listening for special
* notifications, or checkpoints, known to be sent by the monitored
* program as different stages in the execution are reached. As they
* are observed, these notifications are written asynchronously to a
* checkpoint file.
*
* During next program startup the crash monitor reads the checkpoint
* file from the last session. If notifications are missing, a crash
* has likely happened. By inspecting the notifications present, it is
* possible to determine what stages were reached in the program
* before the crash.
*
* Note that since the file is written asynchronously it is possible
* that a received notification is lost if the program crashes right
* after a checkpoint, but before crash monitor has been able to write
* it to disk. Thus, while the presence of a notification in the
* checkpoint file tells us that the corresponding stage was reached
* during the last run, the absence of a notification after a crash
* does not necessarily tell us that the checkpoint wasn't reached.
*/
this.EXPORTED_SYMBOLS = [ "CrashMonitor" ];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
const NOTIFICATIONS = [
"final-ui-startup",
"sessionstore-windows-restored",
"quit-application-granted",
"quit-application",
"profile-change-net-teardown",
"profile-change-teardown",
"profile-before-change",
"sessionstore-final-state-write-complete"
];
let CrashMonitorInternal = {
/**
* Notifications received during the current session.
*
* Object where a property with a value of |true| means that the
* notification of the same name has been received at least once by
* the CrashMonitor during this session. Notifications that have not
* yet been received are not present as properties. |NOTIFICATIONS|
* lists the notifications tracked by the CrashMonitor.
*/
checkpoints: {},
/**
* Notifications received during previous session.
*
* Available after |loadPreviousCheckpoints|. Promise which resolves
* to an object containing a set of properties, where a property
* with a value of |true| means that the notification with the same
* name as the property name was received at least once last
* session.
*/
previousCheckpoints: null,
/* Deferred for AsyncShutdown blocker */
profileBeforeChangeDeferred: Promise.defer(),
/**
* Path to checkpoint file.
*
* Each time a new notification is received, this file is written to
* disc to reflect the information in |checkpoints|.
*/
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
/**
* Load checkpoints from previous session asynchronously.
*
* @return {Promise} A promise that resolves/rejects once loading is complete
*/
loadPreviousCheckpoints: function () {
let promise = Task.spawn(function () {
let notifications;
try {
let decoder = new TextDecoder();
let data = yield OS.File.read(CrashMonitorInternal.path);
let contents = decoder.decode(data);
notifications = JSON.parse(contents);
} catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
// If checkpoint file cannot be read
throw new Task.Result(null);
} catch (ex) {
Cu.reportError("Error while loading crash monitor data: " + ex);
throw new Task.Result(null);
}
throw new Task.Result(Object.freeze(notifications));
});
CrashMonitorInternal.previousCheckpoints = promise;
return promise;
}
};
this.CrashMonitor = {
/**
* Notifications received during previous session.
*
* Return object containing the set of notifications received last
* session as keys with values set to |true|.
*
* @return {Promise} A promise resolving to previous checkpoints
*/
get previousCheckpoints() {
if (!CrashMonitorInternal.initialized) {
throw new Error("CrashMonitor must be initialized before getting previous checkpoints");
}
return CrashMonitorInternal.previousCheckpoints
},
/**
* Initialize CrashMonitor.
*
* Should only be called from the CrashMonitor XPCOM component.
*
* @return {Promise}
*/
init: function () {
if (CrashMonitorInternal.initialized) {
throw new Error("CrashMonitor.init() must only be called once!");
}
let promise = CrashMonitorInternal.loadPreviousCheckpoints();
// Add "profile-after-change" to checkpoint as this method is
// called after receiving it
CrashMonitorInternal.checkpoints["profile-after-change"] = true;
NOTIFICATIONS.forEach(function (aTopic) {
Services.obs.addObserver(this, aTopic, false);
}, this);
// Add shutdown blocker for profile-before-change
AsyncShutdown.profileBeforeChange.addBlocker(
"CrashMonitor: Writing notifications to file after receiving profile-before-change",
CrashMonitorInternal.profileBeforeChangeDeferred.promise
);
CrashMonitorInternal.initialized = true;
return promise;
},
/**
* Handle registered notifications.
*
* Update checkpoint file for every new notification received.
*/
observe: function (aSubject, aTopic, aData) {
if (!(aTopic in CrashMonitorInternal.checkpoints)) {
// If this is the first time this notification is received,
// remember it and write it to file
CrashMonitorInternal.checkpoints[aTopic] = true;
Task.spawn(function() {
try {
let data = JSON.stringify(CrashMonitorInternal.checkpoints);
/* Write to the checkpoint file asynchronously, off the main
* thread, for performance reasons. Note that this means
* that there's not a 100% guarantee that the file will be
* written by the time the notification completes. The
* exception is profile-before-change which has a shutdown
* blocker. */
yield OS.File.writeAtomic(
CrashMonitorInternal.path,
data, {tmpPath: CrashMonitorInternal.path + ".tmp"});
} finally {
// Resolve promise for blocker
if (aTopic == "profile-before-change") {
CrashMonitorInternal.profileBeforeChangeDeferred.resolve();
}
}
});
}
if (NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints)) {
// All notifications received, unregister observers
NOTIFICATIONS.forEach(function (aTopic) {
Services.obs.removeObserver(this, aTopic);
}, this);
}
}
};
Object.freeze(this.CrashMonitor);

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

@ -0,0 +1,3 @@
component {d9d75e86-8f17-4c57-993e-f738f0d86d42} nsCrashMonitor.js
contract @mozilla.org/toolkit/crashmonitor;1 {d9d75e86-8f17-4c57-993e-f738f0d86d42}
category profile-after-change CrashMonitor @mozilla.org/toolkit/crashmonitor;1

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

@ -0,0 +1,17 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_JS_MODULES = [
'CrashMonitor.jsm',
]
EXTRA_COMPONENTS += [
'crashmonitor.manifest',
]
EXTRA_PP_COMPONENTS += [
'nsCrashMonitor.js',
]

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

@ -0,0 +1,29 @@
/* 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/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
let Scope = {}
Components.utils.import("resource://gre/modules/CrashMonitor.jsm", Scope);
let MonitorAPI = Scope.CrashMonitor;
function CrashMonitor() {};
CrashMonitor.prototype = {
classID: Components.ID("{d9d75e86-8f17-4c57-993e-f738f0d86d42}"),
contractID: "@mozilla.org/toolkit/crashmonitor;1",
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "profile-after-change":
MonitorAPI.init();
}
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashMonitor]);

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