mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
1f2744b05d
@ -80,10 +80,13 @@ WebappsStore.prototype = {
|
||||
},
|
||||
|
||||
_feedStore: function() {
|
||||
if (!this._webAppsActor) {
|
||||
return promise.resolve();
|
||||
}
|
||||
this._listenToApps();
|
||||
return this._getAllApps()
|
||||
.then(this._getRunningApps.bind(this))
|
||||
.then(this._getAppsIcons.bind(this))
|
||||
.then(this._getAppsIcons.bind(this));
|
||||
},
|
||||
|
||||
_listenToApps: function() {
|
||||
|
@ -32,30 +32,12 @@ function performTest() {
|
||||
info("process name: " + gProcess._dbgProcess.processName);
|
||||
info("process sig: " + gProcess._dbgProcess.processSignature);
|
||||
|
||||
ok(gProcess._dbgProfile,
|
||||
ok(gProcess._dbgProfilePath,
|
||||
"The remote debugger profile wasn't created properly!");
|
||||
ok(gProcess._dbgProfile.localDir,
|
||||
"The remote debugger profile doesn't have a localDir...");
|
||||
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...");
|
||||
is(gProcess._dbgProfilePath, OS.Path.join(OS.Constants.Path.profileDir, "chrome_debugger_profile"),
|
||||
"The remote debugger profile isn't where we expect it!");
|
||||
|
||||
info("profile localDir: " + gProcess._dbgProfile.localDir.path);
|
||||
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!");
|
||||
info("profile path: " + gProcess._dbgProfilePath);
|
||||
|
||||
gProcess.close();
|
||||
}
|
||||
@ -68,9 +50,7 @@ function aOnClose() {
|
||||
|
||||
info("process exit value: " + gProcess._dbgProcess.exitValue);
|
||||
|
||||
info("profile localDir: " + gProcess._dbgProfile.localDir.path);
|
||||
info("profile rootDir: " + gProcess._dbgProfile.rootDir.path);
|
||||
info("profile name: " + gProcess._dbgProfile.name);
|
||||
info("profile path: " + gProcess._dbgProfilePath);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
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/XPCOMUtils.jsm")
|
||||
@ -154,54 +154,33 @@ BrowserToolboxProcess.prototype = {
|
||||
_initProfile: function() {
|
||||
dumpn("Initializing the chrome toolbox user profile.");
|
||||
|
||||
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
|
||||
.createInstance(Ci.nsIToolkitProfileService);
|
||||
|
||||
let profileName;
|
||||
let debuggingProfileDir = Services.dirsvc.get("ProfLD", Ci.nsIFile);
|
||||
debuggingProfileDir.append(CHROME_DEBUGGER_PROFILE_NAME);
|
||||
try {
|
||||
// Attempt to get the required chrome debugging profile name string.
|
||||
profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME;
|
||||
dumpn("Using chrome toolbox profile name: " + profileName);
|
||||
} catch (e) {
|
||||
// Requested profile string could not be retrieved.
|
||||
profileName = CHROME_DEBUGGER_PROFILE_NAME;
|
||||
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;
|
||||
}
|
||||
debuggingProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
} catch (ex) {
|
||||
if (ex.result !== Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
dumpn("Error trying to create a profile directory, failing.");
|
||||
dumpn("Error: " + (ex.message || ex));
|
||||
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._dbgProfile = profileService.createProfile(null, profileName);
|
||||
profileService.flush();
|
||||
this._dbgProfilePath = debuggingProfileDir.path;
|
||||
|
||||
dumpn("Finished creating the chrome toolbox user profile.");
|
||||
dumpn("Flushed profile service with: " + profileName);
|
||||
// We would like to copy prefs into this new profile...
|
||||
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.");
|
||||
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() });
|
||||
|
||||
|
@ -1419,7 +1419,7 @@ CssRuleView.prototype = {
|
||||
this.element.appendChild(editor.element);
|
||||
} else {
|
||||
for (let rule of rules) {
|
||||
if (rule.selectorText === "element") {
|
||||
if (rule.domRule.type === ELEMENT_STYLE) {
|
||||
let referenceElement = rule.editor.element.nextSibling;
|
||||
this.element.insertBefore(editor.element, referenceElement);
|
||||
break;
|
||||
|
@ -84,7 +84,7 @@ function CheckLockState() {
|
||||
|
||||
if (AppManager.connection &&
|
||||
AppManager.connection.status == Connection.Status.CONNECTED &&
|
||||
AppManager.deviceFront) {
|
||||
AppManager.preferenceFront) {
|
||||
|
||||
// ADB check
|
||||
if (AppManager.selectedRuntime instanceof USBRuntime) {
|
||||
|
@ -114,7 +114,8 @@ let UI = {
|
||||
// not focused.
|
||||
if (AppManager.selectedProject &&
|
||||
AppManager.selectedProject.type != "mainProcess" &&
|
||||
AppManager.selectedProject.type != "runtimeApp") {
|
||||
AppManager.selectedProject.type != "runtimeApp" &&
|
||||
AppManager.selectedProject.type != "tab") {
|
||||
AppManager.validateProject(AppManager.selectedProject);
|
||||
}
|
||||
},
|
||||
@ -138,6 +139,7 @@ let UI = {
|
||||
break;
|
||||
case "project-is-not-running":
|
||||
case "project-is-running":
|
||||
case "list-tabs-response":
|
||||
this.updateCommands();
|
||||
break;
|
||||
case "runtime":
|
||||
@ -562,6 +564,9 @@ let UI = {
|
||||
// If connected and a project is selected
|
||||
if (AppManager.selectedProject.type == "runtimeApp") {
|
||||
playCmd.removeAttribute("disabled");
|
||||
} else if (AppManager.selectedProject.type == "tab") {
|
||||
playCmd.removeAttribute("disabled");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
} else if (AppManager.selectedProject.type == "mainProcess") {
|
||||
playCmd.setAttribute("disabled", "true");
|
||||
stopCmd.setAttribute("disabled", "true");
|
||||
@ -592,16 +597,18 @@ let UI = {
|
||||
|
||||
let runtimePanelButton = document.querySelector("#runtime-panel-button");
|
||||
if (AppManager.connection.status == Connection.Status.CONNECTED) {
|
||||
screenshotCmd.removeAttribute("disabled");
|
||||
permissionsCmd.removeAttribute("disabled");
|
||||
if (AppManager.deviceFront) {
|
||||
detailsCmd.removeAttribute("disabled");
|
||||
permissionsCmd.removeAttribute("disabled");
|
||||
screenshotCmd.removeAttribute("disabled");
|
||||
}
|
||||
disconnectCmd.removeAttribute("disabled");
|
||||
detailsCmd.removeAttribute("disabled");
|
||||
runtimePanelButton.setAttribute("active", "true");
|
||||
} else {
|
||||
screenshotCmd.setAttribute("disabled", "true");
|
||||
permissionsCmd.setAttribute("disabled", "true");
|
||||
disconnectCmd.setAttribute("disabled", "true");
|
||||
detailsCmd.setAttribute("disabled", "true");
|
||||
permissionsCmd.setAttribute("disabled", "true");
|
||||
screenshotCmd.setAttribute("disabled", "true");
|
||||
disconnectCmd.setAttribute("disabled", "true");
|
||||
runtimePanelButton.removeAttribute("active");
|
||||
}
|
||||
|
||||
@ -822,7 +829,13 @@ let Cmds = {
|
||||
|
||||
|
||||
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");
|
||||
} else {
|
||||
runtimeappsHeaderNode.setAttribute("hidden", "true");
|
||||
@ -833,7 +846,7 @@ let Cmds = {
|
||||
runtimeAppsNode.firstChild.remove();
|
||||
}
|
||||
|
||||
if (AppManager.isMainProcessDebuggable()) {
|
||||
if (mainProcess) {
|
||||
let panelItemNode = document.createElement("toolbarbutton");
|
||||
panelItemNode.className = "panel-item";
|
||||
panelItemNode.setAttribute("label", Strings.GetStringFromName("mainProcess_label"));
|
||||
@ -849,10 +862,6 @@ let Cmds = {
|
||||
}, 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++) {
|
||||
let app = sortedApps[i];
|
||||
let panelItemNode = document.createElement("toolbarbutton");
|
||||
@ -871,9 +880,63 @@ let Cmds = {
|
||||
}, 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;
|
||||
},
|
||||
|
||||
_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() {
|
||||
AppManager.scanForWiFiRuntimes();
|
||||
|
||||
@ -924,6 +987,8 @@ let Cmds = {
|
||||
return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
|
||||
case "runtimeApp":
|
||||
return UI.busyUntil(AppManager.runRuntimeApp(), "running app");
|
||||
case "tab":
|
||||
return UI.busyUntil(AppManager.reloadTab(), "reloading tab");
|
||||
}
|
||||
return promise.reject();
|
||||
},
|
||||
|
@ -117,7 +117,7 @@
|
||||
<hbox id="panel-buttons-container">
|
||||
<toolbarbutton id="project-panel-button" class="panel-button no-project" command="cmd_showProjectPanel">
|
||||
<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"/>
|
||||
</toolbarbutton>
|
||||
<spacer flex="1"/>
|
||||
@ -143,6 +143,8 @@
|
||||
<vbox id="project-panel-projects"></vbox>
|
||||
<label class="panel-header" id="panel-header-runtimeapps" hidden="true">&projectPanel_runtimeApps;</label>
|
||||
<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>
|
||||
</panel>
|
||||
|
||||
|
@ -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 {AppProjects} = require("devtools/app-manager/app-projects");
|
||||
const WebappsStore = require("devtools/app-manager/webapps-store");
|
||||
const TabStore = require("devtools/webide/tab-store");
|
||||
const {AppValidator} = require("devtools/app-manager/app-validator");
|
||||
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
|
||||
const AppActorFront = require("devtools/app-actor-front");
|
||||
@ -25,6 +26,7 @@ const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const {USBRuntime, WiFiRuntime, SimulatorRuntime,
|
||||
gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
|
||||
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");
|
||||
|
||||
@ -47,6 +49,11 @@ exports.AppManager = AppManager = {
|
||||
this.onWebAppsStoreready = this.onWebAppsStoreready.bind(this);
|
||||
this.webAppsStore = new WebappsStore(this.connection);
|
||||
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 = {
|
||||
usb: [],
|
||||
@ -81,6 +88,10 @@ exports.AppManager = AppManager = {
|
||||
this.webAppsStore.off("store-ready", this.onWebAppsStoreready);
|
||||
this.webAppsStore.destroy();
|
||||
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._listTabsResponse = null;
|
||||
this.connection.disconnect();
|
||||
@ -192,7 +203,8 @@ exports.AppManager = AppManager = {
|
||||
},
|
||||
|
||||
isProjectRunning: function() {
|
||||
if (this.selectedProject.type == "mainProcess") {
|
||||
if (this.selectedProject.type == "mainProcess" ||
|
||||
this.selectedProject.type == "tab") {
|
||||
return true;
|
||||
}
|
||||
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() {
|
||||
let client = this.connection.client;
|
||||
|
||||
if (this.selectedProject.type == "mainProcess") {
|
||||
return devtools.TargetFactory.forRemoteTab({
|
||||
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);
|
||||
if (!manifest) {
|
||||
console.error("Can't find manifestURL for selected project");
|
||||
return promise.reject();
|
||||
}
|
||||
|
||||
let client = this.connection.client;
|
||||
let actor = this._listTabsResponse.webappsActor;
|
||||
return Task.spawn(function* () {
|
||||
// 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) {
|
||||
this._selectedProject = value;
|
||||
|
||||
// Clear out tab store's selected state, if any
|
||||
this.tabStore.selectedTab = null;
|
||||
|
||||
if (this.selectedProject) {
|
||||
if (this.selectedProject.type == "runtimeApp") {
|
||||
this.runRuntimeApp();
|
||||
@ -278,6 +340,9 @@ exports.AppManager = AppManager = {
|
||||
this.selectedProject.type == "hosted") {
|
||||
this.validateProject(this.selectedProject);
|
||||
}
|
||||
if (this.selectedProject.type == "tab") {
|
||||
this.tabStore.selectedTab = this.selectedProject.app;
|
||||
}
|
||||
}
|
||||
|
||||
this.update("project");
|
||||
@ -300,7 +365,8 @@ exports.AppManager = AppManager = {
|
||||
this._selectedRuntime = value;
|
||||
if (!value && this.selectedProject &&
|
||||
(this.selectedProject.type == "mainProcess" ||
|
||||
this.selectedProject.type == "runtimeApp")) {
|
||||
this.selectedProject.type == "runtimeApp" ||
|
||||
this.selectedProject.type == "tab")) {
|
||||
this.selectedProject = null;
|
||||
}
|
||||
this.update("runtime");
|
||||
|
157
browser/devtools/webide/modules/tab-store.js
Normal file
157
browser/devtools/webide/modules/tab-store.js
Normal 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
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
};
|
@ -10,11 +10,13 @@ DIRS += [
|
||||
'themes',
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
|
||||
|
||||
EXTRA_JS_MODULES.devtools.webide += [
|
||||
'modules/addons.js',
|
||||
'modules/app-manager.js',
|
||||
'modules/remote-resources.js',
|
||||
'modules/runtimes.js'
|
||||
'modules/runtimes.js',
|
||||
'modules/tab-store.js',
|
||||
]
|
||||
|
9
browser/devtools/webide/test/browser.ini
Normal file
9
browser/devtools/webide/test/browser.ini
Normal 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
|
55
browser/devtools/webide/test/browser_tabs.js
Normal file
55
browser/devtools/webide/test/browser_tabs.js
Normal 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();
|
||||
});
|
||||
}
|
@ -14,7 +14,12 @@ const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
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.enableLocalRuntime", true);
|
||||
@ -110,3 +115,41 @@ function documentIsLoaded(doc) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -55,7 +55,8 @@ header {
|
||||
|
||||
header > div {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#icon {
|
||||
@ -73,6 +74,9 @@ h1, #type {
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#type {
|
||||
|
@ -65,6 +65,10 @@ window.busy-determined #action-busy-undetermined {
|
||||
|
||||
/* Panel buttons - projects */
|
||||
|
||||
#project-panel-button {
|
||||
-moz-box-pack: start;
|
||||
}
|
||||
|
||||
#project-panel-button > .panel-button-image {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
@ -81,6 +85,10 @@ window.busy-determined #action-busy-undetermined {
|
||||
-moz-image-region: rect(260px,338px,286px,312px);
|
||||
}
|
||||
|
||||
#project-panel-button > .panel-button-label {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
/* Panel buttons - runtime */
|
||||
|
||||
#runtime-panel-button > .panel-button-image {
|
||||
@ -136,6 +144,7 @@ panel > vbox {
|
||||
panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
padding: 12px 0;
|
||||
min-width: 200px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.panel-item {
|
||||
|
@ -62,6 +62,7 @@
|
||||
|
||||
<!ENTITY projectPanel_myProjects "My Projects">
|
||||
<!ENTITY projectPanel_runtimeApps "Runtime Apps">
|
||||
<!ENTITY projectPanel_tabs "Tabs">
|
||||
<!ENTITY runtimePanel_USBDevices "USB Devices">
|
||||
<!ENTITY runtimePanel_WiFiDevices "Wi-Fi Devices">
|
||||
<!ENTITY runtimePanel_simulators "Simulators">
|
||||
|
@ -22,6 +22,10 @@ importHostedApp_header=Enter Manifest URL
|
||||
notification_showTroubleShooting_label=troubleshooting
|
||||
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.
|
||||
|
||||
error_cantInstallNotFullyConnected=Can't install project. Not fully connected.
|
||||
|
@ -12,6 +12,7 @@
|
||||
background-image: linear-gradient(90deg, #a0dfff 0%, #ceeeff 100%);
|
||||
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;
|
||||
color: rgb(51,51,51);
|
||||
}
|
||||
|
||||
#customization-tipPanel > .panel-arrowcontainer > .panel-arrowcontent:-moz-locale-dir(rtl) {
|
||||
|
@ -224,10 +224,15 @@ let DeviceFront = protocol.FrontClass(DeviceActor, {
|
||||
const _knownDeviceFronts = new WeakMap();
|
||||
|
||||
exports.getDeviceFront = function(client, form) {
|
||||
if (_knownDeviceFronts.has(client))
|
||||
if (!form.deviceActor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_knownDeviceFronts.has(client)) {
|
||||
return _knownDeviceFronts.get(client);
|
||||
}
|
||||
|
||||
let front = new DeviceFront(client, form);
|
||||
_knownDeviceFronts.set(client, front);
|
||||
return front;
|
||||
}
|
||||
};
|
||||
|
@ -116,8 +116,13 @@ let PreferenceFront = protocol.FrontClass(PreferenceActor, {
|
||||
const _knownPreferenceFronts = new WeakMap();
|
||||
|
||||
exports.getPreferenceFront = function(client, form) {
|
||||
if (_knownPreferenceFronts.has(client))
|
||||
if (!form.preferenceActor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_knownPreferenceFronts.has(client)) {
|
||||
return _knownPreferenceFronts.get(client);
|
||||
}
|
||||
|
||||
let front = new PreferenceFront(client, form);
|
||||
_knownPreferenceFronts.set(client, front);
|
||||
|
Loading…
Reference in New Issue
Block a user