diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 801ccf845f7..719661b84f9 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -820,6 +820,8 @@ pref("browser.sessionstore.resume_session_once", false); // minimal interval between two save operations in milliseconds pref("browser.sessionstore.interval", 15000); +// interval when device is unplugged +pref("browser.sessionstore.interval_battery", 60000); // maximum amount of POSTDATA to be saved in bytes per history entry (-1 = all of it) // (NB: POSTDATA will be saved either entirely or not at all) pref("browser.sessionstore.postdata", 0); diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index 3d34c50384f..0bef4cc2a06 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -480,9 +480,6 @@ function HistoryMenu(aPopupShowingEvent) { // Defining the prototype inheritance in the prototype itself would cause // browser.js to halt on "PlacesMenu is not defined" error. this.__proto__.__proto__ = PlacesMenu.prototype; - XPCOMUtils.defineLazyServiceGetter(this, "_ss", - "@mozilla.org/browser/sessionstore;1", - "nsISessionStore"); PlacesMenu.call(this, aPopupShowingEvent, "place:sort=4&maxResults=15"); } @@ -493,7 +490,7 @@ HistoryMenu.prototype = { var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0]; // no restorable tabs, so disable menu - if (this._ss.getClosedTabCount(window) == 0) + if (SessionStore.getClosedTabCount(window) == 0) undoMenu.setAttribute("disabled", true); else undoMenu.removeAttribute("disabled"); @@ -525,7 +522,7 @@ HistoryMenu.prototype = { undoPopup.removeChild(undoPopup.firstChild); // no restorable tabs, so make sure menu is disabled, and return - if (this._ss.getClosedTabCount(window) == 0) { + if (SessionStore.getClosedTabCount(window) == 0) { undoMenu.setAttribute("disabled", true); return; } @@ -534,7 +531,7 @@ HistoryMenu.prototype = { undoMenu.removeAttribute("disabled"); // populate menu - var undoItems = JSON.parse(this._ss.getClosedTabData(window)); + var undoItems = JSON.parse(SessionStore.getClosedTabData(window)); for (var i = 0; i < undoItems.length; i++) { var m = document.createElement("menuitem"); m.setAttribute("label", undoItems[i].title); @@ -580,7 +577,7 @@ HistoryMenu.prototype = { var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0]; // no restorable windows, so disable menu - if (this._ss.getClosedWindowCount() == 0) + if (SessionStore.getClosedWindowCount() == 0) undoMenu.setAttribute("disabled", true); else undoMenu.removeAttribute("disabled"); @@ -601,7 +598,7 @@ HistoryMenu.prototype = { undoPopup.removeChild(undoPopup.firstChild); // no restorable windows, so make sure menu is disabled, and return - if (this._ss.getClosedWindowCount() == 0) { + if (SessionStore.getClosedWindowCount() == 0) { undoMenu.setAttribute("disabled", true); return; } @@ -610,7 +607,7 @@ HistoryMenu.prototype = { undoMenu.removeAttribute("disabled"); // populate menu - let undoItems = JSON.parse(this._ss.getClosedWindowData()); + let undoItems = JSON.parse(SessionStore.getClosedWindowData()); for (let i = 0; i < undoItems.length; i++) { let undoItem = undoItems[i]; let otherTabsCount = undoItem.tabs.length - 1; diff --git a/browser/base/content/browser-tabview.js b/browser/base/content/browser-tabview.js index f6ad77999f2..b168313452e 100644 --- a/browser/base/content/browser-tabview.js +++ b/browser/base/content/browser-tabview.js @@ -67,15 +67,13 @@ let TabView = { if (this.firstUseExperienced) { // ___ visibility - let sessionstore = - Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); - let data = sessionstore.getWindowValue(window, this.VISIBILITY_IDENTIFIER); + let data = SessionStore.getWindowValue(window, this.VISIBILITY_IDENTIFIER); if (data && data == "true") { this.show(); } else { try { - data = sessionstore.getWindowValue(window, this.GROUPS_IDENTIFIER); + data = SessionStore.getWindowValue(window, this.GROUPS_IDENTIFIER); if (data) { let parsedData = JSON.parse(data); this.updateGroupNumberBroadcaster(parsedData.totalNumber || 1); diff --git a/browser/base/content/socialchat.xml b/browser/base/content/socialchat.xml index c61d932c264..c341eab4e79 100644 --- a/browser/base/content/socialchat.xml +++ b/browser/base/content/socialchat.xml @@ -620,7 +620,7 @@ diff --git a/browser/base/content/test/browser_bug580956.js b/browser/base/content/test/browser_bug580956.js index f7cc3c3fe1b..e7573b0f132 100644 --- a/browser/base/content/test/browser_bug580956.js +++ b/browser/base/content/test/browser_bug580956.js @@ -1,7 +1,5 @@ function numClosedTabs() - Cc["@mozilla.org/browser/sessionstore;1"]. - getService(Ci.nsISessionStore). - getClosedTabCount(window); + SessionStore.getClosedTabCount(window); function isUndoCloseEnabled() { updateTabContextMenu(); diff --git a/browser/base/content/test/browser_bug817947.js b/browser/base/content/test/browser_bug817947.js index 061cd8d1873..e5b7997640c 100644 --- a/browser/base/content/test/browser_bug817947.js +++ b/browser/base/content/test/browser_bug817947.js @@ -1,8 +1,6 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -const ss = Cc["@mozilla.org/browser/sessionstore;1"] - .getService(Ci.nsISessionStore); const URL = "http://mochi.test:8888/browser/"; const PREF = "browser.sessionstore.restore_on_demand"; @@ -36,12 +34,12 @@ function preparePendingTab(aCallback) { let tab = gBrowser.addTab(URL); whenLoaded(tab.linkedBrowser, function () { - let state = ss.getTabState(tab); + let state = SessionStore.getTabState(tab); gBrowser.removeTab(tab); tab = gBrowser.addTab("about:blank"); whenLoaded(tab.linkedBrowser, function () { - ss.setTabState(tab, state); + SessionStore.setTabState(tab, state); ok(tab.hasAttribute("pending"), "tab should be pending"); aCallback(tab); }); diff --git a/browser/base/content/test/browser_bug887515.js b/browser/base/content/test/browser_bug887515.js index b3e4f5dbbae..48796162f4b 100644 --- a/browser/base/content/test/browser_bug887515.js +++ b/browser/base/content/test/browser_bug887515.js @@ -1,7 +1,5 @@ function numClosedTabs() - Cc["@mozilla.org/browser/sessionstore;1"]. - getService(Ci.nsISessionStore). - getNumberOfTabsClosedLast(window); + SessionStore.getNumberOfTabsClosedLast(window); var originalTab; var tab1Loaded = false; diff --git a/browser/components/sessionstore/src/SessionSaver.jsm b/browser/components/sessionstore/src/SessionSaver.jsm index b1977d9727c..06fbb312e02 100644 --- a/browser/components/sessionstore/src/SessionSaver.jsm +++ b/browser/components/sessionstore/src/SessionSaver.jsm @@ -29,15 +29,48 @@ XPCOMUtils.defineLazyGetter(this, "gInterval", function () { Services.prefs.addObserver(PREF, () => { this.gInterval = Services.prefs.getIntPref(PREF); - // Cancel any pending runs and call runDelayed() with - // zero to apply the newly configured interval. - SessionSaverInternal.cancel(); - SessionSaverInternal.runDelayed(0); + if (isBatteryCharging()) { + // Cancel any pending runs and call runDelayed() + // to apply the newly configured interval. + SessionSaverInternal.cancel(); + SessionSaverInternal.runDelayed(0); + } }, false); return Services.prefs.getIntPref(PREF); }); +XPCOMUtils.defineLazyGetter(this, "gIntervalBattery", function () { + const PREF = "browser.sessionstore.interval_battery"; + + // Observer that updates the cached value when the preference changes. + Services.prefs.addObserver(PREF, () => { + this.gIntervalBattery = Services.prefs.getIntPref(PREF); + + if (!isBatteryCharging()) { + // Cancel any pending runs and call runDelayed() + // to apply the newly configured interval. + SessionSaverInternal.cancel(); + SessionSaverInternal.runDelayed(0); + } + }, false); + + return Services.prefs.getIntPref(PREF); +}); + +// Check if battery is charging +function isBatteryCharging() { + return Services.appShell.hiddenDOMWindow.navigator.battery.charging; +} + +// Get the current session store interval based on battery status +function getInterval() { + if (isBatteryCharging()) { + return gInterval; + } + return gIntervalBattery; +} + // Wrap a string as a nsISupports. function createSupportsString(data) { let string = Cc["@mozilla.org/supports-string;1"] @@ -148,7 +181,7 @@ let SessionSaverInternal = { } // Interval until the next disk operation is allowed. - delay = Math.max(this._lastSaveTime + gInterval - Date.now(), delay, 0); + delay = Math.max(this._lastSaveTime + getInterval() - Date.now(), delay, 0); // Schedule a state save. this._timeoutID = setTimeout(() => this._saveState(), delay); diff --git a/browser/devtools/app-manager/app-projects.js b/browser/devtools/app-manager/app-projects.js new file mode 100644 index 00000000000..d21bfd7cf90 --- /dev/null +++ b/browser/devtools/app-manager/app-projects.js @@ -0,0 +1,149 @@ +const {Cc,Ci,Cu} = require("chrome"); +const ObservableObject = require("devtools/shared/observable-object"); +const promise = require("sdk/core/promise"); + +const {EventEmitter} = Cu.import("resource:///modules/devtools/shared/event-emitter.js"); + +/** + * IndexedDB wrapper that just save project objects + * + * The only constraint is that project objects have to have + * a unique `location` object. + */ + +const global = this; +const IDB = { + _db: null, + + open: function () { + let deferred = promise.defer(); + + var idbManager = Cc["@mozilla.org/dom/indexeddb/manager;1"] + .getService(Ci.nsIIndexedDatabaseManager); + idbManager.initWindowless(global); + + let request = global.indexedDB.open("AppProjects", 5); + request.onerror = function(event) { + deferred.reject("Unable to open AppProjects indexedDB. " + + "Error code: " + event.target.errorCode); + }; + request.onupgradeneeded = function(event) { + let db = event.target.result; + db.createObjectStore("projects", { keyPath: "location" }); + }; + + request.onsuccess = function() { + let db = IDB._db = request.result; + let objectStore = db.transaction("projects").objectStore("projects"); + let projects = [] + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + projects.push(cursor.value); + cursor.continue(); + } else { + deferred.resolve(projects); + } + }; + }; + + return deferred.promise; + }, + + add: function(project) { + let deferred = promise.defer(); + + var transaction = IDB._db.transaction(["projects"], "readwrite"); + var objectStore = transaction.objectStore("projects"); + var request = objectStore.add(project); + request.onerror = function(event) { + deferred.reject("Unable to add project to the AppProjects indexedDB: " + + this.error.name + " - " + this.error.message ); + }; + request.onsuccess = function() { + deferred.resolve(); + }; + + return deferred.promise; + }, + + remove: function(location) { + let deferred = promise.defer(); + + let request = IDB._db.transaction(["projects"], "readwrite") + .objectStore("projects") + .delete(location); + request.onsuccess = function(event) { + deferred.resolve(); + }; + request.onerror = function() { + deferred.reject("Unable to delete project to the AppProjects indexedDB: " + + this.error.name + " - " + this.error.message ); + }; + + return deferred.promise; + } +}; + +const store = new ObservableObject({ projects:[] }); + +IDB.open().then(function (projects) { + store.object.projects = projects; + AppProjects.emit("ready", store.object.projects); +}); + +const AppProjects = { + addPackaged: function(folder) { + let project = { + type: "packaged", + location: folder.path + }; + return IDB.add(project).then(function () { + store.object.projects.push(project); + // return the added objects (proxified) + return store.object.projects[store.object.projects.length - 1]; + }); + }, + + addHosted: function(manifestURL) { + let project = { + type: "hosted", + location: manifestURL + }; + return IDB.add(project).then(function () { + store.object.projects.push(project); + // return the added objects (proxified) + return store.object.projects[store.object.projects.length - 1]; + }); + }, + + remove: function(location) { + return IDB.remove(location).then(function () { + let projects = store.object.projects; + for (let i = 0; i < projects.length; i++) { + if (projects[i].location == location) { + projects.splice(i, 1); + return; + } + } + throw new Error("Unable to find project in AppProjects store"); + }); + }, + + get: function(location) { + let projects = store.object.projects; + for (let i = 0; i < projects.length; i++) { + if (projects[i].location == location) { + return projects[i]; + } + } + return null; + }, + + store: store +}; + +EventEmitter.decorate(AppProjects); + +exports.AppProjects = AppProjects; + diff --git a/browser/devtools/app-manager/test/Makefile.in b/browser/devtools/app-manager/test/Makefile.in index 8cf9f24ea6c..5a28cecd611 100644 --- a/browser/devtools/app-manager/test/Makefile.in +++ b/browser/devtools/app-manager/test/Makefile.in @@ -14,6 +14,8 @@ MOCHITEST_CHROME_FILES = \ test_template.html \ test_connection_store.html \ test_device_store.html \ + test_projects_store.html \ + hosted_app.manifest \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/browser/devtools/app-manager/test/hosted_app.manifest b/browser/devtools/app-manager/test/hosted_app.manifest new file mode 100644 index 00000000000..c23776affb3 --- /dev/null +++ b/browser/devtools/app-manager/test/hosted_app.manifest @@ -0,0 +1,3 @@ +{ + "name": "My hosted app" +} diff --git a/browser/devtools/app-manager/test/test_projects_store.html b/browser/devtools/app-manager/test/test_projects_store.html new file mode 100644 index 00000000000..907404c1942 --- /dev/null +++ b/browser/devtools/app-manager/test/test_projects_store.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + diff --git a/browser/devtools/debugger/DebuggerProcess.jsm b/browser/devtools/debugger/DebuggerProcess.jsm index 9626138364e..0abaf395bc8 100644 --- a/browser/devtools/debugger/DebuggerProcess.jsm +++ b/browser/devtools/debugger/DebuggerProcess.jsm @@ -11,10 +11,10 @@ const DBG_XUL = "chrome://browser/content/devtools/debugger.xul"; const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger"; Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); -let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +Cu.import("resource://gre/modules/devtools/Loader.jsm"); +let require = devtools.require; let Telemetry = require("devtools/shared/telemetry"); this.EXPORTED_SYMBOLS = ["BrowserDebuggerProcess"]; @@ -50,11 +50,21 @@ BrowserDebuggerProcess.prototype = { * Initializes the debugger server. */ _initServer: function() { - if (!DebuggerServer.initialized) { - DebuggerServer.init(); - DebuggerServer.addBrowserActors(); + if (!this.loader) { + // Create a separate loader instance, so that we can be sure to receive a + // separate instance of the DebuggingServer from the rest of the devtools. + // This allows us to safely use the tools against even the actors and + // DebuggingServer itself. + this.loader = new DevToolsLoader(); + this.loader.main("devtools/server/main"); + this.debuggerServer = this.loader.DebuggerServer; } - DebuggerServer.openListener(Prefs.chromeDebuggingPort); + + if (!this.debuggerServer.initialized) { + this.debuggerServer.init(); + this.debuggerServer.addBrowserActors(); + } + this.debuggerServer.openListener(Prefs.chromeDebuggingPort); }, /** @@ -136,6 +146,8 @@ BrowserDebuggerProcess.prototype = { this._telemetry.toolClosed("jsbrowserdebugger"); + this.debuggerServer.destroy(); + dumpn("Chrome debugger is now closed..."); if (typeof this._closeCallback == "function") { this._closeCallback.call({}, this); diff --git a/browser/devtools/shared/observable-object.js b/browser/devtools/shared/observable-object.js index c42596a62e6..e4c71e14472 100644 --- a/browser/devtools/shared/observable-object.js +++ b/browser/devtools/shared/observable-object.js @@ -51,6 +51,7 @@ function isObject(x) { function Handler(emitter) { this._emitter = emitter; this._wrappers = new WeakMap(); + this._values = new WeakMap(); this._paths = new WeakMap(); } @@ -61,11 +62,15 @@ Handler.prototype = { path = this._paths.get(target).concat(key); } else if (this._wrappers.has(value)) { path = this._paths.get(value); + } else if (this._paths.has(value)) { + path = this._paths.get(value); + value = this._values.get(value); } else { path = this._paths.get(target).concat(key); this._paths.set(value, path); let wrapper = new Proxy(value, this); this._wrappers.set(wrapper, value); + this._values.set(value, wrapper); value = wrapper; } return [value, path]; diff --git a/browser/devtools/shared/test/browser_observableobject.js b/browser/devtools/shared/test/browser_observableobject.js index 642f81208a1..f13d81fa1f3 100644 --- a/browser/devtools/shared/test/browser_observableobject.js +++ b/browser/devtools/shared/test/browser_observableobject.js @@ -77,6 +77,7 @@ function test() { oe.object.foo[1] = oe.object.bar; is(index, expected.length, "Event count is right"); + is(oe.object.bar, oe.object.bar, "Object attributes are wrapped only once"); finish(); } diff --git a/browser/themes/linux/devtools/debugger.css b/browser/themes/linux/devtools/debugger.css index 5b2c8f5fac9..be950914858 100644 --- a/browser/themes/linux/devtools/debugger.css +++ b/browser/themes/linux/devtools/debugger.css @@ -18,12 +18,9 @@ } .side-menu-widget-item-checkbox { - -moz-appearance: none; + -moz-appearance: none; + -moz-margin-end: -6px; padding: 0; - margin-top: 4px; - margin-bottom: -4px; - -moz-margin-start: 4px; - -moz-margin-end: -4px; opacity: 0; transition: opacity .15s ease 0s; } diff --git a/browser/themes/linux/devtools/netmonitor.css b/browser/themes/linux/devtools/netmonitor.css index 4cf2604a945..7c18da94d43 100644 --- a/browser/themes/linux/devtools/netmonitor.css +++ b/browser/themes/linux/devtools/netmonitor.css @@ -316,10 +316,6 @@ box.requests-menu-status[code^="5"] { background: rgba(255,255,255,0.05); } -.side-menu-widget-item-contents { - padding: 0; -} - /* Network request details */ #details-pane { diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index 2478253d55b..dbc41973b65 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -304,19 +304,20 @@ } .side-menu-widget-item { - border-top: 1px solid hsla(210,16%,76%,.1); - border-bottom: 1px solid hsla(210,8%,5%,.25); + border-top: 1px solid hsla(210,8%,5%,.25); + border-bottom: 1px solid hsla(210,16%,76%,.1); + margin-top: -1px; + margin-bottom: -1px; cursor: pointer; } .side-menu-widget-item:last-of-type { - box-shadow: 0 1px 0 hsla(210,16%,76%,.1); + box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25); } .side-menu-widget-item.selected { background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important; - box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15), - inset 0 -1px 0 hsla(210,40%,83%,.05); + box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15); } .side-menu-widget-item.selected > .side-menu-widget-item-arrow { @@ -334,21 +335,14 @@ background-position: center left, top left; } -.side-menu-widget-item-arrow { - -moz-margin-start: -8px; - width: 8px; -} - -.side-menu-widget-item-contents { +.side-menu-widget-item-label { padding: 4px 0px; -} - -.side-menu-widget-item label { cursor: inherit; } -.side-menu-widget-item-other:first-of-type { - border-top-left-radius: 4px; +.side-menu-widget-item-arrow { + -moz-margin-start: -8px; + width: 8px; } .side-menu-widget-item-other { @@ -361,17 +355,8 @@ inset 0 -1px 0 hsla(210,40%,83%,.07); } -.side-menu-widget-item.selected .side-menu-widget-item-other:first-of-type { - border-top: 1px dotted #333; - margin-top: 3px; -} - .side-menu-widget-item-other:first-of-type { - margin-top: 4px; -} - -.side-menu-widget-item-other:last-of-type { - margin-bottom: -4px; + border-top-left-radius: 4px; } .side-menu-widget-item-other > label { diff --git a/browser/themes/osx/devtools/debugger.css b/browser/themes/osx/devtools/debugger.css index 3475aa2b486..21879347b56 100644 --- a/browser/themes/osx/devtools/debugger.css +++ b/browser/themes/osx/devtools/debugger.css @@ -20,12 +20,9 @@ } .side-menu-widget-item-checkbox { - -moz-appearance: none; + -moz-appearance: none; + -moz-margin-end: -6px; padding: 0; - margin-top: 4px; - margin-bottom: -4px; - -moz-margin-start: 4px; - -moz-margin-end: -4px; opacity: 0; transition: opacity .15s ease-out 0s; } diff --git a/browser/themes/osx/devtools/netmonitor.css b/browser/themes/osx/devtools/netmonitor.css index 1192e84eb0e..1e9460b0600 100644 --- a/browser/themes/osx/devtools/netmonitor.css +++ b/browser/themes/osx/devtools/netmonitor.css @@ -316,10 +316,6 @@ box.requests-menu-status[code^="5"] { background: rgba(255,255,255,0.05); } -.side-menu-widget-item-contents { - padding: 0; -} - /* Network request details */ #details-pane { diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css index 41c38988760..15f02ceb243 100644 --- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -304,19 +304,20 @@ } .side-menu-widget-item { - border-top: 1px solid hsla(210,16%,76%,.1); - border-bottom: 1px solid hsla(210,8%,5%,.25); + border-top: 1px solid hsla(210,8%,5%,.25); + border-bottom: 1px solid hsla(210,16%,76%,.1); + margin-top: -1px; + margin-bottom: -1px; cursor: pointer; } .side-menu-widget-item:last-of-type { - box-shadow: 0 1px 0 hsla(210,16%,76%,.1); + box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25); } .side-menu-widget-item.selected { background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important; - box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15), - inset 0 -1px 0 hsla(210,40%,83%,.05); + box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15); } .side-menu-widget-item.selected > .side-menu-widget-item-arrow { @@ -334,21 +335,14 @@ background-position: center left, top left; } -.side-menu-widget-item-arrow { - -moz-margin-start: -8px; - width: 8px; -} - -.side-menu-widget-item-contents { +.side-menu-widget-item-label { padding: 4px 0px; -} - -.side-menu-widget-item label { cursor: inherit; } -.side-menu-widget-item-other:first-of-type { - border-top-left-radius: 4px; +.side-menu-widget-item-arrow { + -moz-margin-start: -8px; + width: 8px; } .side-menu-widget-item-other { @@ -361,17 +355,8 @@ inset 0 -1px 0 hsla(210,40%,83%,.07); } -.side-menu-widget-item.selected .side-menu-widget-item-other:first-of-type { - border-top: 1px dotted #333; - margin-top: 3px; -} - .side-menu-widget-item-other:first-of-type { - margin-top: 4px; -} - -.side-menu-widget-item-other:last-of-type { - margin-bottom: -4px; + border-top-left-radius: 4px; } .side-menu-widget-item-other > label { diff --git a/browser/themes/windows/devtools/debugger.css b/browser/themes/windows/devtools/debugger.css index 748c2d671f2..16f6d7cf9d0 100644 --- a/browser/themes/windows/devtools/debugger.css +++ b/browser/themes/windows/devtools/debugger.css @@ -18,12 +18,9 @@ } .side-menu-widget-item-checkbox { - -moz-appearance: none; + -moz-appearance: none; + -moz-margin-end: -6px; padding: 0; - margin-top: 4px; - margin-bottom: -4px; - -moz-margin-start: 4px; - -moz-margin-end: -4px; opacity: 0; transition: opacity .15s ease 0s; } diff --git a/browser/themes/windows/devtools/netmonitor.css b/browser/themes/windows/devtools/netmonitor.css index 422515d7305..694c0fd8c70 100644 --- a/browser/themes/windows/devtools/netmonitor.css +++ b/browser/themes/windows/devtools/netmonitor.css @@ -316,10 +316,6 @@ box.requests-menu-status[code^="5"] { background: rgba(255,255,255,0.05); } -.side-menu-widget-item-contents { - padding: 0; -} - /* Network request details */ #details-pane { diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css index c23f97bd135..07ebbf634c1 100644 --- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -308,19 +308,20 @@ } .side-menu-widget-item { - border-top: 1px solid hsla(210,16%,76%,.1); - border-bottom: 1px solid hsla(210,8%,5%,.25); + border-top: 1px solid hsla(210,8%,5%,.25); + border-bottom: 1px solid hsla(210,16%,76%,.1); + margin-top: -1px; + margin-bottom: -1px; cursor: pointer; } .side-menu-widget-item:last-of-type { - box-shadow: 0 1px 0 hsla(210,16%,76%,.1); + box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25); } .side-menu-widget-item.selected { background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important; - box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15), - inset 0 -1px 0 hsla(210,40%,83%,.05); + box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15); } .side-menu-widget-item.selected > .side-menu-widget-item-arrow { @@ -338,21 +339,14 @@ background-position: center left, top left; } -.side-menu-widget-item-arrow { - -moz-margin-start: -8px; - width: 8px; -} - -.side-menu-widget-item-contents { +.side-menu-widget-item-label { padding: 4px 0px; -} - -.side-menu-widget-item label { cursor: inherit; } -.side-menu-widget-item-other:first-of-type { - border-top-left-radius: 4px; +.side-menu-widget-item-arrow { + -moz-margin-start: -8px; + width: 8px; } .side-menu-widget-item-other { @@ -365,17 +359,8 @@ inset 0 -1px 0 hsla(210,40%,83%,.07); } -.side-menu-widget-item.selected .side-menu-widget-item-other:first-of-type { - border-top: 1px dotted #333; - margin-top: 3px; -} - .side-menu-widget-item-other:first-of-type { - margin-top: 4px; -} - -.side-menu-widget-item-other:last-of-type { - margin-bottom: -4px; + border-top-left-radius: 4px; } .side-menu-widget-item-other > label { diff --git a/mobile/android/chrome/content/SelectionHandler.js b/mobile/android/chrome/content/SelectionHandler.js index f80fe6afd7a..5512cf9aca0 100644 --- a/mobile/android/chrome/content/SelectionHandler.js +++ b/mobile/android/chrome/content/SelectionHandler.js @@ -78,7 +78,7 @@ var SelectionHandler = { } else if (this._activeType == this.TYPE_CURSOR) { // attachCaret() is called in the "Gesture:SingleTap" handler in BrowserEventHandler // We're guaranteed to call this first, because this observer was added last - this._closeSelection(); + this._deactivate(); } break; } @@ -159,7 +159,7 @@ var SelectionHandler = { case "compositionend": if (this._activeType == this.TYPE_CURSOR) { - this._closeSelection(); + this._deactivate(); } break; } @@ -216,25 +216,25 @@ var SelectionHandler = { this._closeSelection(); this._initTargetInfo(aElement); - this._activeType = this.TYPE_SELECTION; // Clear any existing selection from the document this._contentWindow.getSelection().removeAllRanges(); if (!this._domWinUtils.selectAtPoint(aX, aY, Ci.nsIDOMWindowUtils.SELECT_WORDNOSPACE)) { - this._closeSelection(); + this._deactivate(); return; } let selection = this._getSelection(); // If the range didn't have any text, let's bail if (!selection || selection.rangeCount == 0) { - this._closeSelection(); + this._deactivate(); return; } // Add a listener to end the selection if it's removed programatically selection.QueryInterface(Ci.nsISelectionPrivate).addSelectionListener(this); + this._activeType = this.TYPE_SELECTION; // Initialize the cache this._cache = { start: {}, end: {}}; @@ -498,16 +498,23 @@ var SelectionHandler = { if (this._activeType == this.TYPE_NONE) return; - if (this._activeType == this.TYPE_SELECTION) { - let selection = this._getSelection(); - if (selection) { - // Remove our listener before we clear the selection - selection.QueryInterface(Ci.nsISelectionPrivate).removeSelectionListener(this); - // Clear selection without clearing the anchorNode or focusNode - selection.collapseToStart(); - } - } + if (this._activeType == this.TYPE_SELECTION) + this._clearSelection(); + this._deactivate(); + }, + + _clearSelection: function sh_clearSelection() { + let selection = this._getSelection(); + if (selection) { + // Remove our listener before we clear the selection + selection.QueryInterface(Ci.nsISelectionPrivate).removeSelectionListener(this); + // Clear selection without clearing the anchorNode or focusNode + selection.collapseToStart(); + } + }, + + _deactivate: function sh_deactivate() { this._activeType = this.TYPE_NONE; sendMessageToJava({ type: "TextSelection:HideHandles" }); diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_update.js b/toolkit/components/thumbnails/test/browser_thumbnails_update.js index b37179b567f..15395c1fcc8 100644 --- a/toolkit/components/thumbnails/test/browser_thumbnails_update.js +++ b/toolkit/components/thumbnails/test/browser_thumbnails_update.js @@ -43,7 +43,7 @@ function getThumbnailModifiedTime(url) { /* Check functionality of a normal "captureIfStale" request */ function simpleCaptureTest() { let numNotifications = 0; - const URL = "data:text/html;charset=utf-8,"; + const URL = "http://mochi.test:8888/browser/toolkit/components/thumbnails/test/thumbnails_update.sjs?simple"; function observe(subject, topic, data) { is(topic, "page-thumbnail:create", "got expected topic"); @@ -56,7 +56,7 @@ function simpleCaptureTest() { } Services.obs.addObserver(observe, "page-thumbnail:create", false); - // Create a tab with a red background. + // Create a tab - we don't care what the content is. yield addTab(URL); let browser = gBrowser.selectedBrowser; diff --git a/toolkit/components/thumbnails/test/thumbnails_update.sjs b/toolkit/components/thumbnails/test/thumbnails_update.sjs index 8ae4d79c981..4d8ab406a2f 100644 --- a/toolkit/components/thumbnails/test/thumbnails_update.sjs +++ b/toolkit/components/thumbnails/test/thumbnails_update.sjs @@ -1,7 +1,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -// This server-side script primarily must return different *content* for the +// This server-side script is used for browser_thumbnails_update. One of the +// main things it must do in all cases is ensure a Cache-Control: no-store +// header, so the foreground capture doesn't interfere with the testing. + +// If the querystring is "simple", then all it does it return some content - +// it doesn't really matter what that content is. + +// Otherwise, its main role is that it must return different *content* for the // second request than it did for the first. // Also, it should be able to return an error response when requested for the // second response. @@ -16,6 +23,16 @@ function handleRequest(aRequest, aResponse) { // so set as a "no-store" response. aResponse.setHeader("Cache-Control", "no-store"); + // for the simple test - just return some content. + if (aRequest.queryString == "simple") { + aResponse.write(""); + aResponse.setStatusLine(aRequest.httpVersion, 200, "Its simply OK"); + return; + } + + // it's one of the more complex tests where the first request for the given + // URL must return different content than the second, and possibly an error + // response for the second let doneError = getState(aRequest.queryString); if (!doneError) { // first request - return a response with a green body and 200 response. diff --git a/toolkit/devtools/Loader.jsm b/toolkit/devtools/Loader.jsm index d9ad75154c3..dd81dc798fb 100644 --- a/toolkit/devtools/Loader.jsm +++ b/toolkit/devtools/Loader.jsm @@ -21,7 +21,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devto let loader = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}).Loader; let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; -this.EXPORTED_SYMBOLS = ["devtools"]; +this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools"]; /** * Providers are different strategies for loading the devtools. @@ -36,11 +36,11 @@ let loaderGlobals = { lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils), lazyServiceGetter: XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils) } -} +}; // Used when the tools should be loaded from the Firefox package itself (the default) var BuiltinProvider = { - load: function(done) { + load: function() { this.loader = new loader.Loader({ modules: { "toolkit/loader": loader @@ -78,7 +78,7 @@ var SrcdirProvider = { return Services.io.newFileURI(file).spec; }, - load: function(done) { + load: function() { let srcdir = Services.prefs.getComplexValue("devtools.loader.srcdir", Ci.nsISupportsString); srcdir = OS.Path.normalize(srcdir.data.trim()); @@ -182,9 +182,15 @@ var SrcdirProvider = { /** * The main devtools API. * In addition to a few loader-related details, this object will also include all - * exports from the main module. + * exports from the main module. The standard instance of this loader is + * exported as |devtools| below, but if a fresh copy of the loader is needed, + * then a new one can also be created. */ -this.devtools = { +this.DevToolsLoader = function DevToolsLoader() { + this._chooseProvider(); +}; + +DevToolsLoader.prototype = { _provider: null, /** @@ -267,5 +273,5 @@ this.devtools = { }, }; -// Now load the tools. -devtools._chooseProvider(); +// Export the standard instance of DevToolsLoader used by the tools. +this.devtools = new DevToolsLoader(); diff --git a/toolkit/devtools/client/connection-manager.js b/toolkit/devtools/client/connection-manager.js index fe636449893..f94a9f79e2a 100644 --- a/toolkit/devtools/client/connection-manager.js +++ b/toolkit/devtools/client/connection-manager.js @@ -6,7 +6,7 @@ "use strict"; -const {Cu} = require("chrome"); +const {Cc, Ci, Cu} = require("chrome"); const {setTimeout, clearTimeout} = require('sdk/timers'); const EventEmitter = require("devtools/shared/event-emitter"); @@ -25,6 +25,7 @@ Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); * Methods: * ⬩ Connection createConnection(host, port) * ⬩ void destroyConnection(connection) + * ⬩ Number getFreeTCPPort() * * Properties: * ⬩ Array connections @@ -46,6 +47,7 @@ Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); * ⬩ port Port * ⬩ logs Current logs. "newlog" event notifies new available logs * ⬩ store Reference to a local data store (see below) + * ⬩ keepConnecting Should the connection keep trying connecting * ⬩ status Connection status: * Connection.Status.CONNECTED * Connection.Status.DISCONNECTED @@ -86,6 +88,14 @@ let ConnectionManager = { get connections() { return [c for (c of this._connections)]; }, + getFreeTCPPort: function () { + let serv = Cc['@mozilla.org/network/server-socket;1'] + .createInstance(Ci.nsIServerSocket); + serv.init(-1, true, -1); + let port = serv.port; + serv.close(); + return port; + }, } EventEmitter.decorate(ConnectionManager); @@ -101,6 +111,7 @@ function Connection(host, port) { this._onDisconnected = this._onDisconnected.bind(this); this._onConnected = this._onConnected.bind(this); this._onTimeout = this._onTimeout.bind(this); + this.keepConnecting = false; } Connection.Status = { @@ -180,15 +191,7 @@ Connection.prototype = { let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout"); this._timeoutID = setTimeout(this._onTimeout, delay); - let transport; - if (!this._host) { - transport = DebuggerServer.connectPipe(); - } else { - transport = debuggerSocketConnect(this._host, this._port); - } - this._client = new DebuggerClient(transport); - this._client.addOneTimeListener("closed", this._onDisconnected); - this._client.connect(this._onConnected); + this._clientConnect(); } else { let msg = "Can't connect. Client is not fully disconnected"; this.log(msg); @@ -199,6 +202,7 @@ Connection.prototype = { destroy: function() { this.log("killing connection"); clearTimeout(this._timeoutID); + this.keepConnecting = false; if (this._client) { this._client.close(); this._client = null; @@ -206,6 +210,18 @@ Connection.prototype = { this._setStatus(Connection.Status.DESTROYED); }, + _clientConnect: function () { + let transport; + if (!this._host) { + transport = DebuggerServer.connectPipe(); + } else { + transport = debuggerSocketConnect(this._host, this._port); + } + this._client = new DebuggerClient(transport); + this._client.addOneTimeListener("closed", this._onDisconnected); + this._client.connect(this._onConnected); + }, + get status() { return this._status }, @@ -219,6 +235,13 @@ Connection.prototype = { }, _onDisconnected: function() { + this._client = null; + + if (this._status == Connection.Status.CONNECTING && this.keepConnecting) { + setTimeout(() => this._clientConnect(), 0); + return; + } + clearTimeout(this._timeoutID); switch (this.status) { case Connection.Status.CONNECTED: @@ -230,7 +253,6 @@ Connection.prototype = { default: this.log("disconnected"); } - this._client = null; this._setStatus(Connection.Status.DISCONNECTED); }, @@ -242,7 +264,7 @@ Connection.prototype = { _onTimeout: function() { this.log("connection timeout"); - this.emit(Connection.Events.TIMEOUT, str); + this.emit(Connection.Events.TIMEOUT); this.disconnect(); }, } diff --git a/toolkit/devtools/client/dbg-client.jsm b/toolkit/devtools/client/dbg-client.jsm index 2ea2ae920ef..f6bdca7fb30 100644 --- a/toolkit/devtools/client/dbg-client.jsm +++ b/toolkit/devtools/client/dbg-client.jsm @@ -1675,20 +1675,20 @@ eventSource(ThreadClient.prototype); function TraceClient(aClient, aActor) { this._client = aClient; this._actor = aActor; - this._traces = Object.create(null); - this._activeTraces = 0; + this._activeTraces = new Set(); + this._waitingPackets = new Map(); + this._expectedPacket = 0; - this._client.addListener(UnsolicitedNotifications.enteredFrame, - this.onEnteredFrame.bind(this)); - this._client.addListener(UnsolicitedNotifications.exitedFrame, - this.onExitedFrame.bind(this)); + this.onPacket = this.onPacket.bind(this); + this._client.addListener(UnsolicitedNotifications.enteredFrame, this.onPacket); + this._client.addListener(UnsolicitedNotifications.exitedFrame, this.onPacket); this.request = this._client.request; } TraceClient.prototype = { get actor() { return this._actor; }, - get tracing() { return this._activeTraces > 0; }, + get tracing() { return this._activeTraces.size > 0; }, get _transport() { return this._client._transport; }, @@ -1720,15 +1720,11 @@ TraceClient.prototype = { return aResponse; } - let name = aResponse.name; - - if (!this._traces[name] || !this._traces[name].active) { - this._activeTraces++; + if (!this.tracing) { + this._waitingPackets.clear(); + this._expectedPacket = 0; } - - this._traces[name] = { - active: true - }; + this._activeTraces.add(aResponse.name); return aResponse; }, @@ -1754,8 +1750,7 @@ TraceClient.prototype = { return aResponse; } - this._traces[aResponse.name].active = false; - this._activeTraces--; + this._activeTraces.delete(aResponse.name); return aResponse; }, @@ -1763,17 +1758,24 @@ TraceClient.prototype = { }), /** - * Called when the trace actor notifies that a frame has been entered. + * Called when the trace actor notifies that a frame has been + * entered or exited. + * + * @param aEvent string + * The type of the unsolicited packet (enteredFrame|exitedFrame). + * + * @param aPacket object + * Packet received over the RDP from the trace actor. */ - onEnteredFrame: function JSTC_onEnteredFrame(aEvent, aResponse) { - this.notify("enteredFrame", aResponse); - }, + onPacket: function JSTC_onPacket(aEvent, aPacket) { + this._waitingPackets.set(aPacket.sequence, aPacket); - /** - * Called when the trace actor notifies that a frame has been exited. - */ - onExitedFrame: function JSTC_onExitedFrame(aEvent, aResponse) { - this.notify("exitedFrame", aResponse); + while (this._waitingPackets.has(this._expectedPacket)) { + let packet = this._waitingPackets.get(this._expectedPacket); + this._waitingPackets.delete(this._expectedPacket); + this.notify(packet.type, packet); + this._expectedPacket++; + } } }; diff --git a/toolkit/devtools/server/dbg-server.jsm b/toolkit/devtools/server/dbg-server.jsm index 0fc4d1a783d..55988c3295f 100644 --- a/toolkit/devtools/server/dbg-server.jsm +++ b/toolkit/devtools/server/dbg-server.jsm @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; + /** * Loads the remote debugging protocol code into a sandbox, in order to * shield it from the debuggee. This way, when debugging chrome globals, @@ -15,30 +16,11 @@ const Ci = Components.interfaces; const Cc = Components.classes; const Cu = Components.utils; +const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); + this.EXPORTED_SYMBOLS = ["DebuggerServer", "ActorPool"]; -var loadSubScript = - "function loadSubScript(aURL)\n" + - "{\n" + - "const Ci = Components.interfaces;\n" + - "const Cc = Components.classes;\n" + - " try {\n" + - " let loader = Cc[\"@mozilla.org/moz/jssubscript-loader;1\"]\n" + - " .getService(Ci.mozIJSSubScriptLoader);\n" + - " loader.loadSubScript(aURL, this);\n" + - " } catch(e) {\n" + - " dump(\"Error loading: \" + aURL + \": \" + e + \" - \" + e.stack + \"\\n\");\n" + - " throw e;\n" + - " }\n" + - "}"; +let server = devtools.require("devtools/server/main"); -// Load the debugging server in a sandbox with its own compartment. -var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"] - .createInstance(Ci.nsIPrincipal); - -var gGlobal = Cu.Sandbox(systemPrincipal); -Cu.evalInSandbox(loadSubScript, gGlobal, "1.8"); -gGlobal.loadSubScript("resource://gre/modules/devtools/server/main.js"); - -this.DebuggerServer = gGlobal.DebuggerServer; -this.ActorPool = gGlobal.ActorPool; +this.DebuggerServer = server.DebuggerServer; +this.ActorPool = server.ActorPool; diff --git a/toolkit/devtools/server/main.js b/toolkit/devtools/server/main.js index e5fc94bc501..f4377291f40 100644 --- a/toolkit/devtools/server/main.js +++ b/toolkit/devtools/server/main.js @@ -10,11 +10,7 @@ * debugging global. */ -const Ci = Components.interfaces; -const Cc = Components.classes; -const CC = Components.Constructor; -const Cu = Components.utils; -const Cr = Components.results; +const { Ci, Cc, CC, Cu, Cr, components: Components } = require("chrome"); const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties"; Cu.import("resource://gre/modules/Services.jsm"); @@ -25,7 +21,24 @@ const promptConnections = Services.prefs.getBoolPref("devtools.debugger.prompt-c Cu.import("resource://gre/modules/jsdebugger.jsm"); addDebuggerToGlobal(this); +function loadSubScript(aURL) +{ + try { + let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + loader.loadSubScript(aURL, this); + } catch(e) { + let errorStr = "Error loading: " + aURL + ": " + e + " - " + e.stack + "\n"; + dump(errorStr); + Cu.reportError(errorStr); + throw e; + } +} + +let loaderRequire = require; +this.require = null; loadSubScript.call(this, "resource://gre/modules/commonjs/sdk/core/promise.js"); +this.require = loaderRequire; Cu.import("resource://gre/modules/devtools/SourceMap.jsm"); @@ -259,9 +272,7 @@ var DebuggerServer = { } let moduleAPI = ModuleAPI(); - - let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); - let mod = devtools.require(id); + let mod = require(id); mod.register(moduleAPI); gRegisteredModules[id] = { module: mod, api: moduleAPI }; }, @@ -607,6 +618,7 @@ var DebuggerServer = { } }; +exports.DebuggerServer = DebuggerServer; /** * Construct an ActorPool. @@ -622,6 +634,8 @@ function ActorPool(aConnection) this._actors = {}; } +exports.ActorPool = ActorPool; + ActorPool.prototype = { /** * Add an actor to the actor pool. If the actor doesn't have an ID, diff --git a/toolkit/devtools/server/tests/mochitest/test_connection-manager.html b/toolkit/devtools/server/tests/mochitest/test_connection-manager.html index 263352789f6..c747f0c9af3 100644 --- a/toolkit/devtools/server/tests/mochitest/test_connection-manager.html +++ b/toolkit/devtools/server/tests/mochitest/test_connection-manager.html @@ -20,6 +20,7 @@ window.onload = function() { Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); Cu.import("resource://gre/modules/devtools/Loader.jsm"); + Cu.import("resource://gre/modules/Services.jsm"); DebuggerServer.init(function () { return true; }); DebuggerServer.addBrowserActors(); @@ -43,7 +44,7 @@ window.onload = function() { var c = c2; - var eventsRef = "connecting connected disconnecting disconnected host-changed disconnected destroyed"; + var eventsRef = "connecting connected disconnecting disconnected host-changed disconnected timeout destroyed"; var events = []; var s = Connection.Status; @@ -70,7 +71,7 @@ window.onload = function() { function testError() { c.once(Connection.Events.DISCONNECTED, function(e) { events.push(e); - ConnectionManager.destroyConnection(c); + testKeepConnecting(); }); c.once(Connection.Events.HOST_CHANGED, function(e) { events.push(e); @@ -80,6 +81,24 @@ window.onload = function() { c.host = "localhost"; } + function testKeepConnecting() { + // ensure that keepConnecting keep trying connecting + // until the connection attempts timeout + var originalTimeout = Services.prefs.getIntPref("devtools.debugger.remote-timeout"); + Services.prefs.setIntPref("devtools.debugger.remote-timeout", 1000); + c.once("timeout", function (e) { + events.push(e); + Services.prefs.setIntPref("devtools.debugger.remote-timeout", originalTimeout); + ConnectionManager.destroyConnection(c); + }); + c.keepConnecting = true; + var port = ConnectionManager.getFreeTCPPort(); + ok(parseInt(port), "Free TCP port looks like a port number"); + c.port = port; + c.host = "locahost"; + c.connect(); + } + function finish() { is(events.join(" "), eventsRef, "Events received in the right order"); DebuggerServer.destroy(); diff --git a/toolkit/devtools/server/tests/unit/test_trace_client-01.js b/toolkit/devtools/server/tests/unit/test_trace_client-01.js new file mode 100644 index 00000000000..9e0fa4f1b27 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_trace_client-01.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that TraceClient emits enteredFrame and exitedFrame events in + * order when receiving packets out of order. + */ + +let {defer, resolve} = devtools.require("sdk/core/promise"); + +var gDebuggee; +var gClient; +var gTraceClient; + +function run_test() +{ + initTestTracerServer(); + gDebuggee = addTestGlobal("test-tracer-actor"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestTab(gClient, "test-tracer-actor", function(aResponse, aTabClient) { + gClient.attachTracer(aResponse.traceActor, function(aResponse, aTraceClient) { + gTraceClient = aTraceClient; + test_packet_order(); + }); + }); + }); + do_test_pending(); +} + +function test_packet_order() +{ + let sequence = 0; + + function check_packet(aEvent, aPacket) { + do_check_eq(aPacket.sequence, sequence, + 'packet should have sequence number ' + sequence); + sequence++; + } + + gTraceClient.addListener("enteredFrame", check_packet); + gTraceClient.addListener("exitedFrame", check_packet); + + start_trace() + .then(mock_packets) + .then(start_trace) + .then(mock_packets.bind(null, 14)) + .then(stop_trace) + .then(stop_trace) + .then(function() { + // All traces were stopped, so the sequence number resets + sequence = 0; + return resolve(); + }) + .then(start_trace) + .then(mock_packets) + .then(stop_trace) + .then(function() { + finishClient(gClient); + }); +} + +function start_trace() +{ + let deferred = defer(); + gTraceClient.startTrace([], null, function() { deferred.resolve(); }); + return deferred.promise; +} + +function stop_trace() +{ + let deferred = defer(); + gTraceClient.stopTrace(null, function() { deferred.resolve(); }); + return deferred.promise; +} + +function mock_packets(s = 0) +{ + gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 5 }); + gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 3 }); + gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 2 }); + gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 4 }); + gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 1 }); + + gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 7 }); + gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 8 }); + + // Triggers 0-5 + gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 0 }); + + gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 9 }); + gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 10 }); + + // Triggers 6-10 + gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 6 }); + + // Each following packet is expected; event is fired immediately + gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 11 }); + gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 12 }); + gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 13 }); +} diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini index ff25577171c..3f31b96fdb3 100644 --- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -166,3 +166,4 @@ reason = bug 820380 [test_trace_actor-04.js] [test_trace_actor-05.js] [test_trace_actor-06.js] +[test_trace_client-01.js] diff --git a/toolkit/devtools/server/transport.js b/toolkit/devtools/server/transport.js index bcfc36130fd..9086969e4ce 100644 --- a/toolkit/devtools/server/transport.js +++ b/toolkit/devtools/server/transport.js @@ -192,7 +192,11 @@ DebuggerTransport.prototype = { dumpn("Got: " + packet); let self = this; Services.tm.currentThread.dispatch(makeInfallible(function() { - self.hooks.onPacket(parsed); + // Ensure the hooks are still around by the time this runs (they will go + // away when the transport is closed). + if (self.hooks) { + self.hooks.onPacket(parsed); + } }, "DebuggerTransport instance's this.hooks.onPacket"), 0); return true; diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 5fa0a9aed15..9db1488aa50 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -11,9 +11,8 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/AddonRepository.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", diff --git a/toolkit/mozapps/extensions/content/xpinstallConfirm.js b/toolkit/mozapps/extensions/content/xpinstallConfirm.js index 766f1fa9600..c6eb799ddaf 100644 --- a/toolkit/mozapps/extensions/content/xpinstallConfirm.js +++ b/toolkit/mozapps/extensions/content/xpinstallConfirm.js @@ -4,8 +4,6 @@ * 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/. */ -var args - var XPInstallConfirm = {}; XPInstallConfirm.init = function XPInstallConfirm_init() @@ -15,9 +13,12 @@ XPInstallConfirm.init = function XPInstallConfirm_init() var _focused; var _timeout; + // Default to cancelling the install when the window unloads + XPInstallConfirm._installOK = false; + var bundle = document.getElementById("xpinstallConfirmStrings"); - args = window.arguments[0].wrappedJSObject; + let args = window.arguments[0].wrappedJSObject; var _installCountdownLength = 5; try { @@ -122,6 +123,17 @@ XPInstallConfirm.init = function XPInstallConfirm_init() document.removeEventListener("focus", myfocus, true); document.removeEventListener("blur", myblur, true); window.removeEventListener("unload", myUnload, false); + + // Now perform the desired action - either install the + // addons or cancel the installations + if (XPInstallConfirm._installOK) { + for (let install of args.installs) + install.install(); + } + else { + for (let install of args.installs) + install.cancel(); + } } if (_installCountdownLength > 0) { @@ -142,14 +154,14 @@ XPInstallConfirm.onOK = function XPInstallConfirm_onOk() getService(Components.interfaces.nsITelemetry). getHistogramById("SECURITY_UI"). add(Components.interfaces.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH); - for (let install of args.installs) - install.install(); + // Perform the install or cancel after the window has unloaded + XPInstallConfirm._installOK = true; return true; } XPInstallConfirm.onCancel = function XPInstallConfirm_onCancel() { - for (let install of args.installs) - install.cancel(); + // Perform the install or cancel after the window has unloaded + XPInstallConfirm._installOK = false; return true; } diff --git a/toolkit/themes/windows/global/tree-aero.css b/toolkit/themes/windows/global/tree-aero.css index 06029807acf..85885c75e7b 100644 --- a/toolkit/themes/windows/global/tree-aero.css +++ b/toolkit/themes/windows/global/tree-aero.css @@ -64,7 +64,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { } @media (-moz-windows-default-theme) { - treechildren::-moz-tree-row { + treechildren:not(.autocomplete-treebody)::-moz-tree-row { height: 1.8em; color: -moz-FieldText; -moz-margin-start: 1px; @@ -77,7 +77,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { -moz-outline-radius: 3px; } - treechildren::-moz-tree-row(selected) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected) { -moz-border-top-colors: @selectedBorderColor@ @whiteOpacityBorderColor@; -moz-border-right-colors: @selectedBorderColor@ @whiteOpacityBorderColor@; -moz-border-left-colors: @selectedBorderColor@ @whiteOpacityBorderColor@; @@ -87,7 +87,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { outline: 1px solid @whiteOpacityBorderColor@; } - treechildren::-moz-tree-row(current, focus) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(current, focus) { border-style: solid; -moz-border-top-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; -moz-border-right-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; @@ -96,7 +96,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { outline: 1px solid @whiteOpacityBorderColor@; } - treechildren::-moz-tree-row(selected, focus), + treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected, focus), treechildren::-moz-tree-row(dropOn) { -moz-border-top-colors: @selectedFocusBorderColor@ @whiteOpacityBorderColor@; -moz-border-right-colors: @selectedFocusBorderColor@ @whiteOpacityBorderColor@; @@ -106,7 +106,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { background-color: transparent; } - treechildren::-moz-tree-row(selected, current, focus) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected, current, focus) { border-style: solid; -moz-border-top-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; -moz-border-right-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; @@ -115,7 +115,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { background-image: linear-gradient(@hoverAndCurrentFocusGradientColor1@, @hoverAndCurrentFocusGradientColor2@); } - treechildren::-moz-tree-row(hover) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover) { -moz-border-top-colors: @hoverBorderColor@ @whiteOpacityBorderColor@; -moz-border-right-colors: @hoverBorderColor@ @whiteOpacityBorderColor@; -moz-border-left-colors: @hoverBorderColor@ @whiteOpacityBorderColor@; @@ -124,7 +124,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { outline: 1px solid @whiteOpacityBorderColor@; } - treechildren::-moz-tree-row(hover, current) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover, current) { -moz-border-top-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; -moz-border-right-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; -moz-border-left-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; @@ -132,7 +132,7 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { background-image: linear-gradient(@hoverGradientColor1@, @hoverGradientColor2@); } - treechildren::-moz-tree-row(hover, selected) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover, selected) { -moz-border-top-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; -moz-border-right-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; -moz-border-left-colors: @hoverAndFocusBorderColor@ @whiteOpacityBorderColor@; @@ -154,45 +154,45 @@ treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) { border-radius: 0; } - treechildren::-moz-tree-cell-text { + treechildren:not(.autocomplete-treebody)::-moz-tree-cell-text { padding-bottom: initial; border-color: transparent; background-color: transparent; } - treechildren::-moz-tree-cell-text(selected, focus) { + treechildren:not(.autocomplete-treebody)::-moz-tree-cell-text(selected, focus) { color: -moz-DialogText; } @media (-moz-os-version: windows-win8) { - treechildren::-moz-tree-row { + treechildren:not(.autocomplete-treebody)::-moz-tree-row { border-width: 1px; border-radius: 0; -moz-outline-radius: 0; } - treechildren::-moz-tree-row(selected) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected) { background-image: linear-gradient(@selectedGradientColor2@, @selectedGradientColor2@); } - treechildren::-moz-tree-row(selected, focus), + treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected, focus), treechildren::-moz-tree-row(dropOn) { background-image: linear-gradient(@selectedFocusGradientColor2@, @selectedFocusGradientColor2@); } - treechildren::-moz-tree-row(selected, current, focus) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected, current, focus) { background-image: linear-gradient(@hoverAndCurrentFocusGradientColor2@, @hoverAndCurrentFocusGradientColor2@); } - treechildren::-moz-tree-row(hover) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover) { background-image: linear-gradient(@hoverGradientColor2@, @hoverGradientColor2@); } - treechildren::-moz-tree-row(hover, current) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover, current) { background-image: linear-gradient(@hoverGradientColor2@, @hoverGradientColor2@); } - treechildren::-moz-tree-row(hover, selected) { + treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover, selected) { background-image: linear-gradient(@hoverAndCurrentFocusGradientColor2@, @hoverAndCurrentFocusGradientColor2@); } }