merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2014-09-04 15:03:28 +02:00
commit 1f2744b05d
20 changed files with 486 additions and 96 deletions

View File

@ -80,10 +80,13 @@ WebappsStore.prototype = {
}, },
_feedStore: function() { _feedStore: function() {
if (!this._webAppsActor) {
return promise.resolve();
}
this._listenToApps(); this._listenToApps();
return this._getAllApps() return this._getAllApps()
.then(this._getRunningApps.bind(this)) .then(this._getRunningApps.bind(this))
.then(this._getAppsIcons.bind(this)) .then(this._getAppsIcons.bind(this));
}, },
_listenToApps: function() { _listenToApps: function() {

View File

@ -32,30 +32,12 @@ function performTest() {
info("process name: " + gProcess._dbgProcess.processName); info("process name: " + gProcess._dbgProcess.processName);
info("process sig: " + gProcess._dbgProcess.processSignature); info("process sig: " + gProcess._dbgProcess.processSignature);
ok(gProcess._dbgProfile, ok(gProcess._dbgProfilePath,
"The remote debugger profile wasn't created properly!"); "The remote debugger profile wasn't created properly!");
ok(gProcess._dbgProfile.localDir, is(gProcess._dbgProfilePath, OS.Path.join(OS.Constants.Path.profileDir, "chrome_debugger_profile"),
"The remote debugger profile doesn't have a localDir..."); "The remote debugger profile isn't where we expect it!");
ok(gProcess._dbgProfile.rootDir,
"The remote debugger profile doesn't have a rootDir...");
ok(gProcess._dbgProfile.name,
"The remote debugger profile doesn't have a name...");
info("profile localDir: " + gProcess._dbgProfile.localDir.path); info("profile path: " + gProcess._dbgProfilePath);
info("profile rootDir: " + gProcess._dbgProfile.rootDir.path);
info("profile name: " + gProcess._dbgProfile.name);
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
.createInstance(Ci.nsIToolkitProfileService);
let profile = profileService.getProfileByName(gProcess._dbgProfile.name);
ok(profile,
"The remote debugger profile wasn't *actually* created properly!");
is(profile.localDir.path, gProcess._dbgProfile.localDir.path,
"The remote debugger profile doesn't have the correct localDir!");
is(profile.rootDir.path, gProcess._dbgProfile.rootDir.path,
"The remote debugger profile doesn't have the correct rootDir!");
gProcess.close(); gProcess.close();
} }
@ -68,9 +50,7 @@ function aOnClose() {
info("process exit value: " + gProcess._dbgProcess.exitValue); info("process exit value: " + gProcess._dbgProcess.exitValue);
info("profile localDir: " + gProcess._dbgProfile.localDir.path); info("profile path: " + gProcess._dbgProfilePath);
info("profile rootDir: " + gProcess._dbgProfile.rootDir.path);
info("profile name: " + gProcess._dbgProfile.name);
finish(); finish();
} }

View File

@ -8,7 +8,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components; const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const DBG_XUL = "chrome://browser/content/devtools/framework/toolbox-process-window.xul"; const DBG_XUL = "chrome://browser/content/devtools/framework/toolbox-process-window.xul";
const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger"; const CHROME_DEBUGGER_PROFILE_NAME = "chrome_debugger_profile";
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm") Cu.import("resource://gre/modules/XPCOMUtils.jsm")
@ -154,54 +154,33 @@ BrowserToolboxProcess.prototype = {
_initProfile: function() { _initProfile: function() {
dumpn("Initializing the chrome toolbox user profile."); dumpn("Initializing the chrome toolbox user profile.");
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"] let debuggingProfileDir = Services.dirsvc.get("ProfLD", Ci.nsIFile);
.createInstance(Ci.nsIToolkitProfileService); debuggingProfileDir.append(CHROME_DEBUGGER_PROFILE_NAME);
let profileName;
try { try {
// Attempt to get the required chrome debugging profile name string. debuggingProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME; } catch (ex) {
dumpn("Using chrome toolbox profile name: " + profileName); if (ex.result !== Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
} catch (e) { dumpn("Error trying to create a profile directory, failing.");
// Requested profile string could not be retrieved. dumpn("Error: " + (ex.message || ex));
profileName = CHROME_DEBUGGER_PROFILE_NAME; return;
let msg = "Querying the current profile failed. " + e.name + ": " + e.message;
dumpn(msg);
Cu.reportError(msg);
}
let profileObject;
try {
// Attempt to get the required chrome debugging profile toolkit object.
profileObject = profileService.getProfileByName(profileName);
dumpn("Using chrome toolbox profile object: " + profileObject);
// The profile exists but the corresponding folder may have been deleted.
var enumerator = Services.dirsvc.get("ProfD", Ci.nsIFile).parent.directoryEntries;
while (enumerator.hasMoreElements()) {
let profileDir = enumerator.getNext().QueryInterface(Ci.nsIFile);
if (profileDir.leafName.contains(profileName)) {
// Requested profile was found and the folder exists.
this._dbgProfile = profileObject;
return;
}
} }
// Requested profile was found but the folder was deleted. Cleanup needed.
profileObject.remove(true);
dumpn("The already existing chrome toolbox profile was invalid.");
} catch (e) {
// Requested profile object was not found.
let msg = "Creating a profile failed. " + e.name + ": " + e.message;
dumpn(msg);
Cu.reportError(msg);
} }
// Create a new chrome debugging profile. this._dbgProfilePath = debuggingProfileDir.path;
this._dbgProfile = profileService.createProfile(null, profileName);
profileService.flush();
dumpn("Finished creating the chrome toolbox user profile."); // We would like to copy prefs into this new profile...
dumpn("Flushed profile service with: " + profileName); let prefsFile = debuggingProfileDir.clone();
prefsFile.append("prefs.js");
// ... but unfortunately, when we run tests, it seems the starting profile
// clears out the prefs file before re-writing it, and in practice the
// file is empty when we get here. So just copying doesn't work in that
// case.
// We could force a sync pref flush and then copy it... but if we're doing
// that, we might as well just flush directly to the new profile, which
// always works:
Services.prefs.savePrefFile(prefsFile);
dumpn("Finished creating the chrome toolbox user profile at: " + this._dbgProfilePath);
}, },
/** /**
@ -219,7 +198,7 @@ BrowserToolboxProcess.prototype = {
} }
dumpn("Running chrome debugging process."); dumpn("Running chrome debugging process.");
let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", xulURI]; let args = ["-no-remote", "-foreground", "-profile", this._dbgProfilePath, "-chrome", xulURI];
process.runwAsync(args, args.length, { observe: () => this.close() }); process.runwAsync(args, args.length, { observe: () => this.close() });

View File

@ -1419,7 +1419,7 @@ CssRuleView.prototype = {
this.element.appendChild(editor.element); this.element.appendChild(editor.element);
} else { } else {
for (let rule of rules) { for (let rule of rules) {
if (rule.selectorText === "element") { if (rule.domRule.type === ELEMENT_STYLE) {
let referenceElement = rule.editor.element.nextSibling; let referenceElement = rule.editor.element.nextSibling;
this.element.insertBefore(editor.element, referenceElement); this.element.insertBefore(editor.element, referenceElement);
break; break;

View File

@ -84,7 +84,7 @@ function CheckLockState() {
if (AppManager.connection && if (AppManager.connection &&
AppManager.connection.status == Connection.Status.CONNECTED && AppManager.connection.status == Connection.Status.CONNECTED &&
AppManager.deviceFront) { AppManager.preferenceFront) {
// ADB check // ADB check
if (AppManager.selectedRuntime instanceof USBRuntime) { if (AppManager.selectedRuntime instanceof USBRuntime) {

View File

@ -114,7 +114,8 @@ let UI = {
// not focused. // not focused.
if (AppManager.selectedProject && if (AppManager.selectedProject &&
AppManager.selectedProject.type != "mainProcess" && AppManager.selectedProject.type != "mainProcess" &&
AppManager.selectedProject.type != "runtimeApp") { AppManager.selectedProject.type != "runtimeApp" &&
AppManager.selectedProject.type != "tab") {
AppManager.validateProject(AppManager.selectedProject); AppManager.validateProject(AppManager.selectedProject);
} }
}, },
@ -138,6 +139,7 @@ let UI = {
break; break;
case "project-is-not-running": case "project-is-not-running":
case "project-is-running": case "project-is-running":
case "list-tabs-response":
this.updateCommands(); this.updateCommands();
break; break;
case "runtime": case "runtime":
@ -562,6 +564,9 @@ let UI = {
// If connected and a project is selected // If connected and a project is selected
if (AppManager.selectedProject.type == "runtimeApp") { if (AppManager.selectedProject.type == "runtimeApp") {
playCmd.removeAttribute("disabled"); playCmd.removeAttribute("disabled");
} else if (AppManager.selectedProject.type == "tab") {
playCmd.removeAttribute("disabled");
stopCmd.setAttribute("disabled", "true");
} else if (AppManager.selectedProject.type == "mainProcess") { } else if (AppManager.selectedProject.type == "mainProcess") {
playCmd.setAttribute("disabled", "true"); playCmd.setAttribute("disabled", "true");
stopCmd.setAttribute("disabled", "true"); stopCmd.setAttribute("disabled", "true");
@ -592,16 +597,18 @@ let UI = {
let runtimePanelButton = document.querySelector("#runtime-panel-button"); let runtimePanelButton = document.querySelector("#runtime-panel-button");
if (AppManager.connection.status == Connection.Status.CONNECTED) { if (AppManager.connection.status == Connection.Status.CONNECTED) {
screenshotCmd.removeAttribute("disabled"); if (AppManager.deviceFront) {
permissionsCmd.removeAttribute("disabled"); detailsCmd.removeAttribute("disabled");
permissionsCmd.removeAttribute("disabled");
screenshotCmd.removeAttribute("disabled");
}
disconnectCmd.removeAttribute("disabled"); disconnectCmd.removeAttribute("disabled");
detailsCmd.removeAttribute("disabled");
runtimePanelButton.setAttribute("active", "true"); runtimePanelButton.setAttribute("active", "true");
} else { } else {
screenshotCmd.setAttribute("disabled", "true");
permissionsCmd.setAttribute("disabled", "true");
disconnectCmd.setAttribute("disabled", "true");
detailsCmd.setAttribute("disabled", "true"); detailsCmd.setAttribute("disabled", "true");
permissionsCmd.setAttribute("disabled", "true");
screenshotCmd.setAttribute("disabled", "true");
disconnectCmd.setAttribute("disabled", "true");
runtimePanelButton.removeAttribute("active"); runtimePanelButton.removeAttribute("active");
} }
@ -822,7 +829,13 @@ let Cmds = {
let runtimeappsHeaderNode = document.querySelector("#panel-header-runtimeapps"); let runtimeappsHeaderNode = document.querySelector("#panel-header-runtimeapps");
if (AppManager.connection.status == Connection.Status.CONNECTED) { let sortedApps = AppManager.webAppsStore.object.all;
sortedApps = sortedApps.sort((a, b) => {
return a.name > b.name;
});
let mainProcess = AppManager.isMainProcessDebuggable();
if (AppManager.connection.status == Connection.Status.CONNECTED &&
(sortedApps.length > 0 || mainProcess)) {
runtimeappsHeaderNode.removeAttribute("hidden"); runtimeappsHeaderNode.removeAttribute("hidden");
} else { } else {
runtimeappsHeaderNode.setAttribute("hidden", "true"); runtimeappsHeaderNode.setAttribute("hidden", "true");
@ -833,7 +846,7 @@ let Cmds = {
runtimeAppsNode.firstChild.remove(); runtimeAppsNode.firstChild.remove();
} }
if (AppManager.isMainProcessDebuggable()) { if (mainProcess) {
let panelItemNode = document.createElement("toolbarbutton"); let panelItemNode = document.createElement("toolbarbutton");
panelItemNode.className = "panel-item"; panelItemNode.className = "panel-item";
panelItemNode.setAttribute("label", Strings.GetStringFromName("mainProcess_label")); panelItemNode.setAttribute("label", Strings.GetStringFromName("mainProcess_label"));
@ -849,10 +862,6 @@ let Cmds = {
}, true); }, true);
} }
let sortedApps = AppManager.webAppsStore.object.all;
sortedApps = sortedApps.sort((a, b) => {
return a.name > b.name;
});
for (let i = 0; i < sortedApps.length; i++) { for (let i = 0; i < sortedApps.length; i++) {
let app = sortedApps[i]; let app = sortedApps[i];
let panelItemNode = document.createElement("toolbarbutton"); let panelItemNode = document.createElement("toolbarbutton");
@ -871,9 +880,63 @@ let Cmds = {
}, true); }, true);
} }
// Build the tab list right now, so it's fast...
this._buildProjectPanelTabs();
// But re-list them and rebuild, in case any tabs navigated since the last
// time they were listed.
AppManager.listTabs().then(() => {
this._buildProjectPanelTabs();
});
return deferred.promise; return deferred.promise;
}, },
_buildProjectPanelTabs: function() {
let tabs = AppManager.tabStore.tabs;
let tabsHeaderNode = document.querySelector("#panel-header-tabs");
if (AppManager.connection.status == Connection.Status.CONNECTED &&
tabs.length > 0) {
tabsHeaderNode.removeAttribute("hidden");
} else {
tabsHeaderNode.setAttribute("hidden", "true");
}
let tabsNode = document.querySelector("#project-panel-tabs");
while (tabsNode.hasChildNodes()) {
tabsNode.firstChild.remove();
}
for (let i = 0; i < tabs.length; i++) {
let tab = tabs[i];
let url = new URL(tab.url);
// Wanted to use nsIFaviconService here, but it only works for visited
// tabs, so that's no help for any remote tabs. Maybe some favicon wizard
// knows how to get high-res favicons easily, or we could offer actor
// support for this (bug 1061654).
tab.favicon = url.origin + "/favicon.ico";
tab.name = tab.title || Strings.GetStringFromName("project_tab_loading");
if (url.protocol.startsWith("http")) {
tab.name = url.hostname + ": " + tab.name;
}
let panelItemNode = document.createElement("toolbarbutton");
panelItemNode.className = "panel-item";
panelItemNode.setAttribute("label", tab.name);
panelItemNode.setAttribute("image", tab.favicon);
tabsNode.appendChild(panelItemNode);
panelItemNode.addEventListener("click", () => {
UI.hidePanels();
AppManager.selectedProject = {
type: "tab",
app: tab,
icon: tab.favicon,
location: tab.url,
name: tab.name
};
}, true);
}
},
showRuntimePanel: function() { showRuntimePanel: function() {
AppManager.scanForWiFiRuntimes(); AppManager.scanForWiFiRuntimes();
@ -924,6 +987,8 @@ let Cmds = {
return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app"); return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
case "runtimeApp": case "runtimeApp":
return UI.busyUntil(AppManager.runRuntimeApp(), "running app"); return UI.busyUntil(AppManager.runRuntimeApp(), "running app");
case "tab":
return UI.busyUntil(AppManager.reloadTab(), "reloading tab");
} }
return promise.reject(); return promise.reject();
}, },

View File

@ -117,7 +117,7 @@
<hbox id="panel-buttons-container"> <hbox id="panel-buttons-container">
<toolbarbutton id="project-panel-button" class="panel-button no-project" command="cmd_showProjectPanel"> <toolbarbutton id="project-panel-button" class="panel-button no-project" command="cmd_showProjectPanel">
<image class="panel-button-image"/> <image class="panel-button-image"/>
<label class="panel-button-label" value="&projectButton_label;"/> <label class="panel-button-label" value="&projectButton_label;" crop="end"/>
<image class="panel-button-anchor"/> <image class="panel-button-anchor"/>
</toolbarbutton> </toolbarbutton>
<spacer flex="1"/> <spacer flex="1"/>
@ -143,6 +143,8 @@
<vbox id="project-panel-projects"></vbox> <vbox id="project-panel-projects"></vbox>
<label class="panel-header" id="panel-header-runtimeapps" hidden="true">&projectPanel_runtimeApps;</label> <label class="panel-header" id="panel-header-runtimeapps" hidden="true">&projectPanel_runtimeApps;</label>
<vbox flex="1" id="project-panel-runtimeapps"/> <vbox flex="1" id="project-panel-runtimeapps"/>
<label class="panel-header" id="panel-header-tabs" hidden="true">&projectPanel_tabs;</label>
<vbox flex="1" id="project-panel-tabs"/>
</vbox> </vbox>
</panel> </panel>

View File

@ -15,6 +15,7 @@ const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.
const {TextEncoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); const {TextEncoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
const {AppProjects} = require("devtools/app-manager/app-projects"); const {AppProjects} = require("devtools/app-manager/app-projects");
const WebappsStore = require("devtools/app-manager/webapps-store"); const WebappsStore = require("devtools/app-manager/webapps-store");
const TabStore = require("devtools/webide/tab-store");
const {AppValidator} = require("devtools/app-manager/app-validator"); const {AppValidator} = require("devtools/app-manager/app-validator");
const {ConnectionManager, Connection} = require("devtools/client/connection-manager"); const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
const AppActorFront = require("devtools/app-actor-front"); const AppActorFront = require("devtools/app-actor-front");
@ -25,6 +26,7 @@ const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {USBRuntime, WiFiRuntime, SimulatorRuntime, const {USBRuntime, WiFiRuntime, SimulatorRuntime,
gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes"); gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
const discovery = require("devtools/toolkit/discovery/discovery"); const discovery = require("devtools/toolkit/discovery/discovery");
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties"); const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
@ -47,6 +49,11 @@ exports.AppManager = AppManager = {
this.onWebAppsStoreready = this.onWebAppsStoreready.bind(this); this.onWebAppsStoreready = this.onWebAppsStoreready.bind(this);
this.webAppsStore = new WebappsStore(this.connection); this.webAppsStore = new WebappsStore(this.connection);
this.webAppsStore.on("store-ready", this.onWebAppsStoreready); this.webAppsStore.on("store-ready", this.onWebAppsStoreready);
this.tabStore = new TabStore(this.connection);
this.onTabNavigate = this.onTabNavigate.bind(this);
this.onTabClosed = this.onTabClosed.bind(this);
this.tabStore.on("navigate", this.onTabNavigate);
this.tabStore.on("closed", this.onTabClosed);
this.runtimeList = { this.runtimeList = {
usb: [], usb: [],
@ -81,6 +88,10 @@ exports.AppManager = AppManager = {
this.webAppsStore.off("store-ready", this.onWebAppsStoreready); this.webAppsStore.off("store-ready", this.onWebAppsStoreready);
this.webAppsStore.destroy(); this.webAppsStore.destroy();
this.webAppsStore = null; this.webAppsStore = null;
this.tabStore.off("navigate", this.onTabNavigate);
this.tabStore.off("closed", this.onTabClosed);
this.tabStore.destroy();
this.tabStore = null;
this.connection.off(Connection.Events.STATUS_CHANGED, this.onConnectionChanged); this.connection.off(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
this._listTabsResponse = null; this._listTabsResponse = null;
this.connection.disconnect(); this.connection.disconnect();
@ -192,7 +203,8 @@ exports.AppManager = AppManager = {
}, },
isProjectRunning: function() { isProjectRunning: function() {
if (this.selectedProject.type == "mainProcess") { if (this.selectedProject.type == "mainProcess" ||
this.selectedProject.type == "tab") {
return true; return true;
} }
let manifest = this.getProjectManifestURL(this.selectedProject); let manifest = this.getProjectManifestURL(this.selectedProject);
@ -209,7 +221,51 @@ exports.AppManager = AppManager = {
} }
}, },
listTabs: function() {
return this.tabStore.listTabs();
},
// TODO: Merge this into TabProject as part of project-agnostic work
onTabNavigate: function() {
if (this.selectedProject.type !== "tab") {
return;
}
let tab = this.selectedProject.app = this.tabStore.selectedTab;
let uri = NetUtil.newURI(tab.url);
// Wanted to use nsIFaviconService here, but it only works for visited
// tabs, so that's no help for any remote tabs. Maybe some favicon wizard
// knows how to get high-res favicons easily, or we could offer actor
// support for this (bug 1061654).
tab.favicon = uri.prePath + "/favicon.ico";
tab.name = tab.title || Strings.GetStringFromName("project_tab_loading");
if (uri.scheme.startsWith("http")) {
tab.name = uri.host + ": " + tab.name;
}
this.selectedProject.location = tab.url;
this.selectedProject.name = tab.name;
this.selectedProject.icon = tab.favicon;
this.update("project-validated");
},
onTabClosed: function() {
if (this.selectedProject.type !== "tab") {
return;
}
this.selectedProject = null;
},
reloadTab: function() {
if (this.selectedProject && this.selectedProject.type != "tab") {
return promise.reject("tried to reload non-tab project");
}
return this.getTarget().then(target => {
target.activeTab.reload();
});
},
getTarget: function() { getTarget: function() {
let client = this.connection.client;
if (this.selectedProject.type == "mainProcess") { if (this.selectedProject.type == "mainProcess") {
return devtools.TargetFactory.forRemoteTab({ return devtools.TargetFactory.forRemoteTab({
form: this._listTabsResponse, form: this._listTabsResponse,
@ -218,13 +274,16 @@ exports.AppManager = AppManager = {
}); });
} }
if (this.selectedProject.type == "tab") {
return this.tabStore.getTargetForTab();
}
let manifest = this.getProjectManifestURL(this.selectedProject); let manifest = this.getProjectManifestURL(this.selectedProject);
if (!manifest) { if (!manifest) {
console.error("Can't find manifestURL for selected project"); console.error("Can't find manifestURL for selected project");
return promise.reject(); return promise.reject();
} }
let client = this.connection.client;
let actor = this._listTabsResponse.webappsActor; let actor = this._listTabsResponse.webappsActor;
return Task.spawn(function* () { return Task.spawn(function* () {
// Once we asked the app to launch, the app isn't necessary completely loaded. // Once we asked the app to launch, the app isn't necessary completely loaded.
@ -270,6 +329,9 @@ exports.AppManager = AppManager = {
if (value != this.selectedProject) { if (value != this.selectedProject) {
this._selectedProject = value; this._selectedProject = value;
// Clear out tab store's selected state, if any
this.tabStore.selectedTab = null;
if (this.selectedProject) { if (this.selectedProject) {
if (this.selectedProject.type == "runtimeApp") { if (this.selectedProject.type == "runtimeApp") {
this.runRuntimeApp(); this.runRuntimeApp();
@ -278,6 +340,9 @@ exports.AppManager = AppManager = {
this.selectedProject.type == "hosted") { this.selectedProject.type == "hosted") {
this.validateProject(this.selectedProject); this.validateProject(this.selectedProject);
} }
if (this.selectedProject.type == "tab") {
this.tabStore.selectedTab = this.selectedProject.app;
}
} }
this.update("project"); this.update("project");
@ -300,7 +365,8 @@ exports.AppManager = AppManager = {
this._selectedRuntime = value; this._selectedRuntime = value;
if (!value && this.selectedProject && if (!value && this.selectedProject &&
(this.selectedProject.type == "mainProcess" || (this.selectedProject.type == "mainProcess" ||
this.selectedProject.type == "runtimeApp")) { this.selectedProject.type == "runtimeApp" ||
this.selectedProject.type == "tab")) {
this.selectedProject = null; this.selectedProject = null;
} }
this.update("runtime"); this.update("runtime");

View File

@ -0,0 +1,157 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { Cu } = require("chrome");
const EventEmitter = require("devtools/toolkit/event-emitter");
const { Connection } = require("devtools/client/connection-manager");
const { Promise: promise } =
Cu.import("resource://gre/modules/Promise.jsm", {});
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const { devtools } =
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const _knownTabStores = new WeakMap();
let TabStore;
module.exports = TabStore = function(connection) {
// If we already know about this connection,
// let's re-use the existing store.
if (_knownTabStores.has(connection)) {
return _knownTabStores.get(connection);
}
_knownTabStores.set(connection, this);
EventEmitter.decorate(this);
this._resetStore();
this.destroy = this.destroy.bind(this);
this._onStatusChanged = this._onStatusChanged.bind(this);
this._connection = connection;
this._connection.once(Connection.Events.DESTROYED, this.destroy);
this._connection.on(Connection.Events.STATUS_CHANGED, this._onStatusChanged);
this._onTabListChanged = this._onTabListChanged.bind(this);
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onStatusChanged();
return this;
};
TabStore.prototype = {
destroy: function() {
if (this._connection) {
// While this.destroy is bound using .once() above, that event may not
// have occurred when the TabStore client calls destroy, so we
// manually remove it here.
this._connection.off(Connection.Events.DESTROYED, this.destroy);
this._connection.off(Connection.Events.STATUS_CHANGED, this._onStatusChanged);
_knownTabStores.delete(this._connection);
this._connection = null;
}
},
_resetStore: function() {
this.response = null;
this.tabs = [];
},
_onStatusChanged: function() {
if (this._connection.status == Connection.Status.CONNECTED) {
// Watch for changes to remote browser tabs
this._connection.client.addListener("tabListChanged",
this._onTabListChanged);
this._connection.client.addListener("tabNavigated",
this._onTabNavigated);
this.listTabs();
} else {
if (this._connection.client) {
this._connection.client.removeListener("tabListChanged",
this._onTabListChanged);
this._connection.client.removeListener("tabNavigated",
this._onTabNavigated);
}
this._resetStore();
}
},
_onTabListChanged: function() {
this.listTabs();
},
_onTabNavigated: function(e, { from, title, url }) {
if (!this._selectedTab || from !== this._selectedTab.actor) {
return;
}
this._selectedTab.url = url;
this._selectedTab.title = title;
this.emit("navigate");
},
listTabs: function() {
if (!this._connection || !this._connection.client) {
return promise.reject();
}
let deferred = promise.defer();
this._connection.client.listTabs(response => {
if (response.error) {
this._connection.disconnect();
deferred.reject(response.error);
return;
}
this.response = response;
this.tabs = response.tabs;
this._checkSelectedTab();
deferred.resolve(response);
});
return deferred.promise;
},
// TODO: Tab "selection" should really take place by creating a TabProject
// which is the selected project. This should be done as part of the
// project-agnostic work.
_selectedTab: null,
get selectedTab() {
return this._selectedTab;
},
set selectedTab(tab) {
this._selectedTab = tab;
// Attach to the tab to follow navigation events
if (this._selectedTab) {
this.getTargetForTab();
}
},
_checkSelectedTab: function() {
if (!this._selectedTab) {
return;
}
let alive = this.tabs.some(tab => {
return tab.actor === this._selectedTab.actor;
});
if (!alive) {
this.emit("closed");
}
},
getTargetForTab: function() {
let store = this;
return Task.spawn(function*() {
// If you connect to a tab, then detach from it, the root actor may have
// de-listed the actors that belong to the tab. This breaks the toolbox
// if you try to connect to the same tab again. To work around this
// issue, we force a "listTabs" request before connecting to a tab.
yield store.listTabs();
return devtools.TargetFactory.forRemoteTab({
form: store._selectedTab,
client: store._connection.client,
chrome: false
});
});
},
};

View File

@ -10,11 +10,13 @@ DIRS += [
'themes', 'themes',
] ]
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
EXTRA_JS_MODULES.devtools.webide += [ EXTRA_JS_MODULES.devtools.webide += [
'modules/addons.js', 'modules/addons.js',
'modules/app-manager.js', 'modules/app-manager.js',
'modules/remote-resources.js', 'modules/remote-resources.js',
'modules/runtimes.js' 'modules/runtimes.js',
'modules/tab-store.js',
] ]

View File

@ -0,0 +1,9 @@
[DEFAULT]
subsuite = devtools
support-files =
addons/simulators.json
head.js
templates.json
[browser_tabs.js]
skip-if = true # Fails on TBPL, to be fixed in bug 1062611

View File

@ -0,0 +1,55 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test() {
waitForExplicitFinish();
SimpleTest.requestCompleteLog();
Task.spawn(function() {
const { DebuggerServer } =
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
let tab = yield addTab("about:newtab");
let win = yield openWebIDE();
yield connectToLocal(win);
is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
yield selectTabProject(win);
let project = win.AppManager.selectedProject;
is(project.location, "about:newtab", "Location is correct");
is(project.name, "New Tab", "Name is correct");
yield closeWebIDE(win);
DebuggerServer.destroy();
yield removeTab(tab);
finish();
});
}
function connectToLocal(win) {
let deferred = promise.defer();
win.AppManager.connection.once(
win.Connection.Events.CONNECTED,
() => deferred.resolve());
win.document.querySelectorAll(".runtime-panel-item-custom")[1].click();
return deferred.promise;
}
function selectTabProject(win) {
return Task.spawn(function() {
yield win.AppManager.listTabs();
win.Cmds.showProjectPanel();
yield nextTick();
let tabsNode = win.document.querySelector("#project-panel-tabs");
let tabNode = tabsNode.querySelectorAll(".panel-item")[1];
tabNode.click();
});
}

View File

@ -14,7 +14,12 @@ const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {require} = devtools; const {require} = devtools;
const {AppProjects} = require("devtools/app-manager/app-projects"); const {AppProjects} = require("devtools/app-manager/app-projects");
const TEST_BASE = "chrome://mochitests/content/chrome/browser/devtools/webide/test/"; let TEST_BASE;
if (window.location === "chrome://browser/content/browser.xul") {
TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/webide/test/";
} else {
TEST_BASE = "chrome://mochitests/content/chrome/browser/devtools/webide/test/";
}
Services.prefs.setBoolPref("devtools.webide.enabled", true); Services.prefs.setBoolPref("devtools.webide.enabled", true);
Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true); Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true);
@ -110,3 +115,41 @@ function documentIsLoaded(doc) {
} }
return deferred.promise; return deferred.promise;
} }
function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
let deferred = promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
let linkedBrowser = tab.linkedBrowser;
linkedBrowser.addEventListener("load", function onLoad() {
linkedBrowser.removeEventListener("load", onLoad, true);
info("Tab added and finished loading: " + aUrl);
deferred.resolve(tab);
}, true);
return deferred.promise;
}
function removeTab(aTab, aWindow) {
info("Removing tab.");
let deferred = promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
let tabContainer = targetBrowser.tabContainer;
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
tabContainer.removeEventListener("TabClose", onClose, false);
info("Tab removed and finished closing.");
deferred.resolve();
}, false);
targetBrowser.removeTab(aTab);
return deferred.promise;
}

View File

@ -55,7 +55,8 @@ header {
header > div { header > div {
vertical-align: top; vertical-align: top;
display: inline-block; display: flex;
flex-direction: column;
} }
#icon { #icon {
@ -73,6 +74,9 @@ h1, #type {
h1 { h1 {
font-size: 20px; font-size: 20px;
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
#type { #type {

View File

@ -65,6 +65,10 @@ window.busy-determined #action-busy-undetermined {
/* Panel buttons - projects */ /* Panel buttons - projects */
#project-panel-button {
-moz-box-pack: start;
}
#project-panel-button > .panel-button-image { #project-panel-button > .panel-button-image {
width: 13px; width: 13px;
height: 13px; height: 13px;
@ -81,6 +85,10 @@ window.busy-determined #action-busy-undetermined {
-moz-image-region: rect(260px,338px,286px,312px); -moz-image-region: rect(260px,338px,286px,312px);
} }
#project-panel-button > .panel-button-label {
max-width: 150px;
}
/* Panel buttons - runtime */ /* Panel buttons - runtime */
#runtime-panel-button > .panel-button-image { #runtime-panel-button > .panel-button-image {
@ -136,6 +144,7 @@ panel > vbox {
panel > .panel-arrowcontainer > .panel-arrowcontent { panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 12px 0; padding: 12px 0;
min-width: 200px; min-width: 200px;
max-width: 400px;
} }
.panel-item { .panel-item {

View File

@ -62,6 +62,7 @@
<!ENTITY projectPanel_myProjects "My Projects"> <!ENTITY projectPanel_myProjects "My Projects">
<!ENTITY projectPanel_runtimeApps "Runtime Apps"> <!ENTITY projectPanel_runtimeApps "Runtime Apps">
<!ENTITY projectPanel_tabs "Tabs">
<!ENTITY runtimePanel_USBDevices "USB Devices"> <!ENTITY runtimePanel_USBDevices "USB Devices">
<!ENTITY runtimePanel_WiFiDevices "Wi-Fi Devices"> <!ENTITY runtimePanel_WiFiDevices "Wi-Fi Devices">
<!ENTITY runtimePanel_simulators "Simulators"> <!ENTITY runtimePanel_simulators "Simulators">

View File

@ -22,6 +22,10 @@ importHostedApp_header=Enter Manifest URL
notification_showTroubleShooting_label=troubleshooting notification_showTroubleShooting_label=troubleshooting
notification_showTroubleShooting_accesskey=t notification_showTroubleShooting_accesskey=t
# LOCALIZATION NOTE (project_tab_loading): This is shown as a temporary tab
# title for browser tab projects when the tab is still loading.
project_tab_loading=Loading…
# These messages appear in a notification box when an error occur. # These messages appear in a notification box when an error occur.
error_cantInstallNotFullyConnected=Can't install project. Not fully connected. error_cantInstallNotFullyConnected=Can't install project. Not fully connected.

View File

@ -12,6 +12,7 @@
background-image: linear-gradient(90deg, #a0dfff 0%, #ceeeff 100%); background-image: linear-gradient(90deg, #a0dfff 0%, #ceeeff 100%);
border: 0px solid rgba(0,148,221,.5); border: 0px solid rgba(0,148,221,.5);
box-shadow: 0 1px 5px 0 rgba(0,0,0,.5), inset 0 1px 1px 0 #fff; box-shadow: 0 1px 5px 0 rgba(0,0,0,.5), inset 0 1px 1px 0 #fff;
color: rgb(51,51,51);
} }
#customization-tipPanel > .panel-arrowcontainer > .panel-arrowcontent:-moz-locale-dir(rtl) { #customization-tipPanel > .panel-arrowcontainer > .panel-arrowcontent:-moz-locale-dir(rtl) {

View File

@ -224,10 +224,15 @@ let DeviceFront = protocol.FrontClass(DeviceActor, {
const _knownDeviceFronts = new WeakMap(); const _knownDeviceFronts = new WeakMap();
exports.getDeviceFront = function(client, form) { exports.getDeviceFront = function(client, form) {
if (_knownDeviceFronts.has(client)) if (!form.deviceActor) {
return null;
}
if (_knownDeviceFronts.has(client)) {
return _knownDeviceFronts.get(client); return _knownDeviceFronts.get(client);
}
let front = new DeviceFront(client, form); let front = new DeviceFront(client, form);
_knownDeviceFronts.set(client, front); _knownDeviceFronts.set(client, front);
return front; return front;
} };

View File

@ -116,8 +116,13 @@ let PreferenceFront = protocol.FrontClass(PreferenceActor, {
const _knownPreferenceFronts = new WeakMap(); const _knownPreferenceFronts = new WeakMap();
exports.getPreferenceFront = function(client, form) { exports.getPreferenceFront = function(client, form) {
if (_knownPreferenceFronts.has(client)) if (!form.preferenceActor) {
return null;
}
if (_knownPreferenceFronts.has(client)) {
return _knownPreferenceFronts.get(client); return _knownPreferenceFronts.get(client);
}
let front = new PreferenceFront(client, form); let front = new PreferenceFront(client, form);
_knownPreferenceFronts.set(client, front); _knownPreferenceFronts.set(client, front);