diff --git a/browser/devtools/framework/ToolDefinitions.jsm b/browser/devtools/framework/ToolDefinitions.jsm index 40cf52558c11..a6a0d842a7b1 100644 --- a/browser/devtools/framework/ToolDefinitions.jsm +++ b/browser/devtools/framework/ToolDefinitions.jsm @@ -135,7 +135,7 @@ let styleEditorDefinition = { tooltip: l10n("ToolboxStyleEditor.tooltip", styleEditorStrings), isTargetSupported: function(target) { - return target.isLocalTab; + return !target.isRemote; }, build: function(iframeWindow, toolbox) { @@ -146,6 +146,10 @@ let styleEditorDefinition = { let profilerDefinition = { id: "jsprofiler", + accesskey: l10n("profiler.accesskey", profilerStrings), + key: l10n("profiler.commandkey", profilerStrings), + ordinal: 4, + modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift", killswitch: "devtools.profiler.enabled", url: "chrome://browser/content/profiler.xul", label: l10n("profiler.label", profilerStrings), @@ -153,7 +157,7 @@ let profilerDefinition = { tooltip: l10n("profiler.tooltip", profilerStrings), isTargetSupported: function (target) { - return !target.isRemote; + return true; }, build: function (frame, target) { diff --git a/browser/devtools/profiler/ProfilerController.jsm b/browser/devtools/profiler/ProfilerController.jsm index 183c7a9fb8b9..9ff3cff0f610 100644 --- a/browser/devtools/profiler/ProfilerController.jsm +++ b/browser/devtools/profiler/ProfilerController.jsm @@ -22,14 +22,21 @@ XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () { * Object acting as a mediator between the ProfilerController and * DebuggerServer. */ -function ProfilerConnection() { +function ProfilerConnection(client) { if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } - let transport = DebuggerServer.connectPipe(); - this.client = new DebuggerClient(transport); + this.isRemote = true; + + if (!client) { + let transport = DebuggerServer.connectPipe(); + client = new DebuggerClient(transport); + this.isRemote = false; + } + + this.client = client; } ProfilerConnection.prototype = { @@ -44,12 +51,20 @@ ProfilerConnection.prototype = { connect: function PCn_connect(aCallback) { let client = this.client; - client.connect(function (aType, aTraits) { + let listTabs = function () { client.listTabs(function (aResponse) { this.actor = aResponse.profilerActor; aCallback(); }.bind(this)); - }.bind(this)); + }.bind(this); + + if (this.isRemote) { + return void listTabs(); + } + + client.connect(function (aType, aTraits) { + listTabs(); + }); }, /** @@ -123,8 +138,14 @@ ProfilerConnection.prototype = { /** * Object defining the profiler controller components. */ -function ProfilerController() { - this.profiler = new ProfilerConnection(); +function ProfilerController(target) { + let client; + + if (target.isRemote) { + client = target.client; + } + + this.profiler = new ProfilerConnection(client); this._connected = false; } diff --git a/browser/devtools/profiler/ProfilerPanel.jsm b/browser/devtools/profiler/ProfilerPanel.jsm index 0caab1dad772..0c3efd76ed35 100644 --- a/browser/devtools/profiler/ProfilerPanel.jsm +++ b/browser/devtools/profiler/ProfilerPanel.jsm @@ -27,10 +27,13 @@ XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () { * Its main function is to talk to the Cleopatra instance * inside of the iframe. * - * ProfileUI is also an event emitter. Currently, it emits - * only one event, 'ready', when Cleopatra is done loading. - * You can also check 'isReady' property to see if a - * particular instance has been loaded yet. + * ProfileUI is also an event emitter. It emits the following events: + * - ready, when Cleopatra is done loading (you can also check the isReady + * property to see if a particular instance has been loaded yet. + * - disabled, when Cleopatra gets disabled. Happens when another ProfileUI + * instance starts the profiler. + * - enabled, when Cleopatra gets enabled. Happens when another ProfileUI + * instance stops the profiler. * * @param number uid * Unique ID for this profile. @@ -63,27 +66,45 @@ function ProfileUI(uid, panel) { return; } + let label = doc.querySelector("li#profile-" + this.uid + " > h1"); switch (event.data.status) { case "loaded": + if (this.panel._runningUid !== null) { + this.iframe.contentWindow.postMessage(JSON.stringify({ + uid: this._runningUid, + isCurrent: this._runningUid === uid, + task: "onStarted" + }), "*"); + } + this.isReady = true; this.emit("ready"); break; case "start": - // Start profiling and, once started, notify the - // underlying page so that it could update the UI. + // Start profiling and, once started, notify the underlying page + // so that it could update the UI. Also, once started, we add a + // star to the profile name to indicate which profile is currently + // running. this.panel.startProfiling(function onStart() { - var data = JSON.stringify({task: "onStarted"}); - this.iframe.contentWindow.postMessage(data, "*"); + this.panel.broadcast(this.uid, {task: "onStarted"}); + label.textContent = label.textContent + " *"; }.bind(this)); break; case "stop": - // Stop profiling and, once stopped, notify the - // underlying page so that it could update the UI. + // Stop profiling and, once stopped, notify the underlying page so + // that it could update the UI and remove a star from the profile + // name. this.panel.stopProfiling(function onStop() { - var data = JSON.stringify({task: "onStopped"}); - this.iframe.contentWindow.postMessage(data, "*"); + this.panel.broadcast(this.uid, {task: "onStopped"}); + label.textContent = label.textContent.replace(/\s\*$/, ""); }.bind(this)); + break; + case "disabled": + this.emit("disabled"); + break; + case "enabled": + this.emit("enabled"); } }.bind(this)); } @@ -179,7 +200,7 @@ function ProfilerPanel(frame, toolbox) { this.window = frame.window; this.document = frame.document; this.target = toolbox.target; - this.controller = new ProfilerController(); + this.controller = new ProfilerController(this.target); this.profiles = new Map(); this._uid = 0; @@ -188,15 +209,16 @@ function ProfilerPanel(frame, toolbox) { } ProfilerPanel.prototype = { - isReady: null, - window: null, - document: null, - target: null, - controller: null, - profiles: null, + isReady: null, + window: null, + document: null, + target: null, + controller: null, + profiles: null, - _uid: null, - _activeUid: null, + _uid: null, + _activeUid: null, + _runningUid: null, get activeProfile() { return this.profiles.get(this._activeUid); @@ -207,8 +229,8 @@ ProfilerPanel.prototype = { }, /** - * Open a debug connection and, on success, switch to - * the newly created profile. + * Open a debug connection and, on success, switch to the newly created + * profile. * * @return Promise */ @@ -359,6 +381,41 @@ ProfilerPanel.prototype = { }.bind(this)); }, + /** + * Broadcast messages to all Cleopatra instances. + * + * @param number target + * UID of the recepient profile. All profiles will receive the message + * but the profile specified by 'target' will have a special property, + * isCurrent, set to true. + * @param object data + * An object with a property 'task' that will be sent over to Cleopatra. + */ + broadcast: function PP_broadcast(target, data) { + if (!this.profiles) { + return; + } + + if (data.task === "onStarted") { + this._runningUid = target; + } else { + this._runningUid = null; + } + + let uid = this._uid; + while (uid >= 0) { + if (this.profiles.has(uid)) { + let iframe = this.profiles.get(uid).iframe; + iframe.contentWindow.postMessage(JSON.stringify({ + uid: target, + isCurrent: target === uid, + task: data.task + }), "*"); + } + uid -= 1; + } + }, + /** * Cleanup. */ diff --git a/browser/devtools/profiler/cleopatra/css/devtools.css b/browser/devtools/profiler/cleopatra/css/devtools.css index 8f898ddaf619..79b81e5a3171 100644 --- a/browser/devtools/profiler/cleopatra/css/devtools.css +++ b/browser/devtools/profiler/cleopatra/css/devtools.css @@ -10,4 +10,9 @@ #stopWrapper { display: none; +} + +#profilerMessage { + color: #999; + display: none; } \ No newline at end of file diff --git a/browser/devtools/profiler/cleopatra/js/devtools.js b/browser/devtools/profiler/cleopatra/js/devtools.js index 544aae0aeb9d..f84eacc4c520 100644 --- a/browser/devtools/profiler/cleopatra/js/devtools.js +++ b/browser/devtools/profiler/cleopatra/js/devtools.js @@ -13,6 +13,8 @@ var gInstanceUID; * - loaded, when page is loaded. * - start, when user wants to start profiling. * - stop, when user wants to stop profiling. + * - disabled, when the profiler was disabled + * - enabled, when the profiler was enabled */ function notifyParent(status) { if (!gInstanceUID) { @@ -45,18 +47,34 @@ function notifyParent(status) { function onParentMessage(event) { var start = document.getElementById("startWrapper"); var stop = document.getElementById("stopWrapper"); + var profilerMessage = document.getElementById("profilerMessage"); var msg = JSON.parse(event.data); switch (msg.task) { case "onStarted": - start.style.display = "none"; - start.querySelector("button").removeAttribute("disabled"); - stop.style.display = "inline"; + if (msg.isCurrent) { + start.style.display = "none"; + start.querySelector("button").removeAttribute("disabled"); + stop.style.display = "inline"; + } else { + start.querySelector("button").setAttribute("disabled", true); + var text = gStrings.getFormatStr("profiler.alreadyRunning", [msg.uid]); + profilerMessage.textContent = text; + profilerMessage.style.display = "block"; + notifyParent("disabled"); + } break; case "onStopped": - stop.style.display = "none"; - stop.querySelector("button").removeAttribute("disabled"); - start.style.display = "inline"; + if (msg.isCurrent) { + stop.style.display = "none"; + stop.querySelector("button").removeAttribute("disabled"); + start.style.display = "inline"; + } else { + start.querySelector("button").removeAttribute("disabled"); + profilerMessage.textContent = ""; + profilerMessage.style.display = "none"; + notifyParent("enabled"); + } break; case "receiveProfileData": loadProfile(JSON.stringify(msg.rawProfile)); @@ -107,7 +125,8 @@ function initUI() { controlPane.className = "controlPane"; controlPane.innerHTML = "
" + startProfiling + "
" + - "" + stopProfiling + "
"; + "" + stopProfiling + "
" + + ""; controlPane.querySelector("#startWrapper > span.btn").appendChild(startButton); controlPane.querySelector("#stopWrapper > span.btn").appendChild(stopButton); @@ -213,4 +232,35 @@ function enterFinishedProfileUI() { } toggleJavascriptOnly(); +} + +function enterProgressUI() { + var pane = document.createElement("div"); + var label = document.createElement("a"); + var bar = document.createElement("progress"); + var string = gStrings.getStr("profiler.loading"); + + pane.className = "profileProgressPane"; + pane.appendChild(label); + pane.appendChild(bar); + + var reporter = new ProgressReporter(); + reporter.addListener(function (rep) { + var progress = rep.getProgress(); + + if (label.textContent !== string) { + label.textContent = string; + } + + if (isNaN(progress)) { + bar.removeAttribute("value"); + } else { + bar.value = progress; + } + }); + + gMainArea.appendChild(pane); + Parser.updateLogSetting(); + + return reporter; } \ No newline at end of file diff --git a/browser/devtools/profiler/test/Makefile.in b/browser/devtools/profiler/test/Makefile.in index 80591222c688..44a6cb8f4a77 100644 --- a/browser/devtools/profiler/test/Makefile.in +++ b/browser/devtools/profiler/test/Makefile.in @@ -14,6 +14,8 @@ MOCHITEST_BROWSER_FILES = \ browser_profiler_run.js \ browser_profiler_controller.js \ browser_profiler_profiles.js \ + browser_profiler_remote.js \ + browser_profiler_bug_830664_multiple_profiles.js \ head.js \ include $(topsrcdir)/config/rules.mk diff --git a/browser/devtools/profiler/test/browser_profiler_bug_830664_multiple_profiles.js b/browser/devtools/profiler/test/browser_profiler_bug_830664_multiple_profiles.js new file mode 100644 index 000000000000..afe6246298fc --- /dev/null +++ b/browser/devtools/profiler/test/browser_profiler_bug_830664_multiple_profiles.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const URL = "data:text/html;charset=utf8,JavaScript Profiler test
"; + +let gTab, gPanel, gUid; + +function test() { + waitForExplicitFinish(); + + setUp(URL, function onSetUp(tab, browser, panel) { + gTab = tab; + gPanel = panel; + + gPanel.once("profileCreated", function (_, uid) { + gUid = uid; + let profile = gPanel.profiles.get(uid); + + if (profile.isReady) { + startProfiling(); + } else { + profile.once("ready", startProfiling); + } + }); + gPanel.createProfile(); + }); +} + +function getCleoControls(doc) { + return [ + doc.querySelector("#startWrapper button"), + doc.querySelector("#profilerMessage") + ]; +} + +function sendFromActiveProfile(msg) { + let [win, doc] = getProfileInternals(); + + win.parent.postMessage({ + uid: gPanel.activeProfile.uid, + status: msg + }, "*"); +} + +function startProfiling() { + gPanel.profiles.get(gUid).once("disabled", stopProfiling); + sendFromActiveProfile("start"); +} + +function stopProfiling() { + let [win, doc] = getProfileInternals(gUid); + let [btn, msg] = getCleoControls(doc); + + ok(msg.textContent.match("Profile 1") !== null, "Message is visible"); + ok(btn.hasAttribute("disabled"), "Button is disabled"); + + is(gPanel.document.querySelector("li#profile-1 > h1").textContent, + "Profile 1 *", "Profile 1 has a star next to it."); + is(gPanel.document.querySelector("li#profile-2 > h1").textContent, + "Profile 2", "Profile 2 doesn't have a star next to it."); + + gPanel.profiles.get(gUid).once("enabled", confirmAndFinish); + sendFromActiveProfile("stop"); +} + +function confirmAndFinish() { + let [win, doc] = getProfileInternals(gUid); + let [btn, msg] = getCleoControls(doc); + + ok(msg.style.display === "none", "Message is hidden"); + ok(!btn.hasAttribute("disabled"), "Button is enabled"); + + is(gPanel.document.querySelector("li#profile-1 > h1").textContent, + "Profile 1", "Profile 1 doesn't have a star next to it."); + is(gPanel.document.querySelector("li#profile-2 > h1").textContent, + "Profile 2", "Profile 2 doesn't have a star next to it."); + + tearDown(gTab, function onTearDown() { + gPanel = null; + gTab = null; + gUid = null; + }); +} \ No newline at end of file diff --git a/browser/devtools/profiler/test/browser_profiler_remote.js b/browser/devtools/profiler/test/browser_profiler_remote.js new file mode 100644 index 000000000000..450f9d2902e5 --- /dev/null +++ b/browser/devtools/profiler/test/browser_profiler_remote.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const URL = "data:text/html;charset=utf8,JavaScript Profiler test
"; + +let temp = {}; + +Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp); +let DebuggerServer = temp.DebuggerServer; + +Cu.import("resource://gre/modules/devtools/dbg-client.jsm", temp); +let DebuggerClient = temp.DebuggerClient; +let debuggerSocketConnect = temp.debuggerSocketConnect; + +Cu.import("resource:///modules/devtools/ProfilerController.jsm", temp); +let ProfilerController = temp.ProfilerController; + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref(REMOTE_ENABLED, true); + + loadTab(URL, function onTabLoad(tab, browser) { + DebuggerServer.init(function () true); + DebuggerServer.addBrowserActors(); + is(DebuggerServer._socketConnections, 0); + + DebuggerServer.openListener(2929); + is(DebuggerServer._socketConnections, 1); + + let transport = debuggerSocketConnect("127.0.0.1", 2929); + let client = new DebuggerClient(transport); + client.connect(function onClientConnect() { + let target = { isRemote: true, client: client }; + let controller = new ProfilerController(target); + + controller.connect(function onControllerConnect() { + // If this callback is called, this means listTabs call worked. + // Which means that the transport worked. Time to finish up this + // test. + + function onShutdown() { + window.removeEventListener("Debugger:Shutdown", onShutdown, true); + transport = client = null; + finish(); + } + + window.addEventListener("Debugger:Shutdown", onShutdown, true); + + client.close(function () { + gBrowser.removeTab(tab); + }); + }); + }); + }); +} \ No newline at end of file diff --git a/browser/devtools/profiler/test/browser_profiler_run.js b/browser/devtools/profiler/test/browser_profiler_run.js index 136864224279..87eb99458b9f 100644 --- a/browser/devtools/profiler/test/browser_profiler_run.js +++ b/browser/devtools/profiler/test/browser_profiler_run.js @@ -33,13 +33,6 @@ function attemptTearDown() { }); } -function getProfileInternals() { - let win = gPanel.activeProfile.iframe.contentWindow; - let doc = win.document; - - return [win, doc]; -} - function testUI() { ok(gPanel, "Profiler panel exists"); ok(gPanel.activeProfile, "Active profile exists"); diff --git a/browser/devtools/profiler/test/head.js b/browser/devtools/profiler/test/head.js index 53846e54ac6e..a24f45faac8b 100644 --- a/browser/devtools/profiler/test/head.js +++ b/browser/devtools/profiler/test/head.js @@ -3,6 +3,7 @@ let temp = {}; const PROFILER_ENABLED = "devtools.profiler.enabled"; +const REMOTE_ENABLED = "devtools.debugger.remote-enabled"; Cu.import("resource:///modules/devtools/Target.jsm", temp); let TargetFactory = temp.TargetFactory; @@ -10,6 +11,23 @@ let TargetFactory = temp.TargetFactory; Cu.import("resource:///modules/devtools/gDevTools.jsm", temp); let gDevTools = temp.gDevTools; +Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp); +let DebuggerServer = temp.DebuggerServer; + +registerCleanupFunction(function () { + Services.prefs.clearUserPref(PROFILER_ENABLED); + Services.prefs.clearUserPref(REMOTE_ENABLED); + DebuggerServer.destroy(); +}); + +function getProfileInternals(uid) { + let profile = (uid != null) ? gPanel.profiles.get(uid) : gPanel.activeProfile; + let win = profile.iframe.contentWindow; + let doc = win.document; + + return [win, doc]; +} + function loadTab(url, callback) { let tab = gBrowser.addTab(); gBrowser.selectedTab = tab; @@ -63,6 +81,5 @@ function tearDown(tab, callback=function(){}) { } finish(); - Services.prefs.setBoolPref(PROFILER_ENABLED, false); }); -} +} \ No newline at end of file diff --git a/browser/devtools/shared/DeveloperToolbar.jsm b/browser/devtools/shared/DeveloperToolbar.jsm index 13a9ee67acf1..a98033ce31e2 100644 --- a/browser/devtools/shared/DeveloperToolbar.jsm +++ b/browser/devtools/shared/DeveloperToolbar.jsm @@ -27,6 +27,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands", XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener", "resource://gre/modules/devtools/WebConsoleUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + XPCOMUtils.defineLazyGetter(this, "prefBranch", function() { let prefService = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); @@ -622,7 +625,19 @@ function DT__updateErrorsCount(aChangedTabId) let warnings = this._warningsCount[tabId]; let btn = this._errorCounterButton; if (errors) { - let tooltiptext = toolboxStrings.formatStringFromName("toolboxDockButtons.errorsCount.tooltip", [errors, warnings], 2); + let errorsText = toolboxStrings + .GetStringFromName("toolboxToggleButton.errorsCount"); + errorsText = PluralForm.get(errors, errorsText); + + let warningsText = toolboxStrings + .GetStringFromName("toolboxToggleButton.warningsCount"); + warningsText = PluralForm.get(warnings, warningsText); + + let tooltiptext = toolboxStrings + .formatStringFromName("toolboxToggleButton.tooltiptext", + [errors, errorsText, warnings, + warningsText], 4); + btn.setAttribute("error-count", errors); btn.setAttribute("tooltiptext", tooltiptext); } else { diff --git a/browser/devtools/shared/VariablesView.jsm b/browser/devtools/shared/VariablesView.jsm index f7d250ad4040..c7d4ea0b8d7d 100644 --- a/browser/devtools/shared/VariablesView.jsm +++ b/browser/devtools/shared/VariablesView.jsm @@ -2140,8 +2140,20 @@ create({ constructor: Variable, proto: Scope.prototype }, { separatorLabel.hidden = true; valueLabel.hidden = true; - this.delete = VariablesView.getterOrSetterDeleteCallback; - this.evaluationMacro = VariablesView.overrideValueEvalMacro; + // Changing getter/setter names is never allowed. + this.switch = null; + + // Getter/setter properties require special handling when it comes to + // evaluation and deletion. + if (this.ownerView.eval) { + this.delete = VariablesView.getterOrSetterDeleteCallback; + this.evaluationMacro = VariablesView.overrideValueEvalMacro; + } + // Deleting getters and setters individually is not allowed if no + // evaluation method is provided. + else { + this.delete = null; + } let getter = this.addProperty("get", { value: descriptor.get }); let setter = this.addProperty("set", { value: descriptor.set }); diff --git a/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js b/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js index b3d6b291568f..85d8d825a9f8 100644 --- a/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js +++ b/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js @@ -44,7 +44,7 @@ function test() { function getTooltipValues() { let matches = webconsole.getAttribute("tooltiptext") - .match(/(\d+) errors, (\d+) warnings/); + .match(/(\d+) errors?, (\d+) warnings?/); return matches ? [matches[1], matches[2]] : [0, 0]; } diff --git a/browser/devtools/styleeditor/StyleEditor.jsm b/browser/devtools/styleeditor/StyleEditor.jsm index 8052b14d72ac..db8aaabe56c1 100644 --- a/browser/devtools/styleeditor/StyleEditor.jsm +++ b/browser/devtools/styleeditor/StyleEditor.jsm @@ -130,7 +130,7 @@ StyleEditor.prototype = { { let document = this.contentDocument; if (this._styleSheetIndex == -1) { - for (let i = 0; i < document.styleSheets.length; ++i) { + for (let i = 0; i < document.styleSheets.length; i++) { if (document.styleSheets[i] == this.styleSheet) { this._styleSheetIndex = i; break; @@ -1004,8 +1004,9 @@ StyleEditor.prototype = { // copy the list of listeners to allow adding/removing listeners in handlers let listeners = this._actionListeners.concat(); + // trigger all listeners that have this action handler - for (let i = 0; i < listeners.length; ++i) { + for (let i = 0; i < listeners.length; i++) { let listener = listeners[i]; let actionHandler = listener["on" + aName]; if (actionHandler) { diff --git a/browser/devtools/styleeditor/StyleEditorChrome.jsm b/browser/devtools/styleeditor/StyleEditorChrome.jsm index a8da29eb837a..860144b5f273 100644 --- a/browser/devtools/styleeditor/StyleEditorChrome.jsm +++ b/browser/devtools/styleeditor/StyleEditorChrome.jsm @@ -16,6 +16,7 @@ Cu.import("resource://gre/modules/PluralForm.jsm"); Cu.import("resource:///modules/devtools/StyleEditor.jsm"); Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm"); Cu.import("resource:///modules/devtools/SplitView.jsm"); +Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js"); const STYLE_EDITOR_TEMPLATE = "stylesheet"; @@ -43,34 +44,43 @@ this.StyleEditorChrome = function StyleEditorChrome(aRoot, aContentWindow) this._editors = []; this._listeners = []; // @see addChromeListener + // Store the content window so that we can call the real contentWindow setter + // in the open method. + this._contentWindowTemp = aContentWindow; + this._contentWindow = null; - this._isContentAttached = false; - - let initializeUI = function (aEvent) { - if (aEvent) { - this._window.removeEventListener("load", initializeUI, false); - } - - let viewRoot = this._root.parentNode.querySelector(".splitview-root"); - this._view = new SplitView(viewRoot); - - this._setupChrome(); - - // attach to the content window - this.contentWindow = aContentWindow; - this._contentWindowID = null; - }.bind(this); - - if (this._document.readyState == "complete") { - initializeUI(); - } else { - this._window.addEventListener("load", initializeUI, false); - } } StyleEditorChrome.prototype = { _styleSheetToSelect: null, + open: function() { + let deferred = Promise.defer(); + let initializeUI = function (aEvent) { + if (aEvent) { + this._window.removeEventListener("load", initializeUI, false); + } + let viewRoot = this._root.parentNode.querySelector(".splitview-root"); + this._view = new SplitView(viewRoot); + this._setupChrome(); + + // We need to juggle arount the contentWindow items because we need to + // trigger the setter at the appropriate time. + this.contentWindow = this._contentWindowTemp; // calls setter + this._contentWindowTemp = null; + + deferred.resolve(); + }.bind(this); + + if (this._document.readyState == "complete") { + initializeUI(); + } else { + this._window.addEventListener("load", initializeUI, false); + } + + return deferred.promise; + }, + /** * Retrieve the content window attached to this chrome. * @@ -150,15 +160,6 @@ StyleEditorChrome.prototype = { return this._contentWindow ? this._contentWindow.document : null; }, - /** - * Retrieve whether the content has been attached and StyleEditor instances - * exist for all of its stylesheets. - * - * @return boolean - * @see addChromeListener - */ - get isContentAttached() this._isContentAttached, - /** * Retrieve an array with the StyleEditor instance for each live style sheet, * ordered by style sheet index. @@ -180,14 +181,6 @@ StyleEditorChrome.prototype = { * Add a listener for StyleEditorChrome events. * * The listener implements IStyleEditorChromeListener := { - * onContentAttach: Called when a content window has been attached. - * All editors are instantiated, though they might - * not be loaded yet. - * Arguments: (StyleEditorChrome aChrome) - * @see contentWindow - * @see StyleEditor.isLoaded - * @see StyleEditor.addActionListener - * * onContentDetach: Called when the content window has been detached. * Arguments: (StyleEditorChrome aChrome) * @see contentWindow @@ -287,7 +280,7 @@ StyleEditorChrome.prototype = { // (re)enable UI let matches = this._root.querySelectorAll("toolbarbutton,input,select"); - for (let i = 0; i < matches.length; ++i) { + for (let i = 0; i < matches.length; i++) { matches[i].removeAttribute("disabled"); } }, @@ -305,7 +298,7 @@ StyleEditorChrome.prototype = { this._document.title = _("chromeWindowTitle", document.title || document.location.href); - for (let i = 0; i < document.styleSheets.length; ++i) { + for (let i = 0; i < document.styleSheets.length; i++) { let styleSheet = document.styleSheets[i]; let editor = new StyleEditor(document, styleSheet); @@ -313,8 +306,6 @@ StyleEditorChrome.prototype = { this._editors.push(editor); } - this._triggerChromeListeners("ContentAttach"); - // Queue editors loading so that ContentAttach is consistently triggered // right after all editor instances are available (this.editors) but are // NOT loaded/ready yet. This also helps responsivity during loading when @@ -353,43 +344,36 @@ StyleEditorChrome.prototype = { let select = function DEC_select(aEditor) { let sheet = this._styleSheetToSelect.sheet; - let line = this._styleSheetToSelect.line; - let col = this._styleSheetToSelect.col; - let summary = sheet ? this.getSummaryElementForEditor(aEditor) - : this._view.getSummaryElementByOrdinal(0); + let line = this._styleSheetToSelect.line || 1; + let col = this._styleSheetToSelect.col || 1; - if (line) { - col = col || 1; - - if (!aEditor.sourceEditor) { - // If a line or column was specified we move the caret appropriately. - let self = this; - aEditor.addActionListener({ - onAttach: function SEC_selectSheet_onAttach() - { - aEditor.removeActionListener(this); - self.selectedStyleSheetIndex = aEditor.styleSheetIndex; - aEditor.sourceEditor.setCaretPosition(line - 1, col - 1); - - let newSheet = self._styleSheetToSelect.sheet; - let newLine = self._styleSheetToSelect.line; - let newCol = self._styleSheetToSelect.col; - self._styleSheetToSelect = null; - if (newSheet != sheet) { - self._window.setTimeout(self.selectStyleSheet.bind(self, newSheet, newLine, newCol), 0); - } - } - }); - } else { - // If a line or column was specified we move the caret appropriately. + if (!aEditor.sourceEditor) { + let onAttach = function SEC_selectSheet_onAttach() { + aEditor.removeActionListener(this); + this.selectedStyleSheetIndex = aEditor.styleSheetIndex; aEditor.sourceEditor.setCaretPosition(line - 1, col - 1); + + let newSheet = this._styleSheetToSelect.sheet; + let newLine = this._styleSheetToSelect.line; + let newCol = this._styleSheetToSelect.col; this._styleSheetToSelect = null; - } + if (newSheet != sheet) { + this.selectStyleSheet.bind(this, newSheet, newLine, newCol); + } + }.bind(this); + + aEditor.addActionListener({ + onAttach: onAttach + }); } else { + // If a line or column was specified we move the caret appropriately. + aEditor.sourceEditor.setCaretPosition(line - 1, col - 1); this._styleSheetToSelect = null; } - this._view.activeSummary = summary; + let summary = sheet ? this.getSummaryElementForEditor(aEditor) + : this._view.getSummaryElementByOrdinal(0); + this._view.activeSummary = summary; this.selectedStyleSheetIndex = aEditor.styleSheetIndex; }.bind(this); @@ -404,6 +388,7 @@ StyleEditorChrome.prototype = { if ((sheet && aEditor.styleSheet == sheet) || (aEditor.styleSheetIndex == 0 && sheet == null)) { aChrome.removeChromeListener(this); + aEditor.addActionListener(self); select(aEditor); } } @@ -430,7 +415,7 @@ StyleEditorChrome.prototype = { _disableChrome: function SEC__disableChrome() { let matches = this._root.querySelectorAll("button,toolbarbutton,textbox"); - for (let i = 0; i < matches.length; ++i) { + for (let i = 0; i < matches.length; i++) { matches[i].setAttribute("disabled", "disabled"); } diff --git a/browser/devtools/styleeditor/StyleEditorPanel.jsm b/browser/devtools/styleeditor/StyleEditorPanel.jsm index 7122a495fe85..f84b856d1412 100644 --- a/browser/devtools/styleeditor/StyleEditorPanel.jsm +++ b/browser/devtools/styleeditor/StyleEditorPanel.jsm @@ -39,10 +39,14 @@ StyleEditorPanel.prototype = { */ open: function StyleEditor_open() { let contentWin = this._toolbox.target.window; - this.setPage(contentWin); - this.isReady = true; + let deferred = Promise.defer(); - return Promise.resolve(this); + this.setPage(contentWin).then(function() { + this.isReady = true; + deferred.resolve(this); + }.bind(this)); + + return deferred.promise; }, /** @@ -66,12 +70,16 @@ StyleEditorPanel.prototype = { setPage: function StyleEditor_setPage(contentWindow) { if (this._panelWin.styleEditorChrome) { this._panelWin.styleEditorChrome.contentWindow = contentWindow; + this.selectStyleSheet(null, null, null); } else { let chromeRoot = this._panelDoc.getElementById("style-editor-chrome"); let chrome = new StyleEditorChrome(chromeRoot, contentWindow); + let promise = chrome.open(); + this._panelWin.styleEditorChrome = chrome; + this.selectStyleSheet(null, null, null); + return promise; } - this.selectStyleSheet(null, null, null); }, /** diff --git a/browser/devtools/styleeditor/StyleEditorUtil.jsm b/browser/devtools/styleeditor/StyleEditorUtil.jsm index 7ef4a10c6fec..f083a47c8f3d 100644 --- a/browser/devtools/styleeditor/StyleEditorUtil.jsm +++ b/browser/devtools/styleeditor/StyleEditorUtil.jsm @@ -152,7 +152,7 @@ this.wire = function wire(aRoot, aSelectorOrElement, aDescriptor) aDescriptor = {events: {click: aDescriptor}}; } - for (let i = 0; i < matches.length; ++i) { + for (let i = 0; i < matches.length; i++) { let element = matches[i]; forEach(aDescriptor.events, function (aName, aHandler) { element.addEventListener(aName, aHandler, false); diff --git a/browser/devtools/styleeditor/test/Makefile.in b/browser/devtools/styleeditor/test/Makefile.in index f458d19c2a6b..addbb245203f 100644 --- a/browser/devtools/styleeditor/test/Makefile.in +++ b/browser/devtools/styleeditor/test/Makefile.in @@ -18,7 +18,7 @@ _BROWSER_TEST_FILES = \ browser_styleeditor_cmd_edit.html \ browser_styleeditor_import.js \ browser_styleeditor_init.js \ - $(filter disabled-temporarily--bug-817294, browser_styleeditor_loading.js) \ + browser_styleeditor_loading.js \ browser_styleeditor_new.js \ browser_styleeditor_passedinsheet.js \ browser_styleeditor_pretty.js \ @@ -32,6 +32,7 @@ _BROWSER_TEST_FILES = \ four.html \ head.js \ helpers.js \ + longload.html \ media.html \ media-small.css \ minified.html \ diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_import.js b/browser/devtools/styleeditor/test/browser_styleeditor_import.js index f8d15d8a463a..cd6db75ace81 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_import.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_import.js @@ -19,12 +19,9 @@ function test() addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) { aChrome.addChromeListener({ - onContentAttach: run, onEditorAdded: testEditorAdded }); - if (aChrome.isContentAttached) { - run(aChrome); - } + run(aChrome); }); content.location = TESTCASE_URI; diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_init.js b/browser/devtools/styleeditor/test/browser_styleeditor_init.js index 7835c7c15ba2..c106e3db9bc1 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js @@ -9,23 +9,17 @@ function test() { waitForExplicitFinish(); - addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) { + launchStyleEditorChrome(function(aChrome) { aChrome.addChromeListener({ - onContentAttach: run, onEditorAdded: testEditorAdded }); - if (aChrome.isContentAttached) { - run(aChrome); - } + run(aChrome); }); - content.location = TESTCASE_URI; } -let gContentAttachHandled = false; function run(aChrome) { - gContentAttachHandled = true; is(aChrome.contentWindow.document.readyState, "complete", "content document is complete"); @@ -42,11 +36,6 @@ function run(aChrome) let gEditorAddedCount = 0; function testEditorAdded(aChrome, aEditor) { - if (!gEditorAddedCount) { - is(gContentAttachHandled, true, - "ContentAttach event triggered before EditorAdded"); - } - if (aEditor.styleSheetIndex == 0) { gEditorAddedCount++; testFirstStyleSheetEditor(aChrome, aEditor); diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_loading.js b/browser/devtools/styleeditor/test/browser_styleeditor_loading.js index 07331c6e092c..c422785fc84d 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_loading.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_loading.js @@ -2,7 +2,7 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -const TESTCASE_URI = TEST_BASE + "simple.html"; +const TESTCASE_URI = TEST_BASE + "longload.html"; function test() @@ -13,52 +13,25 @@ function test() // launch Style Editor right when the tab is created (before load) // this checks that the Style Editor still launches correctly when it is opened - // *while* the page is still loading + // *while* the page is still loading. The Style Editor should not signal that + // it is loaded until the accompanying content page is loaded. launchStyleEditorChrome(function (aChrome) { content.location = TESTCASE_URI; - executeSoon(function() { - isnot(gBrowser.selectedBrowser.contentWindow.document.readyState, "complete", - "content document is still loading"); + is(aChrome.contentWindow.document.readyState, "complete", + "content document is complete"); let root = gChromeWindow.document.querySelector(".splitview-root"); - ok(root.classList.contains("loading"), - "style editor root element has 'loading' class name"); + ok(!root.classList.contains("loading"), + "style editor root element does not have 'loading' class name anymore"); let button = gChromeWindow.document.querySelector(".style-editor-newButton"); - ok(button.hasAttribute("disabled"), - "new style sheet button is disabled"); + ok(!button.hasAttribute("disabled"), + "new style sheet button is enabled"); button = gChromeWindow.document.querySelector(".style-editor-importButton"); - ok(button.hasAttribute("disabled"), - "import button is disabled"); + ok(!button.hasAttribute("disabled"), + "import button is enabled"); - if (!aChrome.isContentAttached) { - aChrome.addChromeListener({ - onContentAttach: run - }); - } else { - run(aChrome); - } - }); + finish(); }); } - -function run(aChrome) -{ - is(aChrome.contentWindow.document.readyState, "complete", - "content document is complete"); - - let root = gChromeWindow.document.querySelector(".splitview-root"); - ok(!root.classList.contains("loading"), - "style editor root element does not have 'loading' class name anymore"); - - let button = gChromeWindow.document.querySelector(".style-editor-newButton"); - ok(!button.hasAttribute("disabled"), - "new style sheet button is enabled"); - - button = gChromeWindow.document.querySelector(".style-editor-importButton"); - ok(!button.hasAttribute("disabled"), - "import button is enabled"); - - finish(); -} diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_new.js b/browser/devtools/styleeditor/test/browser_styleeditor_new.js index f8de9da49508..3d05336123c1 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_new.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_new.js @@ -13,12 +13,9 @@ function test() addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) { aChrome.addChromeListener({ - onContentAttach: run, onEditorAdded: testEditorAdded }); - if (aChrome.isContentAttached) { - run(aChrome); - } + run(aChrome); }); content.location = TESTCASE_URI; diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js b/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js index 58ac1796b727..13210fad6bcb 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js @@ -19,13 +19,7 @@ function test() { aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); cache.evictEntries(Ci.nsICache.STORE_ANYWHERE); launchStyleEditorChromeFromWindow(aWindow, function(aChrome) { - if (aChrome.isContentAttached) { - onEditorAdded(aChrome, aChrome.editors[0]); - } else { - aChrome.addChromeListener({ - onEditorAdded: onEditorAdded - }); - } + onEditorAdded(aChrome, aChrome.editors[0]); }); }, true); diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js b/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js index e734f7d0237a..151df51e64db 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js @@ -10,12 +10,7 @@ function test() waitForExplicitFinish(); addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) { - aChrome.addChromeListener({ - onContentAttach: run - }); - if (aChrome.isContentAttached) { - run(aChrome); - } + run(aChrome); }); content.location = TESTCASE_URI; diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js b/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js index 4c2766363b77..37f2e7cf55b3 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js @@ -12,15 +12,8 @@ function test() waitForExplicitFinish(); addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) { - if (aChrome.isContentAttached) { - run(aChrome); - } else { - aChrome.addChromeListener({ - onContentAttach: run - }); - } + run(aChrome); }); - content.location = TESTCASE_URI; } @@ -31,13 +24,13 @@ function run(aChrome) aChrome.editors[0].addActionListener({ onAttach: function onEditorAttached(aEditor) { - let originalSourceEditor = aEditor.sourceEditor; - aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved - - // queue a resize to inverse aspect ratio - // this will trigger a detach and reattach (to workaround bug 254144) executeSoon(function () { waitForFocus(function () { + // queue a resize to inverse aspect ratio + // this will trigger a detach and reattach (to workaround bug 254144) + let originalSourceEditor = aEditor.sourceEditor; + aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved + gOriginalWidth = gChromeWindow.outerWidth; gOriginalHeight = gChromeWindow.outerHeight; gChromeWindow.resizeTo(120, 480); diff --git a/browser/devtools/styleeditor/test/longload.html b/browser/devtools/styleeditor/test/longload.html new file mode 100644 index 000000000000..f4c147589754 --- /dev/null +++ b/browser/devtools/styleeditor/test/longload.html @@ -0,0 +1,28 @@ + + + +