Merge fx-team to m-c.
@ -1066,6 +1066,9 @@ pref("devtools.toolbar.visible", false);
|
||||
pref("devtools.gcli.allowSet", false);
|
||||
pref("devtools.commands.dir", "");
|
||||
|
||||
// Disable the app manager
|
||||
pref("devtools.appmanager.enabled", false);
|
||||
|
||||
// Toolbox preferences
|
||||
pref("devtools.toolbox.footer.height", 250);
|
||||
pref("devtools.toolbox.sidebar.width", 500);
|
||||
|
@ -147,6 +147,8 @@
|
||||
<menuseparator id="appmenu_devtools_separator"/>
|
||||
<menuitem id="appmenu_devToolbar"
|
||||
observes="devtoolsMenuBroadcaster_DevToolbar"/>
|
||||
<menuitem id="appmenu_devAppMgr"
|
||||
observes="devtoolsMenuBroadcaster_DevAppMgr"/>
|
||||
<menuitem id="appmenu_chromeDebugger"
|
||||
observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
|
||||
<menuitem id="appmenu_browserConsole"
|
||||
|
@ -520,6 +520,9 @@
|
||||
<menuitem id="menu_devToolbar"
|
||||
observes="devtoolsMenuBroadcaster_DevToolbar"
|
||||
accesskey="&devToolbarMenu.accesskey;"/>
|
||||
<menuitem id="menu_devAppMgr"
|
||||
observes="devtoolsMenuBroadcaster_DevAppMgr"
|
||||
accesskey="&devAppMgrMenu.accesskey;"/>
|
||||
<menuitem id="menu_chromeDebugger"
|
||||
observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
|
||||
<menuitem id="menu_browserConsole"
|
||||
|
@ -95,6 +95,7 @@
|
||||
<command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
|
||||
<command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
|
||||
<command id="Tools:DevAppMgr" oncommand="gDevToolsBrowser.openAppManager(gBrowser);" disabled="true" hidden="true"/>
|
||||
<command id="Tools:ChromeDebugger" oncommand="BrowserDebuggerProcess.init();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserConsole" oncommand="HUDService.toggleBrowserConsole();"/>
|
||||
<command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
|
||||
@ -183,6 +184,9 @@
|
||||
type="checkbox" autocheck="false"
|
||||
command="Tools:DevToolbar"
|
||||
key="key_devToolbar"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_DevAppMgr"
|
||||
label="&devAppMgrMenu.label;"
|
||||
command="Tools:DevAppMgr"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_ChromeDebugger"
|
||||
label="&chromeDebuggerMenu.label;"
|
||||
command="Tools:ChromeDebugger"/>
|
||||
|
@ -1184,6 +1184,14 @@ var gBrowserInit = {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable App Manager?
|
||||
let appMgrEnabled = gPrefService.getBoolPref("devtools.appmanager.enabled");
|
||||
if (appMgrEnabled) {
|
||||
let cmd = document.getElementById("Tools:DevAppMgr");
|
||||
cmd.removeAttribute("disabled");
|
||||
cmd.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
// Enable Chrome Debugger?
|
||||
let chromeEnabled = gPrefService.getBoolPref("devtools.chrome.enabled");
|
||||
let remoteEnabled = chromeEnabled &&
|
||||
|
145
browser/devtools/app-manager/app-validator.js
Normal file
@ -0,0 +1,145 @@
|
||||
/* 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/. */
|
||||
|
||||
let {Ci,Cu,CC,Cc} = require("chrome");
|
||||
const promise = require("sdk/core/promise");
|
||||
|
||||
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
|
||||
let strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
|
||||
|
||||
function AppValidator(project) {
|
||||
this.project = project;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
|
||||
AppValidator.prototype.error = function (message) {
|
||||
this.errors.push(message);
|
||||
}
|
||||
|
||||
AppValidator.prototype.warning = function (message) {
|
||||
this.warnings.push(message);
|
||||
}
|
||||
|
||||
AppValidator.prototype._getPackagedManifestURL = function () {
|
||||
let manifestFile = FileUtils.File(this.project.location);
|
||||
if (!manifestFile.exists()) {
|
||||
this.error(strings.GetStringFromName("validator.nonExistingFolder"));
|
||||
return;
|
||||
}
|
||||
if (!manifestFile.isDirectory()) {
|
||||
this.error(strings.GetStringFromName("validator.expectProjectFolder"));
|
||||
return;
|
||||
}
|
||||
manifestFile.append("manifest.webapp");
|
||||
if (!manifestFile.exists() || !manifestFile.isFile()) {
|
||||
this.error(strings.GetStringFromName("validator.wrongManifestFileName"));
|
||||
return;
|
||||
}
|
||||
|
||||
return Services.io.newFileURI(manifestFile).spec;
|
||||
}
|
||||
|
||||
AppValidator.prototype._fetchManifest = function (manifestURL) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let req = new XMLHttpRequest();
|
||||
try {
|
||||
req.open("GET", manifestURL, true);
|
||||
} catch(e) {
|
||||
this.error(strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1));
|
||||
deferred.resolve(null);
|
||||
return deferred.promise;
|
||||
}
|
||||
req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
req.onload = (function () {
|
||||
let manifest = null;
|
||||
try {
|
||||
manifest = JSON.parse(req.responseText);
|
||||
} catch(e) {
|
||||
this.error(strings.formatStringFromName("validator.invalidManifestJSON", [manifestURL, e], 2));
|
||||
}
|
||||
deferred.resolve(manifest);
|
||||
}).bind(this);
|
||||
req.onerror = (function () {
|
||||
this.error(strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2));
|
||||
deferred.resolve(null);
|
||||
}).bind(this);
|
||||
|
||||
try {
|
||||
req.send(null);
|
||||
} catch(e) {
|
||||
this.error(strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2));
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
AppValidator.prototype._getManifest = function () {
|
||||
let manifestURL;
|
||||
if (this.project.type == "packaged") {
|
||||
manifestURL = this._getPackagedManifestURL();
|
||||
if (!manifestURL)
|
||||
return promise.resolve(null);
|
||||
} else if (this.project.type == "hosted") {
|
||||
manifestURL = this.project.location;
|
||||
try {
|
||||
Services.io.newURI(manifestURL, null, null);
|
||||
} catch(e) {
|
||||
this.error(strings.formatStringFromName("validator.invalidHostedManifestURL", [manifestURL, e.message]));
|
||||
return promise.resolve(null);
|
||||
}
|
||||
} else {
|
||||
this.error(strings.formatStringFromName("validator.invalidProjectType", [this.project.type], 1));
|
||||
return promise.resolve(null);
|
||||
}
|
||||
return this._fetchManifest(manifestURL);
|
||||
}
|
||||
|
||||
AppValidator.prototype.validateManifest = function (manifest) {
|
||||
if (!manifest.name) {
|
||||
this.error(strings.GetStringFromName("validator.missNameManifestProperty"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!manifest.icons || Object.keys(manifest.icons).length == 0) {
|
||||
this.warning(strings.GetStringFromName("validator.missIconsManifestProperty"));
|
||||
} else if (!manifest.icons["128"]) {
|
||||
this.warning(strings.GetStringFromName("validator.missIconForMarketplace"));
|
||||
}
|
||||
}
|
||||
|
||||
AppValidator.prototype.validateType = function (manifest) {
|
||||
let appType = manifest.type || "web";
|
||||
if (["web", "privileged", "certified"].indexOf(appType) === -1) {
|
||||
this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1));
|
||||
} else if (this.project.type == "hosted" &&
|
||||
["certified", "privileged"].indexOf(appType) !== -1) {
|
||||
this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1));
|
||||
}
|
||||
|
||||
// certified app are not fully supported on the simulator
|
||||
if (appType === "certified") {
|
||||
this.warning(strings.GetStringFromName("validator.noCertifiedSupport"));
|
||||
}
|
||||
}
|
||||
|
||||
AppValidator.prototype.validate = function () {
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
return this._getManifest().
|
||||
then((function (manifest) {
|
||||
if (manifest) {
|
||||
this.manifest = manifest;
|
||||
this.validateManifest(manifest);
|
||||
this.validateType(manifest);
|
||||
}
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
exports.AppValidator = AppValidator;
|
||||
|
99
browser/devtools/app-manager/content/connection-footer.js
Normal file
@ -0,0 +1,99 @@
|
||||
/* 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 = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
|
||||
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
|
||||
const ConnectionStore = require("devtools/app-manager/connection-store");
|
||||
const DeviceStore = require("devtools/app-manager/device-store");
|
||||
|
||||
let UI = {
|
||||
init: function() {
|
||||
this.useFloatingScrollbarsIfNeeded();
|
||||
let connections = ConnectionManager.connections;
|
||||
if (connections.length > 0) {
|
||||
let hash = window.location.hash;
|
||||
if (hash) {
|
||||
let res = (/cid=([^&]+)/).exec(hash)
|
||||
if (res) {
|
||||
let [,cid] = res;
|
||||
this.connection = connections.filter((({uid}) => uid == cid))[0];
|
||||
}
|
||||
}
|
||||
if (!this.connection) {
|
||||
// We take the first connection available.
|
||||
this.connection = connections[0];
|
||||
}
|
||||
} else {
|
||||
let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
|
||||
let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
|
||||
this.connection = ConnectionManager.createConnection(host, port);
|
||||
}
|
||||
|
||||
window.location.hash = "cid=" + this.connection.uid;
|
||||
window.parent.postMessage(JSON.stringify({name:"connection",cid:this.connection.uid}), "*");
|
||||
|
||||
this.store = Utils.mergeStores({
|
||||
"device": new DeviceStore(this.connection),
|
||||
"connection": new ConnectionStore(this.connection),
|
||||
});
|
||||
|
||||
let pre = document.querySelector("#logs > pre");
|
||||
pre.textContent = this.connection.logs;
|
||||
pre.scrollTop = pre.scrollTopMax;
|
||||
this.connection.on(Connection.Events.NEW_LOG, (event, str) => {
|
||||
pre.textContent += "\n" + str;
|
||||
pre.scrollTop = pre.scrollTopMax;
|
||||
});
|
||||
|
||||
this.template = new Template(document.body, this.store, Utils.l10n);
|
||||
this.template.start();
|
||||
},
|
||||
|
||||
useFloatingScrollbarsIfNeeded: function() {
|
||||
if (Services.appinfo.OS == "Darwin") {
|
||||
return;
|
||||
}
|
||||
let scrollbarsUrl = Services.io.newURI("chrome://browser/skin/devtools/floating-scrollbars-light.css", null, null);
|
||||
let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
winUtils.loadSheet(scrollbarsUrl, winUtils.AGENT_SHEET);
|
||||
let computedStyle = window.getComputedStyle(document.documentElement);
|
||||
if (computedStyle) { // Force a reflow to take the new css into account
|
||||
let display = computedStyle.display; // Save display value
|
||||
document.documentElement.style.display = "none";
|
||||
window.getComputedStyle(document.documentElement).display; // Flush
|
||||
document.documentElement.style.display = display; // Restore
|
||||
}
|
||||
},
|
||||
|
||||
disconnect: function() {
|
||||
this.connection.disconnect();
|
||||
},
|
||||
|
||||
connect: function() {
|
||||
this.connection.connect();
|
||||
},
|
||||
|
||||
editConnectionParameters: function() {
|
||||
document.body.classList.add("edit-connection");
|
||||
document.querySelector("input.host").focus();
|
||||
},
|
||||
|
||||
saveConnectionInfo: function() {
|
||||
document.body.classList.remove("edit-connection");
|
||||
document.querySelector("#connect-button").focus();
|
||||
let host = document.querySelector("input.host").value;
|
||||
let port = document.querySelector("input.port").value;
|
||||
this.connection.port = port;
|
||||
this.connection.host = host;
|
||||
Services.prefs.setCharPref("devtools.debugger.remote-host", host);
|
||||
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
|
||||
},
|
||||
}
|
98
browser/devtools/app-manager/content/connection-footer.xhtml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % appMgrDTD SYSTEM "chrome://browser/locale/devtools/app-manager.dtd" >
|
||||
%appMgrDTD;
|
||||
]>
|
||||
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf8"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/devtools/app-manager/connection-footer.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body onload="UI.init()">
|
||||
|
||||
<div id="connection-footer" template='{"type":"attribute","path":"connection.status","name":"status"}'>
|
||||
<div id="banners-and-logs">
|
||||
|
||||
<!-- Connected -->
|
||||
<div id="banner-connected" class="banner">
|
||||
<div class="connected-indicator"></div>
|
||||
<div id="status" class="banner-box">
|
||||
<div class="banner-content">
|
||||
<span template='{"type":"localizedContent","property":"connection.connectedToDevice","paths":["device.description.name"]}'></span>
|
||||
<button class="action-cancel" onclick="UI.disconnect()">&connection.disconnect;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disconnected -->
|
||||
<div id="banner-disconnected" class="banner">
|
||||
<div class="connected-indicator"></div>
|
||||
<div class="banner-box">
|
||||
<div class="banner-content">
|
||||
<span>&connection.notConnected;</span>
|
||||
<button class="action-primary left" onclick="UI.connect()" id="connect-button" template='{"type":"localizedContent","property":"connection.connectTo","paths":["connection.host","connection.port"]}'></button>
|
||||
<button class="right" onclick="UI.editConnectionParameters()">&connection.changeHostAndPort;</button>
|
||||
<div id="start-simulator-box" template='{"type":"attribute","path":"simulators.versions.length","name":"simulators-count"}'>
|
||||
<span>&connection.or;</span>
|
||||
<button id="start-simulator-button" class="action-primary" onclick="UI.startSimulator()">&connection.startSimulator;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connecting -->
|
||||
<div id="banner-connecting" class="banner">
|
||||
<div class="connected-indicator"></div>
|
||||
<div id="status" class="banner-box">
|
||||
<div class="banner-content">
|
||||
<span>&connection.connecting;</span>
|
||||
<button class="action-cancel" onclick="UI.disconnect()">&connection.cancel;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disconnecting -->
|
||||
<div id="banner-disconnecting" class="banner">
|
||||
<div class="connected-indicator"></div>
|
||||
<div id="status" class="banner-box">
|
||||
<div class="banner-content">
|
||||
<span>&connection.disconnecting;</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editing -->
|
||||
<div id="banner-editing" class="banner">
|
||||
<div class="connected-indicator"></div>
|
||||
<div class="banner-box">
|
||||
<div class="banner-content">
|
||||
<form onsubmit="UI.saveConnectionInfo()">
|
||||
<input class="host" template='{"type":"attribute","path":"connection.host","name":"value"}'></input>
|
||||
<input class="port" pattern="\d+" template='{"type":"attribute","path":"connection.port","name":"value"}' type="number"></input>
|
||||
<button type="submit">&connection.saveConnectionInfo;</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs -->
|
||||
<div id="banner-logs">
|
||||
<div id="logs" class="banner-box">
|
||||
<pre></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="application/javascript;version=1.8" src="utils.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="template.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="connection-footer.js"></script>
|
||||
</html>
|
211
browser/devtools/app-manager/content/device.js
Normal file
@ -0,0 +1,211 @@
|
||||
/* 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 = Components.utils;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
|
||||
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
|
||||
const {getDeviceFront} = require("devtools/server/actors/device");
|
||||
const DeviceStore = require("devtools/app-manager/device-store");
|
||||
const WebappsStore = require("devtools/app-manager/webapps-store");
|
||||
const promise = require("sdk/core/promise");
|
||||
|
||||
window.addEventListener("message", function(event) {
|
||||
try {
|
||||
let message = JSON.parse(event.data);
|
||||
if (message.name == "connection") {
|
||||
let cid = parseInt(message.cid);
|
||||
for (let c of ConnectionManager.connections) {
|
||||
if (c.uid == cid) {
|
||||
UI.connection = c;
|
||||
UI.onNewConnection();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}, false);
|
||||
|
||||
let UI = {
|
||||
init: function() {
|
||||
this.showFooterIfNeeded();
|
||||
this._onConnectionStatusChange = this._onConnectionStatusChange.bind(this);
|
||||
this.setTab("apps");
|
||||
if (this.connection) {
|
||||
this.onNewConnection();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
showFooterIfNeeded: function() {
|
||||
let footer = document.querySelector("#connection-footer");
|
||||
if (window.parent == window) {
|
||||
// We're alone. Let's add a footer.
|
||||
footer.removeAttribute("hidden");
|
||||
footer.src = "chrome://browser/content/devtools/app-manager/connection-footer.xhtml";
|
||||
} else {
|
||||
footer.setAttribute("hidden", "true");
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
document.body.classList.add("notconnected");
|
||||
},
|
||||
|
||||
show: function() {
|
||||
document.body.classList.remove("notconnected");
|
||||
},
|
||||
|
||||
onNewConnection: function() {
|
||||
this.connection.on(Connection.Events.STATUS_CHANGED, this._onConnectionStatusChange);
|
||||
|
||||
this.store = Utils.mergeStores({
|
||||
"device": new DeviceStore(this.connection),
|
||||
"apps": new WebappsStore(this.connection),
|
||||
});
|
||||
|
||||
this.template = new Template(document.body, this.store, Utils.l10n);
|
||||
|
||||
this.template.start();
|
||||
this._onConnectionStatusChange();
|
||||
},
|
||||
|
||||
setWallpaper: function(dataurl) {
|
||||
document.getElementById("meta").style.backgroundImage = "url(" + dataurl + ")";
|
||||
},
|
||||
|
||||
_onConnectionStatusChange: function() {
|
||||
if (this.connection.status != Connection.Status.CONNECTED) {
|
||||
this.hide();
|
||||
this.listTabsResponse = null;
|
||||
} else {
|
||||
this.show();
|
||||
this.connection.client.listTabs(
|
||||
response => {
|
||||
this.listTabsResponse = response;
|
||||
let front = getDeviceFront(this.connection.client, this.listTabsResponse);
|
||||
front.getWallpaper().then(longstr => {
|
||||
longstr.string().then(dataURL => {
|
||||
longstr.release().then(null, Cu.reportError);
|
||||
this.setWallpaper(dataURL);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
setTab: function(name) {
|
||||
var tab = document.querySelector(".tab.selected");
|
||||
var panel = document.querySelector(".tabpanel.selected");
|
||||
|
||||
if (tab) tab.classList.remove("selected");
|
||||
if (panel) panel.classList.remove("selected");
|
||||
|
||||
var tab = document.querySelector(".tab." + name);
|
||||
var panel = document.querySelector(".tabpanel." + name);
|
||||
|
||||
if (tab) tab.classList.add("selected");
|
||||
if (panel) panel.classList.add("selected");
|
||||
},
|
||||
|
||||
screenshot: function() {
|
||||
if (!this.listTabsResponse)
|
||||
return;
|
||||
|
||||
let front = getDeviceFront(this.connection.client, this.listTabsResponse);
|
||||
front.screenshotToBlob().then(blob => {
|
||||
let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let gBrowser = topWindow.gBrowser;
|
||||
let url = topWindow.URL.createObjectURL(blob);
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
|
||||
tab.addEventListener("TabClose", function onTabClose() {
|
||||
tab.removeEventListener("TabClose", onTabClose, false);
|
||||
topWindow.URL.revokeObjectURL(url);
|
||||
}, false);
|
||||
}).then(null, console.error);
|
||||
},
|
||||
|
||||
_getTargetForApp: function(manifest) { // FIXME <- will be implemented in bug 912476
|
||||
if (!this.listTabsResponse)
|
||||
return null;
|
||||
|
||||
let actor = this.listTabsResponse.webappsActor;
|
||||
let deferred = promise.defer();
|
||||
let request = {
|
||||
to: actor,
|
||||
type: "getAppActor",
|
||||
manifestURL: manifest,
|
||||
}
|
||||
this.connection.client.request(request, (res) => {
|
||||
if (res.error) {
|
||||
deferred.reject(res.error);
|
||||
} else {
|
||||
let options = {
|
||||
form: res.actor,
|
||||
client: this.connection.client,
|
||||
chrome: false
|
||||
};
|
||||
|
||||
devtools.TargetFactory.forRemoteTab(options).then((target) => {
|
||||
deferred.resolve(target)
|
||||
}, (error) => {
|
||||
deferred.reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
openToolbox: function(manifest) {
|
||||
this._getTargetForApp(manifest).then((target) => {
|
||||
gDevTools.showToolbox(target, "webconsole", devtools.Toolbox.HostType.WINDOW);
|
||||
}, console.error);
|
||||
},
|
||||
|
||||
startApp: function(manifest) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
if (!this.listTabsResponse) {
|
||||
deferred.reject();
|
||||
} else {
|
||||
let actor = this.listTabsResponse.webappsActor;
|
||||
let request = {
|
||||
to: actor,
|
||||
type: "launch",
|
||||
manifestURL: manifest,
|
||||
}
|
||||
this.connection.client.request(request, (res) => {
|
||||
deferred.resolve()
|
||||
});
|
||||
}
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
stopApp: function(manifest) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
if (!this.listTabsResponse) {
|
||||
deferred.reject();
|
||||
} else {
|
||||
let actor = this.listTabsResponse.webappsActor;
|
||||
let request = {
|
||||
to: actor,
|
||||
type: "close",
|
||||
manifestURL: manifest,
|
||||
}
|
||||
this.connection.client.request(request, (res) => {
|
||||
deferred.resolve()
|
||||
});
|
||||
}
|
||||
return deferred.promise;
|
||||
},
|
||||
}
|
98
browser/devtools/app-manager/content/device.xhtml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % appMgrDTD SYSTEM "chrome://browser/locale/devtools/app-manager.dtd" >
|
||||
%appMgrDTD;
|
||||
]>
|
||||
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<meta charset="utf8"/>
|
||||
<base href="chrome://browser/content/devtools/app-manager/"></base>
|
||||
<title>&device.title;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/devtools/app-manager/device.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body onload="UI.init()">
|
||||
|
||||
<div id="notConnectedMessage"><span>&device.notConnected;</span></div>
|
||||
|
||||
<section id="content">
|
||||
<aside id="sidebar">
|
||||
<div id="meta">
|
||||
<header>
|
||||
<h1>
|
||||
<span template='{"type":"textContent","path":"device.description.name"}'></span>
|
||||
<span template='{"type":"textContent","path":"device.description.version"}'></span>
|
||||
<span template='{"type":"textContent","path":"device.description.channel"}'></span>
|
||||
</h1>
|
||||
<h3>
|
||||
<span>Gecko </span>
|
||||
<span template='{"type":"textContent","path":"device.description.geckoversion"}'></span>
|
||||
</h3>
|
||||
<p template='{"type":"localizedContent","property":"device.deviceSize", "paths":["device.description.width","device.description.height","device.description.dpi"]}'></p>
|
||||
</header>
|
||||
<button onclick="UI.screenshot()">&device.screenshot;</button>
|
||||
<div id="tabs-headers">
|
||||
<div onclick="UI.setTab('apps')" class="tab sidebar-item apps">&device.installedApps;</div>
|
||||
<div onclick="UI.setTab('permissions')" class="tab sidebar-item permissions">&device.permissions;</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<section id="detail">
|
||||
<div id="tabs">
|
||||
<div class="tabpanel apps">
|
||||
<div class="app-list" template-loop='{"arrayPath":"apps.all","childSelector":"#app-template"}'></div>
|
||||
</div>
|
||||
<div class="tabpanel permissions permission-table">
|
||||
<div class="permission-table-header">
|
||||
<div>&device.name;</div>
|
||||
<div>&device.app;</div>
|
||||
<div>&device.privileged;</div>
|
||||
<div>&device.certified;</div>
|
||||
</div>
|
||||
<div class="permission-table-body" >
|
||||
<section template-loop='{"arrayPath":"device.permissions","childSelector":"#permission-template"}'></section>
|
||||
</div>
|
||||
<div class="permission-table-footer">
|
||||
<div class="allow-label">&device.allow;</div>
|
||||
<div class="prompt-label">&device.prompt;</div>
|
||||
<div class="deny-label">&device.deny;</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<iframe id="connection-footer" hidden="true"></iframe>
|
||||
</body>
|
||||
|
||||
<template id="permission-template">
|
||||
<div class="permission">
|
||||
<div template='{"type":"textContent","path":"name"}'></div>
|
||||
<div template='{"type":"attribute", "name":"permission", "path":"app"}'></div>
|
||||
<div template='{"type":"attribute", "name":"permission", "path":"privileged"}'></div>
|
||||
<div template='{"type":"attribute", "name":"permission", "path":"certified"}'></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="app-template">
|
||||
<div class="app" template='{"type":"attribute","path":"running","name":"running"}'>
|
||||
<img class="app-icon" template='{"type":"attribute","path":"iconURL","name":"src"}'></img>
|
||||
<span class="app-name" template='{"type":"textContent","path":"name"}'></span>
|
||||
<div class="app-buttons">
|
||||
<button class="button-debug" template='{"type":"attribute","path":"manifestURL","name":"data-manifest"}' onclick="UI.openToolbox(this.dataset.manifest)">&device.debugApp;</button>
|
||||
<button class="button-start" template='{"type":"attribute","path":"manifestURL","name":"data-manifest"}' onclick="UI.startApp(this.dataset.manifest)">&device.startApp;</button>
|
||||
<button class="button-stop" template='{"type":"attribute","path":"manifestURL","name":"data-manifest"}' onclick="UI.stopApp(this.dataset.manifest)">&device.stopApp;</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="application/javascript;version=1.8" src="utils.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="template.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="device.js"></script>
|
||||
|
||||
</html>
|
54
browser/devtools/app-manager/content/index.js
Normal file
@ -0,0 +1,54 @@
|
||||
/* 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 = Components.utils;
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
|
||||
|
||||
let connection;
|
||||
|
||||
window.addEventListener("message", function(event) {
|
||||
try {
|
||||
let json = JSON.parse(event.data);
|
||||
if (json.name == "connection") {
|
||||
let cid = +json.cid;
|
||||
for (let c of ConnectionManager.connections) {
|
||||
if (c.uid == cid) {
|
||||
connection = c;
|
||||
onNewConnection();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) { Cu.reportError(e); }
|
||||
|
||||
// Forward message
|
||||
let panels = document.querySelectorAll(".panel");
|
||||
for (let frame of panels) {
|
||||
frame.contentWindow.postMessage(event.data, "*");
|
||||
}
|
||||
}, false);
|
||||
|
||||
function onNewConnection() {
|
||||
connection.on(Connection.Status.CONNECTED, () => {
|
||||
document.querySelector("#content").classList.add("connected");
|
||||
});
|
||||
connection.on(Connection.Status.DISCONNECTED, () => {
|
||||
document.querySelector("#content").classList.remove("connected");
|
||||
});
|
||||
}
|
||||
|
||||
function selectTab(id) {
|
||||
for (let type of ["button", "panel"]) {
|
||||
let oldSelection = document.querySelector("." + type + "[selected]");
|
||||
let newSelection = document.querySelector("." + id + "-" + type);
|
||||
if (!newSelection) continue;
|
||||
if (oldSelection) oldSelection.removeAttribute("selected");
|
||||
newSelection.setAttribute("selected", "true");
|
||||
}
|
||||
}
|
||||
selectTab("projects");
|
||||
|
38
browser/devtools/app-manager/content/index.xul
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % appMgrDTD SYSTEM "chrome://browser/locale/devtools/app-manager.dtd" >
|
||||
%appMgrDTD;
|
||||
]>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/app-manager/index.css"?>
|
||||
|
||||
<window id="app-manager-window"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&index.title;"
|
||||
windowtype="devtools:app-manager"
|
||||
macanimationtype="document"
|
||||
fullscreenbutton="true"
|
||||
screenX="4" screenY="4"
|
||||
width="800" height="600"
|
||||
persist="screenX screenY width height sizemode">
|
||||
|
||||
<vbox flex="1">
|
||||
<hbox id="content" flex="1">
|
||||
<vbox id="tabs">
|
||||
<button class="button projects-button" onclick="selectTab('projects')">&index.projects;</button>
|
||||
<button class="button device-button" onclick="selectTab('device')">&index.device;</button>
|
||||
</vbox>
|
||||
<hbox id="tab-panels" flex="1">
|
||||
<iframe flex="1" class="panel projects-panel" src="chrome://browser/content/devtools/app-manager/projects.xhtml"/>
|
||||
<iframe flex="1" class="panel device-panel" src="chrome://browser/content/devtools/app-manager/device.xhtml"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<iframe id="connection-footer" src="chrome://browser/content/devtools/app-manager/connection-footer.xhtml"></iframe>
|
||||
</vbox>
|
||||
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/app-manager/index.js"></script>
|
||||
</window>
|
269
browser/devtools/app-manager/content/projects.js
Normal file
@ -0,0 +1,269 @@
|
||||
/* 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 Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
|
||||
const {AppProjects} = require("devtools/app-manager/app-projects");
|
||||
const {AppValidator} = require("devtools/app-manager/app-validator");
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
const promise = require("sdk/core/promise");
|
||||
|
||||
window.addEventListener("message", function(event) {
|
||||
try {
|
||||
let json = JSON.parse(event.data);
|
||||
if (json.name == "connection") {
|
||||
let cid = parseInt(json.cid);
|
||||
for (let c of ConnectionManager.connections) {
|
||||
if (c.uid == cid) {
|
||||
UI.connection = c;
|
||||
UI.onNewConnection();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
}, false);
|
||||
|
||||
let UI = {
|
||||
onload: function() {
|
||||
this.template = new Template(document.body, AppProjects.store, Utils.l10n);
|
||||
this.template.start();
|
||||
|
||||
AppProjects.store.on("set", (event,path,value) => {
|
||||
if (path == "projects") {
|
||||
AppProjects.store.object.projects.forEach(UI.validate);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onNewConnection: function() {
|
||||
this.connection.on(Connection.Events.STATUS_CHANGED, () => this._onConnectionStatusChange());
|
||||
this._onConnectionStatusChange();
|
||||
},
|
||||
|
||||
_onConnectionStatusChange: function() {
|
||||
if (this.connection.status != Connection.Status.CONNECTED) {
|
||||
document.body.classList.remove("connected");
|
||||
this.listTabsResponse = null;
|
||||
} else {
|
||||
document.body.classList.add("connected");
|
||||
this.connection.client.listTabs(
|
||||
response => {this.listTabsResponse = response}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_selectFolder: function() {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, Utils.l10n("project.filePickerTitle"), Ci.nsIFilePicker.modeGetFolder);
|
||||
let res = fp.show();
|
||||
if (res != Ci.nsIFilePicker.returnCancel)
|
||||
return fp.file;
|
||||
return null;
|
||||
},
|
||||
|
||||
addPackaged: function() {
|
||||
let folder = this._selectFolder();
|
||||
if (!folder)
|
||||
return;
|
||||
AppProjects.addPackaged(folder)
|
||||
.then(function (project) {
|
||||
UI.validate(project);
|
||||
UI.selectProject(project.location);
|
||||
});
|
||||
},
|
||||
|
||||
addHosted: function() {
|
||||
let urlInput = document.querySelector("#url-input");
|
||||
let manifestURL = urlInput.value;
|
||||
AppProjects.addHosted(manifestURL)
|
||||
.then(function (project) {
|
||||
UI.validate(project);
|
||||
UI.selectProject(project.location);
|
||||
});
|
||||
},
|
||||
|
||||
_getLocalIconURL: function(project, manifest) {
|
||||
let icon;
|
||||
if (manifest.icons) {
|
||||
let size = Object.keys(manifest.icons).sort(function(a, b) b - a)[0];
|
||||
if (size) {
|
||||
icon = manifest.icons[size];
|
||||
}
|
||||
}
|
||||
if (!icon)
|
||||
return null;
|
||||
if (project.type == "hosted") {
|
||||
let manifestURL = Services.io.newURI(project.location, null, null);
|
||||
let origin = Services.io.newURI(manifestURL.prePath, null, null);
|
||||
return Services.io.newURI(icon, null, origin).spec;
|
||||
} else if (project.type == "packaged") {
|
||||
let projectFolder = FileUtils.File(project.location);
|
||||
let folderURI = Services.io.newFileURI(projectFolder).spec;
|
||||
return folderURI + icon.replace(/^\/|\\/, "");
|
||||
}
|
||||
},
|
||||
|
||||
validate: function(project) {
|
||||
let validation = new AppValidator(project);
|
||||
validation.validate()
|
||||
.then(function () {
|
||||
if (validation.manifest) {
|
||||
project.name = validation.manifest.name;
|
||||
project.icon = UI._getLocalIconURL(project, validation.manifest);
|
||||
project.manifest = validation.manifest;
|
||||
}
|
||||
|
||||
project.validationStatus = "valid";
|
||||
|
||||
if (validation.warnings.length > 0) {
|
||||
project.warningsCount = validation.warnings.length;
|
||||
project.warnings = validation.warnings.join(",\n ");
|
||||
project.validationStatus = "warning";
|
||||
} else {
|
||||
project.warnings = "";
|
||||
project.warningsCount = 0;
|
||||
}
|
||||
|
||||
if (validation.errors.length > 0) {
|
||||
project.errorsCount = validation.errors.length;
|
||||
project.errors = validation.errors.join(",\n ");
|
||||
project.validationStatus = "error";
|
||||
} else {
|
||||
project.errors = "";
|
||||
project.errorsCount = 0;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
update: function(location) {
|
||||
let project = AppProjects.get(location);
|
||||
this.validate(project);
|
||||
},
|
||||
|
||||
remove: function(location) {
|
||||
AppProjects.remove(location);
|
||||
},
|
||||
|
||||
_getProjectManifestURL: function (project) {
|
||||
if (project.type == "packaged") {
|
||||
return "app://" + project.packagedAppOrigin + "/manifest.webapp";
|
||||
} else if (project.type == "hosted") {
|
||||
return project.location;
|
||||
}
|
||||
},
|
||||
|
||||
start: function(location) {
|
||||
let project = AppProjects.get(location);
|
||||
let request = {
|
||||
to: this.listTabsResponse.webappsActor,
|
||||
type: "launch",
|
||||
manifestURL: this._getProjectManifestURL(project)
|
||||
};
|
||||
this.connection.client.request(request, (res) => {
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
stop: function(location) {
|
||||
let project = AppProjects.get(location);
|
||||
let request = {
|
||||
to: this.listTabsResponse.webappsActor,
|
||||
type: "close",
|
||||
manifestURL: this._getProjectManifestURL(project)
|
||||
};
|
||||
this.connection.client.request(request, (res) => {
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
_getTargetForApp: function(manifest) { // FIXME <- will be implemented in bug 912476
|
||||
if (!this.listTabsResponse)
|
||||
return null;
|
||||
let actor = this.listTabsResponse.webappsActor;
|
||||
let deferred = promise.defer();
|
||||
let request = {
|
||||
to: actor,
|
||||
type: "getAppActor",
|
||||
manifestURL: manifest,
|
||||
}
|
||||
this.connection.client.request(request, (res) => {
|
||||
if (res.error) {
|
||||
deferred.reject(res.error);
|
||||
} else {
|
||||
let options = {
|
||||
form: res.actor,
|
||||
client: this.connection.client,
|
||||
chrome: false
|
||||
};
|
||||
|
||||
devtools.TargetFactory.forRemoteTab(options).then((target) => {
|
||||
deferred.resolve(target)
|
||||
}, (error) => {
|
||||
deferred.reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
openToolbox: function(location) {
|
||||
let project = AppProjects.get(location);
|
||||
let manifest = this._getProjectManifestURL(project);
|
||||
this._getTargetForApp(manifest).then((target) => {
|
||||
gDevTools.showToolbox(target,
|
||||
null,
|
||||
devtools.Toolbox.HostType.WINDOW,
|
||||
this.connection.uid);
|
||||
}, console.error);
|
||||
},
|
||||
|
||||
|
||||
reveal: function(location) {
|
||||
let project = AppProjects.get(location);
|
||||
if (project.type == "packaged") {
|
||||
let projectFolder = FileUtils.File(project.location);
|
||||
projectFolder.reveal();
|
||||
} else {
|
||||
// TODO: eventually open hosted apps in firefox
|
||||
// when permissions are correctly supported by firefox
|
||||
}
|
||||
},
|
||||
|
||||
selectProject: function(location) {
|
||||
let projects = AppProjects.store.object.projects;
|
||||
let idx = 0;
|
||||
for (; idx < projects.length; idx++) {
|
||||
if (projects[idx].location == location) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx == projects.length) {
|
||||
// Not found
|
||||
return;
|
||||
}
|
||||
|
||||
let oldButton = document.querySelector(".project-item.selected");
|
||||
if (oldButton) {
|
||||
oldButton.classList.remove("selected");
|
||||
}
|
||||
let button = document.getElementById(location);
|
||||
button.classList.add("selected");
|
||||
|
||||
let template = '{"path":"projects.' + idx + '","childSelector":"#lense-template"}';
|
||||
|
||||
let lense = document.querySelector("#lense");
|
||||
lense.setAttribute("template-for", template);
|
||||
this.template._processFor(lense);
|
||||
},
|
||||
}
|
93
browser/devtools/app-manager/content/projects.xhtml
Normal file
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % appMgrDTD SYSTEM "chrome://browser/locale/devtools/app-manager.dtd" >
|
||||
%appMgrDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<meta charset="utf8"/>
|
||||
<base href="chrome://browser/content/devtools/app-manager/"></base>
|
||||
<title>&projects.title;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/devtools/app-manager/projects.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body onload="UI.onload()">
|
||||
<aside id="sidebar">
|
||||
<div id="project-list" template='{"type":"attribute","path":"projects.length","name":"projects-count"}'>
|
||||
<div template-loop='{"arrayPath":"projects","childSelector":"#project-item-template"}'></div>
|
||||
<div id="no-project">&projects.noProject;</div>
|
||||
</div>
|
||||
<div id="new-packaged-project" onclick="UI.addPackaged()">&projects.addPackaged;</div>
|
||||
<div id="new-hosted-project">&projects.addHosted;
|
||||
<form onsubmit="UI.addHosted(); return false;" id="new-hosted-project-wrapper">
|
||||
<input value="" id="url-input" type="url" pattern="https?://.+" placeholder="&projects.hostedManifestPlaceHolder;" size="50" />
|
||||
<div onclick="UI.addHosted()" id="new-hosted-project-click"></div>
|
||||
<input type="submit" hidden="true"></input>
|
||||
</form>
|
||||
</div>
|
||||
</aside>
|
||||
<section id="lense"></section>
|
||||
</body>
|
||||
|
||||
<template id="project-item-template">
|
||||
<div class="project-item" template='{"type":"attribute","path":"location","name":"id"}' onclick="UI.selectProject(this.id)">
|
||||
<div class="project-item-status" template='{"type":"attribute","path":"validationStatus","name":"status"}'></div>
|
||||
<img class="project-item-icon" template='{"type":"attribute","path":"icon","name":"src"}' />
|
||||
<div class="project-item-meta">
|
||||
<div class="button-remove" onclick="UI.remove(this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}' title="&projects.removeApp;"></div>
|
||||
<strong template='{"type":"textContent","path":"name"}'></strong>
|
||||
<span class="project-item-type" template='{"type":"textContent","path":"type"}'></span>
|
||||
<p class="project-item-description" template='{"type":"textContent","path":"manifest.description"}'></p>
|
||||
<div template='{"type":"attribute","path":"validationStatus","name":"status"}'>
|
||||
<div class="project-item-errors"><span template='{"type":"textContent","path":"errorsCount"}'></span></div>
|
||||
<div class="project-item-warnings"><span template='{"type":"textContent","path":"warningsCount"}'></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="lense-template">
|
||||
<div>
|
||||
<div class="project-details" template='{"type":"attribute","path":"validationStatus","name":"status"}'>
|
||||
<div class="project-header">
|
||||
<img class="project-icon" template='{"type":"attribute","path":"icon","name":"src"}'/>
|
||||
<div class="project-details">
|
||||
<div class="project-title">
|
||||
<h1 template='{"type":"textContent","path":"name"}'></h1>
|
||||
<div class="project-status" template='{"type":"attribute","path":"validationStatus","name":"status"}'>
|
||||
<p class="project-validation" template='{"type":"textContent","path":"validationStatus"}'></p>
|
||||
<p class="project-type" template='{"type":"textContent","path":"type"}'></p>
|
||||
</div>
|
||||
</div>
|
||||
<span template='{"type":"textContent","path":"manifest.developer.name"}'></span>
|
||||
<p class="project-location" template='{"type":"textContent","path":"location"}' onclick="UI.reveal(this.textContent)"></p>
|
||||
<p class="project-description" template='{"type":"textContent","path":"manifest.description"}'></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="project-buttons">
|
||||
<button class="project-button-refresh" onclick="UI.update(this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}'>&projects.reloadFiles;</button>
|
||||
<!-- Not available until bug 911785 is fixed
|
||||
<button class="device-action project-button-install" onclick="UI.install(this, this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}'>&projects.installApp;</button>
|
||||
-->
|
||||
<button class="device-action project-button-start" onclick="UI.start(this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}'>&projects.startApp;</button>
|
||||
<button class="device-action project-button-stop" onclick="UI.stop(this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}'>&projects.stopApp;</button>
|
||||
<!-- Not available until bug 911785 is fixed
|
||||
<button class="device-action project-button-debug" onclick="UI.openToolbox(this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}'>&projects.debugApp;</button>
|
||||
-->
|
||||
</div>
|
||||
<div class="project-errors" template='{"type":"textContent","path":"errors"}'></div>
|
||||
<div class="project-warnings" template='{"type":"textContent","path":"warnings"}'></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script type="application/javascript;version=1.8" src="utils.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="projects.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="template.js"></script>
|
||||
</html>
|
@ -65,6 +65,7 @@ function Template(root, store, l10nResolver) {
|
||||
|
||||
this._nodeListeners = new Map();
|
||||
this._loopListeners = new Map();
|
||||
this._forListeners = new Map();
|
||||
this._root = root;
|
||||
this._doc = this._root.ownerDocument;
|
||||
|
||||
@ -134,6 +135,14 @@ Template.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// For:
|
||||
set = this._forListeners.get(path);
|
||||
if (set) {
|
||||
for (let elt of set) {
|
||||
this._processFor(elt);
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes:
|
||||
set = this._nodeListeners.get(path);
|
||||
if (set) {
|
||||
@ -176,6 +185,14 @@ Template.prototype = {
|
||||
set.add(element);
|
||||
},
|
||||
|
||||
_registerFor: function(path, element) {
|
||||
if (!this._forListeners.has(path)) {
|
||||
this._forListeners.set(path, new Set());
|
||||
}
|
||||
let set = this._forListeners.get(path);
|
||||
set.add(element);
|
||||
},
|
||||
|
||||
_processNode: function(element, rootPath="") {
|
||||
// The actual magic.
|
||||
// The element has a template attribute.
|
||||
@ -263,8 +280,8 @@ Template.prototype = {
|
||||
// through the array, and build one child per
|
||||
// item. The template for this child is pointed
|
||||
// by the childSelector property.
|
||||
let e = element;
|
||||
try {
|
||||
let e = element;
|
||||
let template, count;
|
||||
let str = e.getAttribute("template-loop");
|
||||
let json = JSON.parse(str);
|
||||
@ -304,12 +321,51 @@ Template.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_processFor: function(element, rootPath="") {
|
||||
let e = element;
|
||||
try {
|
||||
let template;
|
||||
let str = e.getAttribute("template-for");
|
||||
let json = JSON.parse(str);
|
||||
if (!("path" in json) ||
|
||||
!("childSelector" in json)) {
|
||||
throw new Error("missing property");
|
||||
}
|
||||
if (rootPath) {
|
||||
json.path = rootPath + "." + json.path;
|
||||
}
|
||||
|
||||
let templateParent = this._doc.querySelector(json.childSelector);
|
||||
if (!templateParent) {
|
||||
throw new Error("can't find child");
|
||||
}
|
||||
let content = this._doc.createElement("div");
|
||||
content.innerHTML = templateParent.innerHTML;
|
||||
content = content.firstElementChild;
|
||||
|
||||
this._processTree(content, json.path);
|
||||
|
||||
this._unregisterNodes(e.querySelectorAll("[template]"));
|
||||
this._registerFor(json.path, e);
|
||||
|
||||
e.innerHTML = "";
|
||||
e.appendChild(content);
|
||||
|
||||
} catch(exception) {
|
||||
console.error("Invalid template: " + e.outerHTML + " (" + exception + ")");
|
||||
}
|
||||
},
|
||||
|
||||
_processTree: function(parent, rootPath="") {
|
||||
let loops = parent.querySelectorAll(":not(template) [template-loop]");
|
||||
let fors = parent.querySelectorAll(":not(template) [template-for]");
|
||||
let nodes = parent.querySelectorAll(":not(template) [template]");
|
||||
for (let e of loops) {
|
||||
this._processLoop(e, rootPath);
|
||||
}
|
||||
for (let e of fors) {
|
||||
this._processFor(e, rootPath);
|
||||
}
|
||||
for (let e of nodes) {
|
||||
this._processNode(e, rootPath);
|
||||
}
|
||||
|
53
browser/devtools/app-manager/content/utils.js
Normal file
@ -0,0 +1,53 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
*
|
||||
* Some helpers for common operations in the App Manager:
|
||||
*
|
||||
* . mergeStores: merge several store into one.
|
||||
* . l10n: resolves strings from app-manager.properties.
|
||||
*
|
||||
*/
|
||||
|
||||
let Utils = (function() {
|
||||
const Cu = Components.utils;
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
|
||||
function _forwardSetEvent(key, store, finalStore) {
|
||||
store.on("set", function(event, path, value) {
|
||||
finalStore.emit("set", [key].concat(path), value);
|
||||
});
|
||||
}
|
||||
|
||||
function mergeStores(stores) {
|
||||
let finalStore = {object:{}};
|
||||
EventEmitter.decorate(finalStore);
|
||||
for (let key in stores) {
|
||||
finalStore.object[key] = stores[key].object,
|
||||
_forwardSetEvent(key, stores[key], finalStore);
|
||||
}
|
||||
return finalStore;
|
||||
}
|
||||
|
||||
|
||||
let strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
|
||||
|
||||
function l10n (property, args = []) {
|
||||
if (args && args.length > 0) {
|
||||
return strings.formatStringFromName(property, args, args.length);
|
||||
} else {
|
||||
return strings.GetStringFromName(property);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mergeStores: mergeStores,
|
||||
l10n: l10n
|
||||
}
|
||||
})();
|
@ -1122,7 +1122,7 @@ SourceScripts.prototype = {
|
||||
window.clearTimeout(fetchTimeout);
|
||||
}
|
||||
if (aResponse.error) {
|
||||
deferred.reject([aSource, aResponse.message]);
|
||||
deferred.reject([aSource, aResponse.message || aResponse.error]);
|
||||
} else {
|
||||
deferred.resolve([aSource, aResponse.source]);
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ const SEARCH_TOKEN_FLAG = "#";
|
||||
const SEARCH_LINE_FLAG = ":";
|
||||
const SEARCH_VARIABLE_FLAG = "*";
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
|
||||
|
||||
/**
|
||||
* Object defining the debugger view components.
|
||||
*/
|
||||
@ -279,8 +281,10 @@ let DebuggerView = {
|
||||
window.dispatchEvent(document, "Debugger:SourceShown", aSource);
|
||||
},
|
||||
([, aError]) => {
|
||||
// Rejected. TODO: Bug 884484.
|
||||
let msg = "Error loading: " + aSource.url + "\n" + aError;
|
||||
// Rejected.
|
||||
let msg = L10N.getStr("errorLoadingText") + DevToolsUtils.safeErrorString(aError);
|
||||
this.editor.setText(msg);
|
||||
window.dispatchEvent(document, "Debugger:SourceErrorShown", aError);
|
||||
dumpn(msg);
|
||||
Cu.reportError(msg);
|
||||
});
|
||||
|
@ -67,6 +67,7 @@ MOCHITEST_BROWSER_TESTS = \
|
||||
browser_dbg_sources-cache.js \
|
||||
browser_dbg_scripts-switching.js \
|
||||
browser_dbg_scripts-switching-02.js \
|
||||
browser_dbg_scripts-switching-03.js \
|
||||
browser_dbg_scripts-sorting.js \
|
||||
browser_dbg_scripts-searching-01.js \
|
||||
browser_dbg_scripts-searching-02.js \
|
||||
|
@ -0,0 +1,60 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Make sure that the Debugger-View error loading source text is correct
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
|
||||
|
||||
var gPane = null;
|
||||
var gTab = null;
|
||||
var gDebuggee = null;
|
||||
var gDebugger = null;
|
||||
var gView = null;
|
||||
var gL10N = null;
|
||||
|
||||
function test()
|
||||
{
|
||||
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane){
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
gView = gDebugger.DebuggerView;
|
||||
gL10N = gDebugger.L10N;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
});
|
||||
}
|
||||
|
||||
function onScriptShown(aEvent)
|
||||
{
|
||||
gView.editorSource = { url: "http://example.com/fake.js", actor: "fake.actor" };
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.addEventListener("Debugger:SourceErrorShown", onScriptErrorShown);
|
||||
}
|
||||
|
||||
function onScriptErrorShown(aEvent)
|
||||
{
|
||||
gDebugger.removeEventListener("Debugger:SourceErrorShown", onScriptErrorShown);
|
||||
testDebuggerLoadingError();
|
||||
}
|
||||
|
||||
function testDebuggerLoadingError()
|
||||
{
|
||||
ok(gDebugger.editor.getText().search(new RegExp(gL10N.getStr("errorLoadingText")) != -1),
|
||||
"The valid error loading message is displayed.");
|
||||
closeDebuggerAndFinish();
|
||||
}
|
||||
|
||||
registerCleanupFunction(function()
|
||||
{
|
||||
removeTab(gTab);
|
||||
gPane = null;
|
||||
gTab = null;
|
||||
gDebugger = null;
|
||||
gView = null;
|
||||
gDebuggee = null;
|
||||
gL10N = null;
|
||||
});
|
@ -374,6 +374,13 @@ let gDevToolsBrowser = {
|
||||
gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/connect.xhtml");
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the App Manager
|
||||
*/
|
||||
openAppManager: function(gBrowser) {
|
||||
gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/app-manager/index.xul");
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this DevTools's presence to a browser window's document
|
||||
*
|
||||
|
@ -66,3 +66,12 @@ browser.jar:
|
||||
content/browser/devtools/connect.css (framework/connect/connect.css)
|
||||
content/browser/devtools/connect.js (framework/connect/connect.js)
|
||||
content/browser/devtools/app-manager/template.js (app-manager/content/template.js)
|
||||
content/browser/devtools/app-manager/utils.js (app-manager/content/utils.js)
|
||||
content/browser/devtools/app-manager/connection-footer.js (app-manager/content/connection-footer.js)
|
||||
content/browser/devtools/app-manager/connection-footer.xhtml (app-manager/content/connection-footer.xhtml)
|
||||
content/browser/devtools/app-manager/device.js (app-manager/content/device.js)
|
||||
content/browser/devtools/app-manager/device.xhtml (app-manager/content/device.xhtml)
|
||||
content/browser/devtools/app-manager/projects.js (app-manager/content/projects.js)
|
||||
content/browser/devtools/app-manager/projects.xhtml (app-manager/content/projects.xhtml)
|
||||
content/browser/devtools/app-manager/index.xul (app-manager/content/index.xul)
|
||||
content/browser/devtools/app-manager/index.js (app-manager/content/index.js)
|
||||
|
@ -436,7 +436,7 @@ ResponsiveUI.prototype = {
|
||||
this.resizeBarH.className = "devtools-responsiveui-resizebarH";
|
||||
this.resizeBarH.setAttribute("bottom", "0");
|
||||
this.resizeBarH.setAttribute("left", "0");
|
||||
this.resizeBarV.setAttribute("tooltiptext", resizerTooltip);
|
||||
this.resizeBarH.setAttribute("tooltiptext", resizerTooltip);
|
||||
this.resizeBarH.onmousedown = this.bound_startResizing;
|
||||
|
||||
this.container.insertBefore(this.toolbar, this.stack);
|
||||
|
@ -352,6 +352,7 @@ InplaceEditor.prototype = {
|
||||
|
||||
this.input.value = newValue.value;
|
||||
this.input.setSelectionRange(newValue.start, newValue.end);
|
||||
this.warning.hidden = this.validate(this.input.value);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
@ -67,7 +67,7 @@ function createDummyDocument() {
|
||||
docShell.createAboutBlankContentViewer(Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal));
|
||||
let window = docShell.contentViewer.DOMDocument.defaultView;
|
||||
window.location = "data:text/html,<html></html>";
|
||||
let deferred = promise.defer()
|
||||
let deferred = promise.defer();
|
||||
eventTarget.addEventListener("DOMContentLoaded", function handler(event) {
|
||||
eventTarget.removeEventListener("DOMContentLoaded", handler, false);
|
||||
deferred.resolve(window.document);
|
||||
@ -126,8 +126,6 @@ function ElementStyle(aElement, aStore, aPageStyle)
|
||||
this.store.disabled = new WeakMap();
|
||||
}
|
||||
|
||||
let doc = aElement.ownerDocument;
|
||||
|
||||
// To figure out how shorthand properties are interpreted by the
|
||||
// engine, we will set properties on a dummy element and observe
|
||||
// how their .style attribute reflects them as computed values.
|
||||
@ -333,9 +331,9 @@ ElementStyle.prototype = {
|
||||
for (let computedProp of computedProps) {
|
||||
let earlier = taken[computedProp.name];
|
||||
let overridden;
|
||||
if (earlier
|
||||
&& computedProp.priority === "important"
|
||||
&& earlier.priority !== "important") {
|
||||
if (earlier &&
|
||||
computedProp.priority === "important" &&
|
||||
earlier.priority !== "important") {
|
||||
// New property is higher priority. Mark the earlier property
|
||||
// overridden (which will reverse its dirty state).
|
||||
earlier._overriddenDirty = !earlier._overriddenDirty;
|
||||
@ -545,7 +543,6 @@ Rule.prototype = {
|
||||
|
||||
aModifications.setProperty(prop.name, prop.value, prop.priority);
|
||||
|
||||
|
||||
prop.updateComputed();
|
||||
}
|
||||
|
||||
@ -574,15 +571,14 @@ Rule.prototype = {
|
||||
name: textProp.name,
|
||||
value: "",
|
||||
priority: ""
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (aName && textProp.name == aName) {
|
||||
store.userProperties.setProperty(
|
||||
this.style, textProp.name,
|
||||
null,
|
||||
cssProp.value,
|
||||
textProp.value);
|
||||
this.style,
|
||||
textProp.name,
|
||||
cssProp.value);
|
||||
}
|
||||
textProp.priority = cssProp.priority;
|
||||
}
|
||||
@ -998,7 +994,7 @@ TextProperty.prototype = {
|
||||
function CssRuleView(aDoc, aStore, aPageStyle)
|
||||
{
|
||||
this.doc = aDoc;
|
||||
this.store = aStore;
|
||||
this.store = aStore || {};
|
||||
this.pageStyle = aPageStyle;
|
||||
this.element = this.doc.createElementNS(HTML_NS, "div");
|
||||
this.element.className = "ruleview devtools-monospace";
|
||||
@ -1995,7 +1991,7 @@ TextPropertyEditor.prototype = {
|
||||
*/
|
||||
function UserProperties()
|
||||
{
|
||||
this.weakMap = new WeakMap();
|
||||
this.map = new Map();
|
||||
}
|
||||
|
||||
UserProperties.prototype = {
|
||||
@ -2006,28 +2002,26 @@ UserProperties.prototype = {
|
||||
* The CSSStyleDeclaration against which the property is mapped.
|
||||
* @param {string} aName
|
||||
* The name of the property to get.
|
||||
* @param {string} aComputedValue
|
||||
* The computed value of the property. The user value will only be
|
||||
* returned if the computed value hasn't changed since, and this will
|
||||
* be returned as the default if no user value is available.
|
||||
* @param {string} aDefault
|
||||
* The value to return if the property is has been changed outside of
|
||||
* the rule view.
|
||||
* @return {string}
|
||||
* The property value if it has previously been set by the user, null
|
||||
* otherwise.
|
||||
*/
|
||||
getProperty: function UP_getProperty(aStyle, aName, aComputedValue) {
|
||||
let entry = this.weakMap.get(aStyle, null);
|
||||
getProperty: function(aStyle, aName, aDefault) {
|
||||
let key = this.getKey(aStyle);
|
||||
let entry = this.map.get(key, null);
|
||||
|
||||
if (entry && aName in entry) {
|
||||
let item = entry[aName];
|
||||
if (item.computed != aComputedValue) {
|
||||
if (item != aDefault) {
|
||||
delete entry[aName];
|
||||
return aComputedValue;
|
||||
return aDefault;
|
||||
}
|
||||
|
||||
return item.user;
|
||||
return item;
|
||||
}
|
||||
return aComputedValue;
|
||||
|
||||
return aDefault;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2037,20 +2031,18 @@ UserProperties.prototype = {
|
||||
* The CSSStyleDeclaration against which the property is to be mapped.
|
||||
* @param {String} aName
|
||||
* The name of the property to set.
|
||||
* @param {String} aComputedValue
|
||||
* The computed property value. The user value will not be used if the
|
||||
* computed value changes.
|
||||
* @param {String} aUserValue
|
||||
* The value of the property to set.
|
||||
*/
|
||||
setProperty: function UP_setProperty(aStyle, aName, aComputedValue, aUserValue) {
|
||||
let entry = this.weakMap.get(aStyle, null);
|
||||
setProperty: function(aStyle, aName, aUserValue) {
|
||||
let key = this.getKey(aStyle);
|
||||
let entry = this.map.get(key, null);
|
||||
if (entry) {
|
||||
entry[aName] = { computed: aComputedValue, user: aUserValue };
|
||||
entry[aName] = aUserValue;
|
||||
} else {
|
||||
let props = {};
|
||||
props[aName] = { computed: aComputedValue, user: aUserValue };
|
||||
this.weakMap.set(aStyle, props);
|
||||
props[aName] = aUserValue;
|
||||
this.map.set(key, props);
|
||||
}
|
||||
},
|
||||
|
||||
@ -2062,10 +2054,15 @@ UserProperties.prototype = {
|
||||
* @param {String} aName
|
||||
* The name of the property to check.
|
||||
*/
|
||||
contains: function UP_contains(aStyle, aName) {
|
||||
let entry = this.weakMap.get(aStyle, null);
|
||||
contains: function(aStyle, aName) {
|
||||
let key = this.getKey(aStyle);
|
||||
let entry = this.map.get(key, null);
|
||||
return !!entry && aName in entry;
|
||||
},
|
||||
|
||||
getKey: function(aStyle) {
|
||||
return aStyle.href + ":" + aStyle.line;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,7 @@ function RuleViewTool(aInspector, aWindow, aIFrame)
|
||||
this.doc = aWindow.document;
|
||||
this.outerIFrame = aIFrame;
|
||||
|
||||
this.view = new RuleView.CssRuleView(this.doc, null);
|
||||
this.view = new RuleView.CssRuleView(this.doc);
|
||||
this.doc.documentElement.appendChild(this.view.element);
|
||||
|
||||
this._changeHandler = () => {
|
||||
@ -264,4 +264,4 @@ ComputedViewTool.prototype = {
|
||||
delete this.document;
|
||||
delete this.inspector;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -12,16 +12,17 @@ function startTest(aInspector, aRuleView)
|
||||
inspector = aInspector;
|
||||
ruleWindow = aRuleView.doc.defaultView;
|
||||
ruleView = aRuleView;
|
||||
let style = '' +
|
||||
'#testid {' +
|
||||
' background-color: blue;' +
|
||||
'} ' +
|
||||
'.testclass, .unmatched {' +
|
||||
' background-color: green;' +
|
||||
'}';
|
||||
let style = "" +
|
||||
"#testid {" +
|
||||
" background-color: blue;" +
|
||||
"}" +
|
||||
".testclass, .unmatched {" +
|
||||
" background-color: green;" +
|
||||
"}";
|
||||
|
||||
let styleNode = addStyle(doc, style);
|
||||
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
|
||||
doc.body.innerHTML = "<div id='testid' class='testclass'>Styled Node</div>" +
|
||||
"<div id='testid2'>Styled Node</div>";
|
||||
|
||||
let testElement = doc.getElementById("testid");
|
||||
inspector.selection.setNode(testElement);
|
||||
@ -30,7 +31,7 @@ function startTest(aInspector, aRuleView)
|
||||
inspector.selection.setNode(null);
|
||||
inspector.once("inspector-updated", () => {
|
||||
is(ruleView.element.querySelectorAll("#noResults").length, 1, "After highlighting null, has a no-results element again.");
|
||||
inspector.selection.setNode(testElement)
|
||||
inspector.selection.setNode(testElement);
|
||||
inspector.once("inspector-updated", () => {
|
||||
let classEditor = ruleView.element.children[2]._ruleEditor;
|
||||
is(classEditor.selectorText.querySelector(".ruleview-selector-matched").textContent, ".testclass", ".textclass should be matched.");
|
||||
@ -50,7 +51,6 @@ function testCancelNew()
|
||||
let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
|
||||
waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
|
||||
is(inplaceEditor(elementRuleEditor.newPropSpan), aEditor, "Next focused editor should be the new property editor.");
|
||||
let input = aEditor.input;
|
||||
waitForEditorBlur(aEditor, function () {
|
||||
ok(!elementRuleEditor.rule._applyingModifications, "Shouldn't have an outstanding modification request after a cancel.");
|
||||
is(elementRuleEditor.rule.textProps.length, 0, "Should have canceled creating a new text property.");
|
||||
@ -137,7 +137,7 @@ function testEditProperty()
|
||||
input.select();
|
||||
|
||||
waitForEditorBlur(aEditor, function() {
|
||||
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {;
|
||||
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
|
||||
is(idRuleEditor.rule.style._rawStyle().getPropertyValue("border-color"), "red",
|
||||
"border-color should have been set.");
|
||||
|
||||
@ -182,10 +182,29 @@ function testDisableProperty()
|
||||
is(idRuleEditor.rule.style._rawStyle().getPropertyValue("border-color"), "red",
|
||||
"Border-color should have been reset.");
|
||||
|
||||
finishTest();
|
||||
testPropertyStillMarkedDirty();
|
||||
}));
|
||||
}
|
||||
|
||||
function testPropertyStillMarkedDirty() {
|
||||
// Select an unstyled node.
|
||||
let testElement = doc.getElementById("testid2");
|
||||
inspector.selection.setNode(testElement);
|
||||
inspector.once("inspector-updated", () => {
|
||||
// Select the original node again.
|
||||
testElement = doc.getElementById("testid");
|
||||
inspector.selection.setNode(testElement);
|
||||
inspector.once("inspector-updated", () => {
|
||||
let props = ruleView.element.querySelectorAll(".ruleview-property");
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
is(props[i].hasAttribute("dirty"), i <= 1,
|
||||
"props[" + i + "] marked dirty as appropriate");
|
||||
}
|
||||
finishTest();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function finishTest()
|
||||
{
|
||||
inspector = ruleWindow = ruleView = null;
|
||||
|
@ -250,6 +250,8 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
<!ENTITY devToolbarCloseButton.tooltiptext "Close Developer Toolbar">
|
||||
<!ENTITY devToolbarMenu.label "Developer Toolbar">
|
||||
<!ENTITY devToolbarMenu.accesskey "v">
|
||||
<!ENTITY devAppMgrMenu.label "App Manager">
|
||||
<!ENTITY devAppMgrMenu.accesskey "a">
|
||||
<!ENTITY devToolbar.keycode "VK_F2">
|
||||
<!ENTITY devToolbar.keytext "F2">
|
||||
<!ENTITY devToolboxMenuItem.label "Toggle Tools">
|
||||
|
@ -0,0 +1,49 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<!ENTITY index.title "App Manager">
|
||||
<!ENTITY index.projects "My Apps">
|
||||
<!ENTITY index.device "My Device">
|
||||
|
||||
<!ENTITY device.screenshot "Screenshot">
|
||||
<!ENTITY device.title "Device Control Center">
|
||||
<!ENTITY device.notConnected "Not connected. Please connect your device below.">
|
||||
<!ENTITY device.startApp "Start">
|
||||
<!ENTITY device.stopApp "Stop">
|
||||
<!ENTITY device.debugApp "Debug">
|
||||
<!ENTITY device.name "Name">
|
||||
<!ENTITY device.app "App">
|
||||
<!ENTITY device.privileged "Privileged">
|
||||
<!ENTITY device.certified "Certified">
|
||||
<!ENTITY device.allow "Allow">
|
||||
<!ENTITY device.prompt "Prompt">
|
||||
<!ENTITY device.deny "Deny">
|
||||
<!ENTITY device.installedApps "Installed Apps">
|
||||
<!ENTITY device.permissions "Permissions">
|
||||
|
||||
<!ENTITY connection.disconnect "Disconnect">
|
||||
<!ENTITY connection.showDeviceCtrlCenter "Click for More Details">
|
||||
<!ENTITY connection.notConnected "Not Connected">
|
||||
<!ENTITY connection.changeHostAndPort "Change">
|
||||
<!ENTITY connection.startSimulator "Start Simulator">
|
||||
<!ENTITY connection.saveConnectionInfo "Save">
|
||||
<!ENTITY connection.connecting "Connecting…">
|
||||
<!ENTITY connection.disconnecting "Disconnecting…">
|
||||
<!ENTITY connection.cancel "Cancel">
|
||||
<!ENTITY connection.or "or">
|
||||
|
||||
<!ENTITY projects.localApps "Local Apps">
|
||||
<!ENTITY projects.addApp "Add">
|
||||
<!ENTITY projects.addPackaged "Add Packaged App">
|
||||
<!ENTITY projects.addHosted "Add Hosted App">
|
||||
<!ENTITY projects.title "Local Apps">
|
||||
<!ENTITY projects.appDetails "App Details">
|
||||
<!ENTITY projects.removeApp "Remove">
|
||||
<!ENTITY projects.reloadFiles "Refresh">
|
||||
<!ENTITY projects.installApp "Install">
|
||||
<!ENTITY projects.startApp "Start">
|
||||
<!ENTITY projects.stopApp "Stop">
|
||||
<!ENTITY projects.debugApp "Debug">
|
||||
<!ENTITY projects.hostedManifestPlaceHolder "http://example.com/app/webapp.manifest">
|
||||
<!ENTITY projects.noProject "No project linked. Add a new packaged app below (a directory) or a hosted app (link to a manifest file).">
|
@ -0,0 +1,23 @@
|
||||
# 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/.
|
||||
|
||||
device.deviceSize=Device size: %1$Sx%2$S (%3$S DPI)
|
||||
connection.connectedToDevice=Connected to %1$S
|
||||
connection.connectTo=Connect to %1$S:%2$S
|
||||
project.filePickerTitle=Select a webapp folder
|
||||
|
||||
validator.nonExistingFolder=The project folder doesn't exists
|
||||
validator.expectProjectFolder=The project folder ends up being a file
|
||||
validator.wrongManifestFileName=Packaged apps require a manifest file that can only be named 'manifest.webapp' at project root folder
|
||||
validator.invalidManifestURL=Invalid manifest URL '%S'
|
||||
validator.invalidManifestJSON=The webapp manifest isn't a valid JSON file: %1$S at: %2$S
|
||||
validator.noAccessManifestURL=Unable to read manifest file: %1$S at: %2$S
|
||||
validator.invalidHostedManifestURL=Invalid hosted manifest URL '%1$S': %2$S
|
||||
validator.invalidProjectType=Unknown project type '%S'
|
||||
validator.missNameManifestProperty=Missing mandatory 'name' in Manifest.
|
||||
validator.missIconsManifestProperty=Missing 'icons' in Manifest.
|
||||
validator.missIconForMarketplace=app submission to the Marketplace needs at least an 128 icon
|
||||
validator.invalidAppType=Unknown app type: '%S'.
|
||||
validator.invalidHostedPriviledges=Hosted App can't be type '%S'.
|
||||
validator.noCertifiedSupport='certified' apps are not fully supported on the App manager.
|
@ -133,6 +133,10 @@ breakpointMenuItem.deleteAll=Remove all breakpoints
|
||||
# yet.
|
||||
loadingText=Loading\u2026
|
||||
|
||||
# LOCALIZATION NOTE (errorLoadingText): The text that is displayed in the debugger
|
||||
# viewer when there is an error loading a file
|
||||
errorLoadingText=Error loading source:\n
|
||||
|
||||
# LOCALIZATION NOTE (emptyStackText): The text that is displayed in the watch
|
||||
# expressions list to add a new item.
|
||||
addWatchExpressionText=Add watch expression
|
||||
|
@ -52,6 +52,8 @@
|
||||
locale/browser/devtools/connection-screen.dtd (%chrome/browser/devtools/connection-screen.dtd)
|
||||
locale/browser/devtools/connection-screen.properties (%chrome/browser/devtools/connection-screen.properties)
|
||||
locale/browser/devtools/font-inspector.dtd (%chrome/browser/devtools/font-inspector.dtd)
|
||||
locale/browser/devtools/app-manager.dtd (%chrome/browser/devtools/app-manager.dtd)
|
||||
locale/browser/devtools/app-manager.properties (%chrome/browser/devtools/app-manager.properties)
|
||||
locale/browser/newTab.dtd (%chrome/browser/newTab.dtd)
|
||||
locale/browser/newTab.properties (%chrome/browser/newTab.properties)
|
||||
locale/browser/openLocation.dtd (%chrome/browser/openLocation.dtd)
|
||||
|
@ -226,6 +226,17 @@ browser.jar:
|
||||
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
|
||||
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
|
||||
skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
|
||||
skin/classic/browser/devtools/app-manager/connection-footer.css (../shared/devtools/app-manager/connection-footer.css)
|
||||
skin/classic/browser/devtools/app-manager/index.css (../shared/devtools/app-manager/index.css)
|
||||
skin/classic/browser/devtools/app-manager/device.css (../shared/devtools/app-manager/device.css)
|
||||
skin/classic/browser/devtools/app-manager/projects.css (../shared/devtools/app-manager/projects.css)
|
||||
skin/classic/browser/devtools/app-manager/warning.svg (../shared/devtools/app-manager/images/warning.svg)
|
||||
skin/classic/browser/devtools/app-manager/error.svg (../shared/devtools/app-manager/images/error.svg)
|
||||
skin/classic/browser/devtools/app-manager/plus.svg (../shared/devtools/app-manager/images/plus.svg)
|
||||
skin/classic/browser/devtools/app-manager/remove.svg (../shared/devtools/app-manager/images/remove.svg)
|
||||
skin/classic/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
|
||||
skin/classic/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
|
||||
skin/classic/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-16-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
@ -316,6 +316,17 @@ browser.jar:
|
||||
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
|
||||
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
|
||||
skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
|
||||
skin/classic/browser/devtools/app-manager/connection-footer.css (../shared/devtools/app-manager/connection-footer.css)
|
||||
skin/classic/browser/devtools/app-manager/index.css (../shared/devtools/app-manager/index.css)
|
||||
skin/classic/browser/devtools/app-manager/device.css (../shared/devtools/app-manager/device.css)
|
||||
skin/classic/browser/devtools/app-manager/projects.css (../shared/devtools/app-manager/projects.css)
|
||||
skin/classic/browser/devtools/app-manager/warning.svg (../shared/devtools/app-manager/images/warning.svg)
|
||||
skin/classic/browser/devtools/app-manager/error.svg (../shared/devtools/app-manager/images/error.svg)
|
||||
skin/classic/browser/devtools/app-manager/plus.svg (../shared/devtools/app-manager/images/plus.svg)
|
||||
skin/classic/browser/devtools/app-manager/remove.svg (../shared/devtools/app-manager/images/remove.svg)
|
||||
skin/classic/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
|
||||
skin/classic/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
|
||||
skin/classic/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
200
browser/themes/shared/devtools/app-manager/connection-footer.css
Normal file
@ -0,0 +1,200 @@
|
||||
/* 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/. */
|
||||
|
||||
/************** LAYOUT **************/
|
||||
|
||||
#connection-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#banners-and-logs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#logs {
|
||||
display: flex;
|
||||
width: 40%;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: none;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
#connection-footer[status="connected"] #banner-connected,
|
||||
#connection-footer[status="connecting"] #banner-connecting,
|
||||
#connection-footer[status="disconnected"] #banner-disconnected,
|
||||
#connection-footer[status="disconnecting"] #banner-disconnecting {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
body.edit-connection .banner {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.edit-connection #banner-editing {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
#banner-logs {
|
||||
width: 40%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#logs > pre {
|
||||
overflow: auto;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
#status.banner-box {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.banner-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#banner-connected > .banner-box {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#start-simulator-box {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#start-simulator-box[simulators-count="0"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/************** PIXELS **************/
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #333;
|
||||
background-color: white;
|
||||
font-family: Lucida Grande, Helvetica, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(to bottom, #49535C, #394148);
|
||||
box-shadow: 0px 1px 1px #3C444D, inset 0 1px 0px rgba(255,255,255,0.1);
|
||||
color: #9FA6AD;
|
||||
text-shadow: 0px 1px 1px rgba(0,0,0,0.6);
|
||||
border: 1px solid #111;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
button.left {
|
||||
margin-right: 0px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
button.right {
|
||||
margin-left: -6px;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
button.action-primary {
|
||||
background: linear-gradient(to bottom, #276DA3, #1E5580);
|
||||
color: #EEE;
|
||||
}
|
||||
|
||||
button.action-cancel {
|
||||
background: linear-gradient(to bottom, #B32B02, #942300);
|
||||
color: #EEE;
|
||||
}
|
||||
|
||||
#banners-and-logs {
|
||||
border-top: #111 solid;
|
||||
border-width: 1px 0;
|
||||
background: linear-gradient(to bottom, #323A42, #29313A);
|
||||
color: #A8BABF;
|
||||
box-shadow: inset 0 0 1px #424A51;
|
||||
}
|
||||
|
||||
#status {
|
||||
background: linear-gradient(to bottom, #454F59, #404952);
|
||||
box-shadow: inset 0 0 1px #606D78, inset 0 1px 0 #5E6973;
|
||||
}
|
||||
|
||||
#logs > pre {
|
||||
border: 1px solid #111;
|
||||
box-shadow: 0px 1px 1px #49525A, inset 0 0 5px rgba(0,0,0,0.3);
|
||||
font-size: 10px;
|
||||
background: #22272D;
|
||||
padding: 5px;
|
||||
height: 100%;
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#logs > pre span{
|
||||
text-shadow: 0 1px 2px #000;
|
||||
color: #3195FB;
|
||||
position: fixed;
|
||||
right: calc(30% - 15px);
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
#logs > pre b {
|
||||
font-size: 10px;
|
||||
color: #70C4FF;
|
||||
}
|
||||
|
||||
.banner-box {
|
||||
box-shadow: inset 0 0 1px #667480, inset 0 1px 0 #5E6973;
|
||||
border-right: 1px solid #111;
|
||||
background-position: center right;
|
||||
background-size: 1px 100%;
|
||||
background-repeat: no-repeat;
|
||||
padding: 10px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.connected-status {
|
||||
color: #B3BFC9;
|
||||
text-shadow: 0px 1px 2px rgba(0,0,0,0.9);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.connected-status {
|
||||
font-size: 150%;
|
||||
top: 10%;
|
||||
padding-right: 3px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.connected-indicator {
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.3), inset 0 0px 1px rgba(255,255,255,0.3);
|
||||
height: 100%;
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
|
||||
#banner-connected .connected-indicator,
|
||||
#banner-connecting .connected-indicator {
|
||||
background: linear-gradient(to bottom, #69B8FF, #339FFF );
|
||||
}
|
||||
|
||||
#banner-disconnected .connected-indicator,
|
||||
#banner-editing .connected-indicator,
|
||||
#banner-disconnecting .connected-indicator {
|
||||
background: linear-gradient(to bottom, #375A87, #1C4375 );
|
||||
}
|
378
browser/themes/shared/devtools/app-manager/device.css
Normal file
@ -0,0 +1,378 @@
|
||||
/* 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/. */
|
||||
|
||||
/***************** GENERAL *****************/
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 0.9rem;
|
||||
color: #333;
|
||||
background-color: rgb(225, 225, 225);
|
||||
font-family: Lucida Grande, Helvetica, Helvetica Neue, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#detail {
|
||||
background-image: url('noise.png');
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#meta {
|
||||
background-size: 100%;
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
#connection-footer {
|
||||
border-width: 0;
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************** APP BUTTONS *****************/
|
||||
|
||||
|
||||
|
||||
.app-buttons {
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
color: #BBB;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
border: 1px solid #CCC;
|
||||
padding: 5px 15px;
|
||||
cursor: pointer;
|
||||
background: rgba(255,255,255,0.4);
|
||||
text-transform: uppercase;
|
||||
border-radius: 3px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.app-buttons > button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app[running="false"] > .app-buttons > .button-start,
|
||||
.app[running="true"] > .app-buttons > .button-stop,
|
||||
.app[running="true"] > .app-buttons > .button-debug {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.button-debug {
|
||||
color: #3498DB;
|
||||
}
|
||||
|
||||
.button-debug:hover {
|
||||
background-color: #3498DB;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.button-start {
|
||||
color: #18BC9C
|
||||
}
|
||||
|
||||
.button-start:hover {
|
||||
background-color: #18BC9C;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.button-stop {
|
||||
color: #E74C3C;
|
||||
}
|
||||
|
||||
.button-stop:hover {
|
||||
background-color: #E74C3C;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************** PERMISSIONS *****************/
|
||||
|
||||
|
||||
|
||||
|
||||
.permission-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.permission-table-body {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.permission-table-header,
|
||||
.permission-table-footer {
|
||||
display: flex;
|
||||
background: #FFF;
|
||||
border-top: 1px solid #CCC;
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.permission-table-header > div,
|
||||
.permission-table-footer > div {
|
||||
z-index: 2;
|
||||
flex-grow: 1;
|
||||
background: linear-gradient(to bottom, #49535C, #394148);
|
||||
box-shadow: 0px 1px 3px rgba(12, 20, 30, 0.5), inset 0 1px 0px rgba(255,255,255,0.1);
|
||||
color: #9FA6AD;
|
||||
text-shadow: 0px 1px 1px rgba(0,0,0,0.6);
|
||||
border: 0;
|
||||
margin: auto 0;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.permission-table-header > div {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
|
||||
.permission-table-header > div:first-child {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
flex-basis: 30%;
|
||||
}
|
||||
|
||||
.permission-table-header {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #CCC;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.permission-table-footer {
|
||||
box-shadow: 0 -1px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.permission {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.permission:nth-child(odd) {
|
||||
background: #E4E4E4;
|
||||
}
|
||||
|
||||
.permission:hover {
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
.permission > div {
|
||||
flex-grow: 1;
|
||||
flex-basis: 20%;
|
||||
text-align: center;
|
||||
padding: 3px;
|
||||
border-right: 1px solid #CCC;
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.permission > div:first-child {
|
||||
text-align: left;
|
||||
padding: 3px 10px;
|
||||
flex-basis: 30%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.permission > div[permission="1"]:before, .allow-label:after {
|
||||
color: #98CF39;
|
||||
content: ' \2713';
|
||||
}
|
||||
|
||||
.permission > div[permission="2"]:before, .deny-label:after {
|
||||
color: #CC4908;
|
||||
content: ' \2715';
|
||||
}
|
||||
|
||||
.permission > div[permission="3"]:before, .prompt-label:after {
|
||||
color: #009EED;
|
||||
content: ' !';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***************** SIDEBAR *****************/
|
||||
|
||||
|
||||
|
||||
|
||||
#sidebar {
|
||||
background: #EEE;
|
||||
position: relative;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 350px;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
background-color: #F6F6F6;
|
||||
box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1);
|
||||
color: #666;
|
||||
line-height: 120%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 15px 10px;
|
||||
display: block;
|
||||
text-align: left;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.sidebar-item > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-item:hover {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.sidebar-item.selected {
|
||||
background: linear-gradient(to bottom, #276DA3, #155282);
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************** HEADER *****************/
|
||||
|
||||
header {
|
||||
padding-top: 140px;
|
||||
background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.7));
|
||||
color: #FFF;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.8);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************** APPS *****************/
|
||||
|
||||
|
||||
|
||||
|
||||
.apps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
flex-grow: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.app {
|
||||
padding: 10px 20px;
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.app:hover {
|
||||
background-color: #EFEFEF;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************** NOT CONNECTED *****************/
|
||||
|
||||
|
||||
|
||||
body:not(.notconnected) > #notConnectedMessage,
|
||||
body.notconnected > #content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#notConnectedMessage {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
#notConnectedMessage > span {
|
||||
padding: 20px;
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#notConnectedMessage > span:before {
|
||||
content: '';
|
||||
background: url('error.svg') no-repeat;
|
||||
background-size: 18px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************** TABS *****************/
|
||||
|
||||
#tabs {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.tabpanel:not(.selected) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tabs-headers {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
14
browser/themes/shared/devtools/app-manager/images/error.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64">
|
||||
<path fill="#E25026" d="M32,4.894c-15.74,0-28.5,12.76-28.5,28.5s12.76,28.5,28.5,28.5s28.5-12.76,28.5-28.5S47.74,4.894,32,4.894
|
||||
z M46.903,48.674c-1.817,1.817-4.691,1.76-6.449,0.002l-8.327-8.327l-8.151,8.151c-1.877,1.877-4.87,1.814-6.685,0
|
||||
c-1.877-1.877-1.879-4.811-0.002-6.687l8.151-8.151l-8.327-8.327c-1.76-1.76-1.817-4.634,0-6.451c1.76-1.76,4.691-1.76,6.451,0
|
||||
l8.327,8.327l8.151-8.151c1.877-1.877,4.811-1.874,6.687,0.002c1.814,1.814,1.877,4.808,0,6.685l-8.151,8.151l8.327,8.327
|
||||
C48.662,43.982,48.662,46.914,46.903,48.674z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1021 B |
@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="160px" height="160px" viewBox="0 0 160 160">
|
||||
<filter
|
||||
id="AI_GaussianBlur_4">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2"
|
||||
id="feGaussianBlur4" />
|
||||
</filter>
|
||||
<g
|
||||
id="g54">
|
||||
<linearGradient
|
||||
id="SVGID_5_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="40"
|
||||
y1="146.0156"
|
||||
x2="40"
|
||||
y2="93.9844">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#58595B"
|
||||
id="stop57" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#808285"
|
||||
id="stop59" />
|
||||
</linearGradient>
|
||||
<path
|
||||
d="M55.609,140.812c0,2.846-2.357,5.203-5.203,5.203H29.594c-2.846,0-5.203-2.357-5.203-5.203V99.188 c0-2.846,2.357-5.203,5.203-5.203h20.812c2.846,0,5.203,2.357,5.203,5.203V140.812z M51.707,105.691 c0-0.691-0.61-1.301-1.301-1.301H29.594c-0.691,0-1.301,0.609-1.301,1.301v28.617c0,0.691,0.61,1.301,1.301,1.301h20.812 c0.691,0,1.301-0.609,1.301-1.301V105.691z M43.252,99.188h-6.504c-0.366,0-0.65,0.284-0.65,0.65s0.285,0.65,0.65,0.65h6.504 c0.366,0,0.65-0.284,0.65-0.65S43.618,99.188,43.252,99.188z M40,137.561c-1.789,0-3.252,1.463-3.252,3.252 s1.463,3.252,3.252,3.252s3.252-1.463,3.252-3.252S41.789,137.561,40,137.561z"
|
||||
id="path61"
|
||||
fill="url(#SVGID_5_)" />
|
||||
</g>
|
||||
<g
|
||||
id="g16">
|
||||
<linearGradient
|
||||
id="SVGID_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="40"
|
||||
y1="55.868"
|
||||
x2="40"
|
||||
y2="15.5872">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#58595B"
|
||||
id="stop19" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#808285"
|
||||
id="stop21" />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#SVGID_1_)"
|
||||
d="M40.001,15.587c-2.742,0.001-8.104,9.047-9.931,18.021l-6.165,6.164v16.095l4.297,0.001l5.991-5.991 c0.612,0.641,1.287,1.181,2.011,1.609h7.581c0.728-0.426,1.408-0.965,2.021-1.609l5.991,5.991l4.298-0.001V39.773l-6.165-6.164 C48.104,24.635,42.741,15.587,40.001,15.587z M40.006,25.023c0.955,0,2.636,2.682,3.683,5.891c-1.134-0.21-2.343-0.328-3.602-0.328 c-1.324,0-2.592,0.132-3.775,0.364C37.358,27.726,39.047,25.023,40.006,25.023z"
|
||||
id="path23" />
|
||||
<linearGradient
|
||||
id="SVGID_2_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="40"
|
||||
y1="64.4128"
|
||||
x2="40"
|
||||
y2="53.6309">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#58595B"
|
||||
id="stop26" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#808285"
|
||||
id="stop28" />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#SVGID_2_)"
|
||||
d="M41.927,55.598c0,1.891-1.467,3.424-1.957,3.424c-0.489,0-1.957-1.534-1.957-3.424 c0-0.732,0.133-1.409,0.356-1.967h-2.266c-0.266,0.98-0.415,2.077-0.416,3.235c0.001,4.168,3.234,7.547,4.313,7.547 s4.313-3.378,4.313-7.546c0-1.159-0.15-2.254-0.416-3.236l-2.326,0.001C41.794,54.188,41.927,54.865,41.927,55.598z"
|
||||
id="path30" />
|
||||
</g>
|
||||
|
||||
<g
|
||||
id="g38">
|
||||
<linearGradient
|
||||
id="SVGID_3_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="120"
|
||||
y1="55.868"
|
||||
x2="120"
|
||||
y2="15.5872">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#1C75BC"
|
||||
id="stop41" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#27AAE1"
|
||||
id="stop43" />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#SVGID_3_)"
|
||||
d="M120.001,15.587c-2.742,0.001-8.104,9.047-9.931,18.021l-6.165,6.164v16.095l4.297,0.001 l5.991-5.991c0.612,0.641,1.287,1.181,2.011,1.609h7.581c0.728-0.426,1.408-0.965,2.021-1.609l5.991,5.991l4.298-0.001V39.773 l-6.165-6.164C128.104,24.635,122.741,15.587,120.001,15.587z M120.006,25.023c0.955,0,2.636,2.682,3.683,5.891 c-1.134-0.21-2.343-0.328-3.602-0.328c-1.324,0-2.592,0.132-3.775,0.364C117.358,27.726,119.047,25.023,120.006,25.023z"
|
||||
id="path45" />
|
||||
<linearGradient
|
||||
id="SVGID_4_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="120"
|
||||
y1="64.4128"
|
||||
x2="120"
|
||||
y2="53.6309">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#1C75BC"
|
||||
id="stop48" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#27AAE1"
|
||||
id="stop50" />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#SVGID_4_)"
|
||||
d="M121.927,55.598c0,1.891-1.467,3.424-1.957,3.424c-0.489,0-1.957-1.534-1.957-3.424 c0-0.732,0.133-1.409,0.356-1.967h-2.266c-0.266,0.98-0.415,2.077-0.416,3.235c0.001,4.168,3.234,7.547,4.313,7.547 s4.313-3.378,4.313-7.546c0-1.159-0.15-2.254-0.416-3.236l-2.326,0.001C121.794,54.188,121.927,54.865,121.927,55.598z"
|
||||
id="path52" />
|
||||
</g>
|
||||
|
||||
|
||||
<g
|
||||
id="g67">
|
||||
<linearGradient
|
||||
id="SVGID_6_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="120"
|
||||
y1="146.0156"
|
||||
x2="120"
|
||||
y2="93.9844">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#1C75BC"
|
||||
id="stop70" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#27AAE1"
|
||||
id="stop72" />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#SVGID_6_)"
|
||||
d="M135.609,140.812c0,2.846-2.357,5.203-5.203,5.203h-20.812c-2.846,0-5.203-2.357-5.203-5.203V99.188 c0-2.846,2.357-5.203,5.203-5.203h20.812c2.846,0,5.203,2.357,5.203,5.203V140.812z M131.707,105.691 c0-0.691-0.61-1.301-1.301-1.301h-20.812c-0.691,0-1.301,0.609-1.301,1.301v28.617c0,0.691,0.61,1.301,1.301,1.301h20.812 c0.691,0,1.301-0.609,1.301-1.301V105.691z M123.252,99.188h-6.504c-0.366,0-0.65,0.284-0.65,0.65s0.285,0.65,0.65,0.65h6.504 c0.366,0,0.65-0.284,0.65-0.65S123.618,99.188,123.252,99.188z M120,137.561c-1.789,0-3.252,1.463-3.252,3.252 s1.463,3.252,3.252,3.252s3.252-1.463,3.252-3.252S121.789,137.561,120,137.561z"
|
||||
id="path74" />
|
||||
</g>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 5.7 KiB |
BIN
browser/themes/shared/devtools/app-manager/images/noise.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
13
browser/themes/shared/devtools/app-manager/images/plus.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64">
|
||||
<path fill="#ABABAB" d="M32.336,3.894c-15.74,0-28.5,12.76-28.5,28.5s12.76,28.5,28.5,28.5s28.5-12.76,28.5-28.5
|
||||
S48.076,3.894,32.336,3.894z M44.86,36.966h-7.823v7.62c0,2.582-2.12,4.702-4.702,4.702c-2.584,0-4.704-2.12-4.704-4.702v-7.62
|
||||
h-7.817c-2.52,0-4.572-2.056-4.572-4.572s2.053-4.572,4.572-4.572h7.817v-7.62c0-2.582,2.12-4.702,4.704-4.702
|
||||
c2.582,0,4.702,2.12,4.702,4.702v7.62h7.823c2.514,0,4.57,2.056,4.57,4.572S47.374,36.966,44.86,36.966z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 934 B |
10
browser/themes/shared/devtools/app-manager/images/remove.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64">
|
||||
<path d="m 12.183457,12.241457 c -11.129861,11.12986 -11.129861,29.175226 0,40.305086 11.12986,11.129861 29.175226,11.129861 40.305086,0 11.129861,-11.12986 11.129861,-29.175226 0,-40.305086 -11.12986,-11.129861 -29.175226,-11.129861 -40.305086,0 z m 32.241241,14.52963 -5.531697,5.531696 5.388154,5.388154 c 1.82575,1.82575 1.82575,4.823882 0,6.649632 -1.827164,1.827164 -4.825297,1.827164 -6.651047,0.0014 l -5.388153,-5.388153 -5.527454,5.527453 c -1.781909,1.781909 -4.686704,1.779081 -6.465784,0 -1.779081,-1.77908 -1.781202,-4.684582 0,-6.465784 l 5.527453,-5.527454 -5.388153,-5.388153 c -1.82575,-1.82575 -1.82575,-4.823883 0.0014,-6.651047 1.82575,-1.82575 4.823882,-1.82575 6.649632,0 l 5.388154,5.388154 5.531696,-5.531697 c 1.777667,-1.777666 4.68529,-1.777666 6.46437,0.0014 1.779081,1.77908 1.779081,4.686703 0.0014,6.46437 z"
|
||||
style="fill:#d8abab" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
31
browser/themes/shared/devtools/app-manager/images/rocket.svg
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<g opacity="0.1">
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M12,2.3c-1.127,0-3.333,3.721-4.084,7.411l-2.535,2.535v6.619l1.767,0l2.464-2.464
|
||||
c0.252,0.264,0.529,0.486,0.827,0.662h3.118c0.299-0.175,0.579-0.397,0.831-0.662l2.464,2.464l1.767,0v-6.619l-2.535-2.535
|
||||
C15.333,6.021,13.127,2.3,12,2.3z M12.003,6.181c0.393,0,1.084,1.103,1.515,2.423c-0.466-0.087-0.963-0.135-1.481-0.135
|
||||
c-0.545,0-1.066,0.054-1.553,0.15C10.914,7.292,11.608,6.181,12.003,6.181z"/>
|
||||
<path fill="#FFFFFF" d="M12.792,18.755c0,0.778-0.603,1.408-0.805,1.408c-0.201,0-0.805-0.631-0.805-1.408
|
||||
c0-0.301,0.055-0.579,0.147-0.809h-0.932c-0.109,0.403-0.171,0.854-0.171,1.33c0,1.714,1.33,3.104,1.774,3.104
|
||||
s1.774-1.389,1.774-3.103c0-0.477-0.062-0.927-0.171-1.331l-0.957,0C12.738,18.175,12.792,18.453,12.792,18.755z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#414042" d="M12,2c-1.127,0-3.333,3.721-4.084,7.411l-2.535,2.535v6.619l1.767,0l2.464-2.464
|
||||
c0.252,0.264,0.529,0.486,0.827,0.662h3.118c0.299-0.175,0.579-0.397,0.831-0.662l2.464,2.464l1.767,0v-6.619l-2.535-2.535
|
||||
C15.333,5.721,13.127,2,12,2z M12.003,5.881c0.393,0,1.084,1.103,1.515,2.423c-0.466-0.087-0.963-0.135-1.481-0.135
|
||||
c-0.545,0-1.066,0.054-1.553,0.15C10.914,6.992,11.608,5.881,12.003,5.881z"/>
|
||||
<path fill="#414042" d="M12.792,18.455c0,0.778-0.603,1.408-0.805,1.408c-0.201,0-0.805-0.631-0.805-1.408
|
||||
c0-0.301,0.055-0.579,0.147-0.809h-0.932c-0.109,0.403-0.171,0.854-0.171,1.33c0,1.714,1.33,3.104,1.774,3.104
|
||||
s1.774-1.389,1.774-3.103c0-0.477-0.062-0.927-0.171-1.331l-0.957,0C12.738,17.875,12.792,18.153,12.792,18.455z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64">
|
||||
<path fill="#ECB51F" d="M61.689,51.121L36.437,7.384c-2.441-4.227-6.434-4.227-8.875,0L2.311,51.121
|
||||
c-2.441,4.227-0.444,7.686,4.437,7.686h50.504C62.133,58.807,64.13,55.349,61.689,51.121z M35.968,47.68
|
||||
c0,2.191-1.688,3.877-3.968,3.877s-3.968-1.686-3.968-3.877v-0.093c0-2.187,1.688-3.873,3.968-3.873s3.968,1.686,3.968,3.873V47.68z
|
||||
M36.059,21.548l-1.961,17.146c-0.137,1.233-0.958,2.009-2.098,2.009s-1.961-0.776-2.098-2.009l-1.961-17.146
|
||||
c-0.137-1.322,0.592-2.325,1.825-2.325h4.469C35.466,19.223,36.196,20.226,36.059,21.548z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
80
browser/themes/shared/devtools/app-manager/index.css
Normal file
@ -0,0 +1,80 @@
|
||||
/* 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/. */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
font-family: Lucida Grande, Helvetica, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
#tabs {
|
||||
box-shadow: inset -1px 0 5px rgba(0,0,0,0.9);
|
||||
background: #22272D;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
-moz-appearance: none;
|
||||
border: none;
|
||||
border-top: 1px solid #323234;
|
||||
border-bottom: 1px solid #121214;
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
-moz-box-align: end;
|
||||
font-size: 10px;
|
||||
text-shadow: 0 1px 2px #111;
|
||||
color: #9FA6AD;
|
||||
}
|
||||
|
||||
.button[selected] {
|
||||
color: #26A6DE;
|
||||
border-color: #191B1E;
|
||||
box-shadow: inset -5px 0 5px #111;
|
||||
}
|
||||
|
||||
.button::-moz-focus-inner {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.panel:not([selected="true"]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.projects-button {
|
||||
background: url('chrome://browser/skin/devtools/app-manager/index-icons.svg') no-repeat;
|
||||
background-position: left -5px;
|
||||
}
|
||||
|
||||
.projects-button[selected] {
|
||||
background-position: right -5px;
|
||||
}
|
||||
|
||||
.device-button {
|
||||
background-image: url('chrome://browser/skin/devtools/app-manager/index-icons.svg'), radial-gradient(at center left, red 40%, transparent);
|
||||
background-position: left -85px, top left;
|
||||
background-repeat: no-repeat, no-repeat;
|
||||
background-size: 160px 160px, 2px 80px;
|
||||
}
|
||||
|
||||
.connected .device-button {
|
||||
background-image: url('chrome://browser/skin/devtools/app-manager/index-icons.svg'), radial-gradient(at center left, #B1FC0D 40%, transparent);
|
||||
}
|
||||
|
||||
.device-button[selected] {
|
||||
background-position: right -85px, top left;
|
||||
}
|
||||
|
||||
#connection-footer {
|
||||
border-width: 0;
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
}
|
428
browser/themes/shared/devtools/app-manager/projects.css
Normal file
@ -0,0 +1,428 @@
|
||||
/* 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/. */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
color: #333;
|
||||
background-color: white;
|
||||
font-family: Lucida Grande, Helvetica, Helvetica Neue, sans-serif;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body:not(.connected) button.device-action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
|
||||
/********* SIDEBAR ***********/
|
||||
|
||||
|
||||
|
||||
#sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 350px;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
background: #EEE;
|
||||
position: relative;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
#project-list {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#project-list:not([projects-count="0"]) > #no-project {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#no-project {
|
||||
padding: 100px 20px 0;
|
||||
font-weight: bold;
|
||||
color: #BBB;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/********* PROJECT MENU ***********/
|
||||
|
||||
|
||||
.project-item {
|
||||
padding: 10px 0;
|
||||
background-color: #F6F6F6;
|
||||
box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1);
|
||||
color: #666;
|
||||
line-height: 120%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.project-item:hover {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.project-item > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.project-item.selected {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.button-remove {
|
||||
background-image: url('remove.svg');
|
||||
background-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.project-item:hover .button-remove {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.project-item-status {
|
||||
width: 10px;
|
||||
margin: -10px 0;
|
||||
border-right: 1px solid rgba(0,0,0,0.1);
|
||||
box-shadow: inset 0 0 1px 1px rgba(255,255,255,0.2), inset 0 -1px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.project-item-status[status="warning"] {
|
||||
background: linear-gradient(to bottom, #F5BD56, #E3A93D);
|
||||
}
|
||||
|
||||
.project-item-status[status="error"] {
|
||||
background: linear-gradient(to bottom, #E34F22, #B83C16);
|
||||
}
|
||||
|
||||
.project-item-status[status="valid"] {
|
||||
background: linear-gradient(to bottom, #90D11F, #77AD18);
|
||||
}
|
||||
|
||||
.project-item-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.project-item-meta {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1 !important;
|
||||
}
|
||||
|
||||
.project-item-type {
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
float: right;
|
||||
padding-right: 10px;
|
||||
color: #AAA;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.project-item-description {
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
/********* ADD PROJECT ***********/
|
||||
|
||||
#new-packaged-project {
|
||||
box-shadow: 0 -1px 10px rgba(0,0,0,0.3);
|
||||
background-position: calc(100% - 10px) 10px;
|
||||
}
|
||||
|
||||
#new-packaged-project,
|
||||
#new-hosted-project {
|
||||
background-color: #EEE;
|
||||
border: none;
|
||||
border-top: 1px solid #DDD;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#new-packaged-project:hover,
|
||||
#new-hosted-project:hover {
|
||||
background-color: #DDD;
|
||||
}
|
||||
|
||||
#new-hosted-project-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#new-packaged-project,
|
||||
#new-hosted-project-click {
|
||||
background-image: url('plus.svg');
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#new-hosted-project-click {
|
||||
background-position: top right;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#url-input {
|
||||
flex-grow: 1;
|
||||
width: 90%;
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #DDD;
|
||||
padding: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
|
||||
/********* LENSE ***********/
|
||||
|
||||
|
||||
#lense {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
background-color: rgb(225, 225, 225);
|
||||
background-image: url('rocket.svg'), url('noise.png');
|
||||
background-repeat: no-repeat, repeat;
|
||||
background-size: 35%, auto;
|
||||
background-position: center center, top left;
|
||||
}
|
||||
|
||||
#lense > div {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
/********* PROJECT ***********/
|
||||
|
||||
|
||||
.project-details {
|
||||
background-color: rgb(225, 225, 225);
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
.project-status {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.project-title {
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.project-title > h1 {
|
||||
flex-grow: 1;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.project-location {
|
||||
color: gray;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.project-location:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.project-header {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #CCC;
|
||||
margin: 10px 20px 10px 20px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.project-icon {
|
||||
flex-shrink: 0;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.project-location {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.project-description {
|
||||
font-style: italic;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.project-status > p {
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 2px;
|
||||
margin-top: 6px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.project-validation {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
[status="valid"] > .project-validation {
|
||||
background-color: #82BD1B;
|
||||
}
|
||||
|
||||
[status="warning"] > .project-validation {
|
||||
background-color: #ECB51F;
|
||||
}
|
||||
|
||||
[status="error"] > .project-validation {
|
||||
background-color: #C24119;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/********* PROJECT BUTTONS ***********/
|
||||
|
||||
|
||||
|
||||
.project-buttons {
|
||||
display: flex;
|
||||
margin-left: 20px;
|
||||
color: #BBB;
|
||||
}
|
||||
|
||||
.project-buttons > button {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
border: 1px solid #CCC;
|
||||
border-left-width: 0;
|
||||
padding: 5px 15px;
|
||||
cursor: pointer;
|
||||
background: rgba(255,255,255,0.4);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.project-buttons > button:first-child {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.project-button-debug {
|
||||
color: #3498DB;
|
||||
}
|
||||
|
||||
.project-button-debug:hover {
|
||||
background-color: #3498DB;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.project-button-install,
|
||||
.project-button-start {
|
||||
color: #18BC9C
|
||||
}
|
||||
|
||||
.project-button-install:hover,
|
||||
.project-button-start:hover {
|
||||
background-color: #18BC9C;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.project-button-stop {
|
||||
color: #E74C3C;
|
||||
}
|
||||
|
||||
.project-button-stop:hover {
|
||||
background-color: #E74C3C;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.project-button-refresh {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.project-button-refresh:hover {
|
||||
background-color: #777;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/********* ERRORS AND WARNINGS ***********/
|
||||
|
||||
.project-warnings,
|
||||
.project-errors,
|
||||
.project-item-warnings,
|
||||
.project-item-errors {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[status="warning"] > .project-item-warnings,
|
||||
[status="error"] > .project-item-errors,
|
||||
[status="warning"] > .project-warnings,
|
||||
[status="error"] > .project-errors {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.project-warnings {
|
||||
margin: 20px 20px 0;
|
||||
padding: 10px 10px;
|
||||
border-left: 3px solid #ECB51E;
|
||||
background-color: rgba(236, 181, 20, 0.1);
|
||||
}
|
||||
|
||||
.project-errors {
|
||||
margin: 20px;
|
||||
padding: 10px 10px;
|
||||
border-left: 3px solid #E34F22;
|
||||
background-color: rgba(227,79,34,0.1);
|
||||
}
|
||||
|
||||
.project-item-warnings {
|
||||
background-image: url('warning.svg');
|
||||
}
|
||||
|
||||
.project-item-errors {
|
||||
background-image: url('error.svg');
|
||||
color: rgb(227, 79, 34);
|
||||
}
|
||||
|
||||
.project-item-warnings,
|
||||
.project-item-errors {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 12px;
|
||||
background-position: left center;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.project-item-warnings > span,
|
||||
.project-item-errors > span {
|
||||
font-size: 11px;
|
||||
padding-left: 16px;
|
||||
font-weight: bold;
|
||||
}
|
@ -253,6 +253,17 @@ browser.jar:
|
||||
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
|
||||
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
|
||||
skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
|
||||
skin/classic/browser/devtools/app-manager/connection-footer.css (../shared/devtools/app-manager/connection-footer.css)
|
||||
skin/classic/browser/devtools/app-manager/index.css (../shared/devtools/app-manager/index.css)
|
||||
skin/classic/browser/devtools/app-manager/device.css (../shared/devtools/app-manager/device.css)
|
||||
skin/classic/browser/devtools/app-manager/projects.css (../shared/devtools/app-manager/projects.css)
|
||||
skin/classic/browser/devtools/app-manager/warning.svg (../shared/devtools/app-manager/images/warning.svg)
|
||||
skin/classic/browser/devtools/app-manager/error.svg (../shared/devtools/app-manager/images/error.svg)
|
||||
skin/classic/browser/devtools/app-manager/plus.svg (../shared/devtools/app-manager/images/plus.svg)
|
||||
skin/classic/browser/devtools/app-manager/remove.svg (../shared/devtools/app-manager/images/remove.svg)
|
||||
skin/classic/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
|
||||
skin/classic/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
|
||||
skin/classic/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
@ -516,6 +527,17 @@ browser.jar:
|
||||
skin/classic/aero/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
|
||||
skin/classic/aero/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
|
||||
skin/classic/aero/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
|
||||
skin/classic/aero/browser/devtools/app-manager/connection-footer.css (../shared/devtools/app-manager/connection-footer.css)
|
||||
skin/classic/aero/browser/devtools/app-manager/index.css (../shared/devtools/app-manager/index.css)
|
||||
skin/classic/aero/browser/devtools/app-manager/device.css (../shared/devtools/app-manager/device.css)
|
||||
skin/classic/aero/browser/devtools/app-manager/projects.css (../shared/devtools/app-manager/projects.css)
|
||||
skin/classic/aero/browser/devtools/app-manager/warning.svg (../shared/devtools/app-manager/images/warning.svg)
|
||||
skin/classic/aero/browser/devtools/app-manager/error.svg (../shared/devtools/app-manager/images/error.svg)
|
||||
skin/classic/aero/browser/devtools/app-manager/plus.svg (../shared/devtools/app-manager/images/plus.svg)
|
||||
skin/classic/aero/browser/devtools/app-manager/remove.svg (../shared/devtools/app-manager/images/remove.svg)
|
||||
skin/classic/aero/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
|
||||
skin/classic/aero/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
|
||||
skin/classic/aero/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/aero/browser/sync-throbber.png
|
||||
skin/classic/aero/browser/sync-16.png
|
||||
|
@ -1,7 +1,10 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
let gAppId = "actor-test";
|
||||
const APP_ORIGIN = "app://" + gAppId;
|
||||
|
||||
add_test(function testLaunchInexistantApp() {
|
||||
let request = {type: "launch", manifestURL: "http://foo.com"};
|
||||
@ -34,7 +37,7 @@ add_test(function testInstallPackaged() {
|
||||
|
||||
// The install request is asynchronous and send back an event to tell
|
||||
// if the installation succeed or failed
|
||||
gClient.addListener("webappsEvent", function (aState, aType, aPacket) {
|
||||
gClient.addOneTimeListener("webappsEvent", function listener(aState, aType, aPacket) {
|
||||
do_check_eq(aType.appId, gAppId);
|
||||
if ("error" in aType) {
|
||||
do_print("Error: " + aType.error);
|
||||
@ -104,11 +107,12 @@ add_test(function testCloseApp() {
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
let json = JSON.parse(data);
|
||||
do_check_eq(json.manifestURL, manifestURL);
|
||||
run_next_test();
|
||||
|
||||
}, "webapps-close", false);
|
||||
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
do_check_false("error" in aResponse);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
@ -172,6 +176,129 @@ add_test(function testUninstall() {
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function testFileUploadInstall() {
|
||||
function createUpload() {
|
||||
let request = {
|
||||
type: "uploadPackage"
|
||||
};
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
getPackageContent(aResponse.actor);
|
||||
});
|
||||
}
|
||||
function getPackageContent(uploadActor) {
|
||||
let packageFile = do_get_file("data/app.zip");
|
||||
OS.File.read(packageFile.path)
|
||||
.then(function (bytes) {
|
||||
// To work around the fact that JSON.stringify translates the typed
|
||||
// array to object, we are encoding the typed array here into a string
|
||||
let content = String.fromCharCode.apply(null, bytes);
|
||||
uploadChunk(uploadActor, content);
|
||||
});
|
||||
}
|
||||
function uploadChunk(uploadActor, content) {
|
||||
let request = {
|
||||
to: uploadActor,
|
||||
type: "chunk",
|
||||
chunk: content
|
||||
};
|
||||
gClient.request(request, function (aResponse) {
|
||||
endsUpload(uploadActor);
|
||||
});
|
||||
}
|
||||
function endsUpload(uploadActor, content) {
|
||||
let request = {
|
||||
to: uploadActor,
|
||||
type: "done"
|
||||
};
|
||||
gClient.request(request, function (aResponse) {
|
||||
installApp(uploadActor);
|
||||
});
|
||||
}
|
||||
function installApp(uploadActor) {
|
||||
let request = {type: "install", appId: gAppId, upload: uploadActor};
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
do_check_eq(aResponse.appId, gAppId);
|
||||
});
|
||||
gClient.addOneTimeListener("webappsEvent", function listener(aState, aType, aPacket) {
|
||||
do_check_eq(aType.appId, gAppId);
|
||||
if ("error" in aType) {
|
||||
do_print("Error: " + aType.error);
|
||||
}
|
||||
if ("message" in aType) {
|
||||
do_print("Error message: " + aType.message);
|
||||
}
|
||||
do_check_eq("error" in aType, false);
|
||||
|
||||
removeUpload(uploadActor);
|
||||
});
|
||||
}
|
||||
function removeUpload(uploadActor, content) {
|
||||
let request = {
|
||||
to: uploadActor,
|
||||
type: "remove"
|
||||
};
|
||||
gClient.request(request, function (aResponse) {
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
createUpload();
|
||||
});
|
||||
|
||||
add_test(function testInstallHosted() {
|
||||
gAppId = "hosted-app";
|
||||
let request = {
|
||||
type: "install",
|
||||
appId: gAppId,
|
||||
manifest: {
|
||||
name: "My hosted app"
|
||||
},
|
||||
metadata: {
|
||||
origin: "http://foo.com",
|
||||
installOrigin: "http://metadata.foo.com",
|
||||
manifestURL: "http://foo.com/metadata/manifest.webapp"
|
||||
}
|
||||
};
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
do_check_eq(aResponse.appId, gAppId);
|
||||
});
|
||||
|
||||
// The install request is asynchronous and send back an event to tell
|
||||
// if the installation succeed or failed
|
||||
gClient.addOneTimeListener("webappsEvent", function listener(aState, aType, aPacket) {
|
||||
do_check_eq(aType.appId, gAppId);
|
||||
if ("error" in aType) {
|
||||
do_print("Error: " + aType.error);
|
||||
}
|
||||
if ("message" in aType) {
|
||||
do_print("Error message: " + aType.message);
|
||||
}
|
||||
do_check_eq("error" in aType, false);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function testCheckHostedApp() {
|
||||
let request = {type: "getAll"};
|
||||
webappActorRequest(request, function (aResponse) {
|
||||
do_check_true("apps" in aResponse);
|
||||
let apps = aResponse.apps;
|
||||
do_check_true(apps.length > 0);
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let app = apps[i];
|
||||
if (app.id == gAppId) {
|
||||
do_check_eq(app.name, "My hosted app");
|
||||
do_check_eq(app.origin, "http://foo.com");
|
||||
do_check_eq(app.installOrigin, "http://metadata.foo.com");
|
||||
do_check_eq(app.manifestURL, "http://foo.com/metadata/manifest.webapp");
|
||||
run_next_test();
|
||||
return;
|
||||
}
|
||||
}
|
||||
do_throw("Unable to find the test app by its id");
|
||||
});
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
setup();
|
||||
|
||||
|
@ -3,8 +3,6 @@ head = head_apps.js
|
||||
tail = tail_apps.js
|
||||
|
||||
[test_webappsActor.js]
|
||||
# Persistent failures.
|
||||
skip-if = true
|
||||
[test_appInstall.js]
|
||||
# Persistent failures.
|
||||
skip-if = true
|
||||
|
@ -138,6 +138,12 @@ Connection.Events = {
|
||||
Connection.prototype = {
|
||||
logs: "",
|
||||
log: function(str) {
|
||||
let d = new Date();
|
||||
let hours = ("0" + d.getHours()).slice(-2);
|
||||
let minutes = ("0" + d.getMinutes()).slice(-2);
|
||||
let seconds = ("0" + d.getSeconds()).slice(-2);
|
||||
let timestamp = [hours, minutes, seconds].join(":") + ": ";
|
||||
str = timestamp + str;
|
||||
this.logs += "\n" + str;
|
||||
this.emit(Connection.Events.NEW_LOG, str);
|
||||
},
|
||||
@ -212,10 +218,10 @@ Connection.prototype = {
|
||||
|
||||
_clientConnect: function () {
|
||||
let transport;
|
||||
if (!this._host) {
|
||||
if (!this.host) {
|
||||
transport = DebuggerServer.connectPipe();
|
||||
} else {
|
||||
transport = debuggerSocketConnect(this._host, this._port);
|
||||
transport = debuggerSocketConnect(this.host, this.port);
|
||||
}
|
||||
this._client = new DebuggerClient(transport);
|
||||
this._client.addOneTimeListener("closed", this._onDisconnected);
|
||||
@ -243,12 +249,13 @@ Connection.prototype = {
|
||||
}
|
||||
|
||||
clearTimeout(this._timeoutID);
|
||||
|
||||
switch (this.status) {
|
||||
case Connection.Status.CONNECTED:
|
||||
this.log("disconnected (unexpected)");
|
||||
break;
|
||||
case Connection.Status.CONNECTING:
|
||||
this.log("Connection error");
|
||||
this.log("connection error. Possible causes: USB port not connected, port not forwarded (adb forward), wrong host or port, remote debugging not enabled on the device.");
|
||||
break;
|
||||
default:
|
||||
this.log("disconnected");
|
||||
@ -263,7 +270,7 @@ Connection.prototype = {
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this.log("connection timeout");
|
||||
this.log("connection timeout. Possible causes: didn't click on 'accept' (prompt).");
|
||||
this.emit(Connection.Events.TIMEOUT);
|
||||
this.disconnect();
|
||||
},
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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 {Cc, Ci, Cu} = require("chrome");
|
||||
const {Cc, Ci, Cu, CC} = require("chrome");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const {method, RetVal} = protocol;
|
||||
const promise = require("sdk/core/promise");
|
||||
@ -145,6 +145,24 @@ let DeviceActor = protocol.ActorClass({
|
||||
|
||||
}, {request: {},response: { value: RetVal("json")}}),
|
||||
|
||||
getWallpaper: method(function() {
|
||||
let deferred = promise.defer();
|
||||
this._getSetting("wallpaper.image").then((blob) => {
|
||||
let FileReader = CC("@mozilla.org/files/filereader;1");
|
||||
let reader = new FileReader();
|
||||
let conn = this.conn;
|
||||
reader.addEventListener("load", function() {
|
||||
let str = new LongStringActor(conn, reader.result);
|
||||
deferred.resolve(str);
|
||||
});
|
||||
reader.addEventListener("error", function() {
|
||||
deferred.reject(reader.error);
|
||||
});
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
return deferred.promise;
|
||||
}, {request: {},response: { value: RetVal("longstring")}}),
|
||||
|
||||
screenshotToDataURL: method(function() {
|
||||
let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
|
||||
let canvas = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
|
@ -3376,8 +3376,6 @@ function ThreadSources(aThreadActor, aUseSourceMaps, aAllowPredicate,
|
||||
this._allow = aAllowPredicate;
|
||||
this._onNewSource = aOnNewSource;
|
||||
|
||||
// source map URL --> promise of SourceMapConsumer
|
||||
this._sourceMaps = Object.create(null);
|
||||
// generated source url --> promise of SourceMapConsumer
|
||||
this._sourceMapsByGeneratedSource = Object.create(null);
|
||||
// original source url --> promise of SourceMapConsumer
|
||||
@ -3450,7 +3448,6 @@ ThreadSources.prototype = {
|
||||
})
|
||||
.then(null, (e) => {
|
||||
reportError(e);
|
||||
delete this._sourceMaps[this._normalize(aScript.sourceMapURL, aScript.url)];
|
||||
delete this._sourceMapsByGeneratedSource[aScript.url];
|
||||
return [this.source(aScript.url)];
|
||||
})
|
||||
@ -3465,9 +3462,6 @@ ThreadSources.prototype = {
|
||||
* |aScript| must have a non-null sourceMapURL.
|
||||
*/
|
||||
sourceMap: function TS_sourceMap(aScript) {
|
||||
if (aScript.url in this._sourceMapsByGeneratedSource) {
|
||||
return this._sourceMapsByGeneratedSource[aScript.url];
|
||||
}
|
||||
dbg_assert(aScript.sourceMapURL, "Script should have a sourceMapURL");
|
||||
let sourceMapURL = this._normalize(aScript.sourceMapURL, aScript.url);
|
||||
let map = this._fetchSourceMap(sourceMapURL, aScript.url)
|
||||
@ -3495,17 +3489,12 @@ ThreadSources.prototype = {
|
||||
* them from aScriptURL.
|
||||
*/
|
||||
_fetchSourceMap: function TS__fetchSourceMap(aAbsSourceMapURL, aScriptURL) {
|
||||
if (aAbsSourceMapURL in this._sourceMaps) {
|
||||
return this._sourceMaps[aAbsSourceMapURL];
|
||||
}
|
||||
|
||||
let promise = fetch(aAbsSourceMapURL).then(({ content }) => {
|
||||
let map = new SourceMapConsumer(content);
|
||||
this._setSourceMapRoot(map, aAbsSourceMapURL, aScriptURL);
|
||||
return map;
|
||||
});
|
||||
this._sourceMaps[aAbsSourceMapURL] = promise;
|
||||
return promise;
|
||||
return fetch(aAbsSourceMapURL, { loadFromCache: false })
|
||||
.then(({ content }) => {
|
||||
let map = new SourceMapConsumer(content);
|
||||
this._setSourceMapRoot(map, aAbsSourceMapURL, aScriptURL);
|
||||
return map;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3731,7 +3720,10 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
try {
|
||||
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) {
|
||||
if (!Components.isSuccessCode(aStatus)) {
|
||||
deferred.reject(new Error("Request failed: " + url));
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatus
|
||||
+ " after NetUtil.asyncFetch for url = "
|
||||
+ url));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3741,7 +3733,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
aStream.close();
|
||||
});
|
||||
} catch (ex) {
|
||||
deferred.reject(new Error("Request failed: " + url));
|
||||
deferred.reject(ex);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -3759,7 +3751,10 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
let streamListener = {
|
||||
onStartRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed: " + url));
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStartRequest handler for url = "
|
||||
+ url));
|
||||
}
|
||||
},
|
||||
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
|
||||
@ -3767,7 +3762,10 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed: " + url));
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStopRequest handler for url = "
|
||||
+ url));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let CC = Components.Constructor;
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
let promise;
|
||||
|
||||
function debug(aMsg) {
|
||||
@ -19,6 +21,89 @@ function debug(aMsg) {
|
||||
*/
|
||||
}
|
||||
|
||||
function PackageUploadActor(aPath, aFile) {
|
||||
this._path = aPath;
|
||||
this._file = aFile;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
PackageUploadActor.prototype = {
|
||||
actorPrefix: "packageUploadActor",
|
||||
|
||||
/**
|
||||
* This method isn't exposed to the client.
|
||||
* It is meant to be called by server code, in order to get
|
||||
* access to the temporary file out of the actor ID.
|
||||
*/
|
||||
getFilePath: function () {
|
||||
return this._path;
|
||||
},
|
||||
|
||||
/**
|
||||
* This method allows you to upload a piece of file.
|
||||
* It expects a chunk argument that is the a string to write to the file.
|
||||
*/
|
||||
chunk: function (aRequest) {
|
||||
let chunk = aRequest.chunk;
|
||||
if (!chunk || chunk.length <= 0) {
|
||||
return {error: "parameterError",
|
||||
message: "Missing or invalid chunk argument"};
|
||||
}
|
||||
// Translate the string used to transfer the chunk over JSON
|
||||
// back to a typed array
|
||||
let data = new Uint8Array(chunk.length);
|
||||
for (let i = 0, l = chunk.length; i < l ; i++) {
|
||||
data[i] = chunk.charCodeAt(i);
|
||||
}
|
||||
return this._file.write(data)
|
||||
.then((written) => {
|
||||
this.size += written;
|
||||
return {
|
||||
written: written,
|
||||
size: this.size
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This method needs to be called, when you are done uploading
|
||||
* chunks, before trying to access/use the temporary file.
|
||||
* Otherwise, the file may be partially written
|
||||
* and also be locked.
|
||||
*/
|
||||
done: function (aRequest) {
|
||||
return this._file.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* This method allows you to delete the temporary file,
|
||||
* when you are done using it.
|
||||
*/
|
||||
remove: function (aRequest) {
|
||||
this._cleanupFile();
|
||||
|
||||
return {};
|
||||
},
|
||||
|
||||
_cleanupFile: function () {
|
||||
try {
|
||||
this._file.close();
|
||||
} catch(e) {}
|
||||
try {
|
||||
OS.File.remove(this._path);
|
||||
} catch(e) {}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The request types this actor can handle.
|
||||
*/
|
||||
PackageUploadActor.prototype.requestTypes = {
|
||||
"chunk": PackageUploadActor.prototype.chunk,
|
||||
"done": PackageUploadActor.prototype.done,
|
||||
"remove": PackageUploadActor.prototype.remove
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a WebappsActor. WebappsActor provides remote access to
|
||||
* install apps.
|
||||
@ -37,11 +122,28 @@ function WebappsActor(aConnection) {
|
||||
// Keep reference of already created app actors.
|
||||
// key: app frame message manager, value: ContentTabActor's grip() value
|
||||
this._appActorsMap = new Map();
|
||||
|
||||
this.conn = aConnection;
|
||||
this._uploads = [];
|
||||
this._actorPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._actorPool);
|
||||
}
|
||||
|
||||
WebappsActor.prototype = {
|
||||
actorPrefix: "webapps",
|
||||
|
||||
disconnect: function () {
|
||||
// When we stop using this actor, we should ensure removing all files.
|
||||
for (let upload of this._uploads) {
|
||||
upload.remove();
|
||||
}
|
||||
this._uploads = null;
|
||||
|
||||
this.conn.removeActorPool(this._actorPool);
|
||||
this._actorPool = null;
|
||||
this.conn = null;
|
||||
},
|
||||
|
||||
_registerApp: function wa_actorRegisterApp(aApp, aId, aDir) {
|
||||
debug("registerApp");
|
||||
let reg = DOMApplicationRegistry;
|
||||
@ -110,64 +212,126 @@ WebappsActor.prototype = {
|
||||
return type;
|
||||
},
|
||||
|
||||
installHostedApp: function wa_actorInstallHosted(aDir, aId, aReceipts) {
|
||||
uploadPackage: function () {
|
||||
debug("uploadPackage\n");
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["file-upload"], true, false);
|
||||
if (!tmpDir.exists() || !tmpDir.isDirectory()) {
|
||||
return {error: "fileAccessError",
|
||||
message: "Unable to create temporary folder"};
|
||||
}
|
||||
let tmpFile = tmpDir;
|
||||
tmpFile.append("package.zip");
|
||||
tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
|
||||
if (!tmpFile.exists() || !tmpDir.isFile()) {
|
||||
return {error: "fileAccessError",
|
||||
message: "Unable to create temporary file"};
|
||||
}
|
||||
|
||||
return OS.File.open(tmpFile.path, { write: true, truncate: true })
|
||||
.then((file) => {
|
||||
let actor = new PackageUploadActor(tmpFile.path, file);
|
||||
this._actorPool.addActor(actor);
|
||||
this._uploads.push(actor);
|
||||
return { actor: actor.actorID };
|
||||
});
|
||||
},
|
||||
|
||||
installHostedApp: function wa_actorInstallHosted(aDir, aId, aReceipts,
|
||||
aManifest, aMetadata) {
|
||||
debug("installHostedApp");
|
||||
let self = this;
|
||||
|
||||
function readManifest() {
|
||||
if (aManifest) {
|
||||
return promise.resolve(aManifest);
|
||||
} else {
|
||||
let deferred = promise.defer();
|
||||
let manFile = aDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
DOMApplicationRegistry._loadJSONAsync(manFile, function(aManifest) {
|
||||
if (!aManifest) {
|
||||
deferred.reject("Error parsing manifest.webapp.");
|
||||
} else {
|
||||
deferred.resolve(aManifest);
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
function checkSideloading(aManifest) {
|
||||
let appType = self._getAppType(aManifest.type);
|
||||
|
||||
// In production builds, don't allow installation of certified apps.
|
||||
if (!DOMApplicationRegistry.allowSideloadingCertified &&
|
||||
appType == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
||||
throw new Error("Installing certified apps is not allowed.");
|
||||
}
|
||||
return appType;
|
||||
}
|
||||
function writeManifest(aAppType) {
|
||||
// Move manifest.webapp to the destination directory.
|
||||
// The destination directory for this app.
|
||||
let installDir = DOMApplicationRegistry._getAppDir(aId);
|
||||
if (aManifest) {
|
||||
let deferred = promise.defer();
|
||||
let manFile = installDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest), function () {
|
||||
deferred.resolve(aAppType);
|
||||
});
|
||||
return deferred.promise;
|
||||
} else {
|
||||
let manFile = aDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
manFile.moveTo(installDir, "manifest.webapp");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function readMetadata(aAppType) {
|
||||
if (aMetadata) {
|
||||
return { metadata: aMetadata, appType: aAppType };
|
||||
}
|
||||
// Read the origin and manifest url from metadata.json
|
||||
let deferred = promise.defer();
|
||||
let metaFile = aDir.clone();
|
||||
metaFile.append("metadata.json");
|
||||
DOMApplicationRegistry._loadJSONAsync(metaFile, function(aMetadata) {
|
||||
if (!aMetadata) {
|
||||
deferred.reject("Error parsing metadata.json.");
|
||||
return;
|
||||
}
|
||||
if (!aMetadata.origin) {
|
||||
deferred.reject("Missing 'origin' property in metadata.json");
|
||||
return;
|
||||
}
|
||||
deferred.resolve({ metadata: aMetadata, appType: aAppType });
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
let runnable = {
|
||||
run: function run() {
|
||||
try {
|
||||
// Move manifest.webapp to the destination directory.
|
||||
let manFile = aDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
DOMApplicationRegistry._loadJSONAsync(manFile, function(aManifest) {
|
||||
if (!aManifest) {
|
||||
self._sendError("Error Parsing manifest.webapp", aId);
|
||||
return;
|
||||
}
|
||||
|
||||
let appType = self._getAppType(aManifest.type);
|
||||
|
||||
// In production builds, don't allow installation of certified apps.
|
||||
if (!DOMApplicationRegistry.allowSideloadingCertified &&
|
||||
appType == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
||||
self._sendError("Installing certified apps is not allowed.", aId);
|
||||
return;
|
||||
}
|
||||
|
||||
// The destination directory for this app.
|
||||
let installDir = DOMApplicationRegistry._getAppDir(aId);
|
||||
manFile.moveTo(installDir, "manifest.webapp");
|
||||
|
||||
// Read the origin and manifest url from metadata.json
|
||||
let metaFile = aDir.clone();
|
||||
metaFile.append("metadata.json");
|
||||
DOMApplicationRegistry._loadJSONAsync(metaFile, function(aMetadata) {
|
||||
if (!aMetadata) {
|
||||
self._sendError("Error Parsing metadata.json", aId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aMetadata.origin) {
|
||||
self._sendError("Missing 'origin' property in metadata.json", aId);
|
||||
return;
|
||||
}
|
||||
|
||||
let origin = aMetadata.origin;
|
||||
let manifestURL = aMetadata.manifestURL ||
|
||||
readManifest().
|
||||
then(checkSideloading).
|
||||
then(writeManifest).
|
||||
then(readMetadata).
|
||||
then(function ({ metadata, appType }) {
|
||||
let origin = metadata.origin;
|
||||
let manifestURL = metadata.manifestURL ||
|
||||
origin + "/manifest.webapp";
|
||||
// Create a fake app object with the minimum set of properties we need.
|
||||
let app = {
|
||||
origin: origin,
|
||||
installOrigin: aMetadata.installOrigin || origin,
|
||||
installOrigin: metadata.installOrigin || origin,
|
||||
manifestURL: manifestURL,
|
||||
appStatus: appType,
|
||||
receipts: aReceipts,
|
||||
};
|
||||
|
||||
self._registerApp(app, aId, aDir);
|
||||
}, function (error) {
|
||||
self._sendError(error, aId);
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
// If anything goes wrong, just send it back.
|
||||
self._sendError(e.toString(), aId);
|
||||
@ -266,9 +430,27 @@ WebappsActor.prototype = {
|
||||
let appDir = FileUtils.getDir("TmpD", ["b2g", appId], false, false);
|
||||
|
||||
if (!appDir || !appDir.exists()) {
|
||||
return { error: "badParameterType",
|
||||
message: "missing directory " + appDir.path
|
||||
}
|
||||
if (aRequest.upload) {
|
||||
// Ensure creating the directory (recursively)
|
||||
appDir = FileUtils.getDir("TmpD", ["b2g", appId], true, false);
|
||||
let actor = this.conn.getActor(aRequest.upload);
|
||||
if (!actor) {
|
||||
return { error: "badParameter",
|
||||
message: "Unable to find upload actor '" + aRequest.upload
|
||||
+ "'" };
|
||||
}
|
||||
let appFile = FileUtils.File(actor.getFilePath());
|
||||
if (!appFile.exists()) {
|
||||
return { error: "badParameter",
|
||||
message: "The uploaded file doesn't exist on device" };
|
||||
}
|
||||
appFile.moveTo(appDir, "application.zip");
|
||||
}
|
||||
else if (!aRequest.manifest && !aRequest.metadata) {
|
||||
return { error: "badParameterType",
|
||||
message: "missing directory " + appDir.path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let testFile = appDir.clone();
|
||||
@ -277,6 +459,7 @@ WebappsActor.prototype = {
|
||||
let receipts = (aRequest.receipts && Array.isArray(aRequest.receipts))
|
||||
? aRequest.receipts
|
||||
: [];
|
||||
let manifest, metadata;
|
||||
|
||||
if (testFile.exists()) {
|
||||
this.installPackagedApp(appDir, appId, receipts);
|
||||
@ -290,14 +473,20 @@ WebappsActor.prototype = {
|
||||
});
|
||||
|
||||
if (missing) {
|
||||
try {
|
||||
appDir.remove(true);
|
||||
} catch(e) {}
|
||||
return { error: "badParameterType",
|
||||
message: "hosted app file is missing" }
|
||||
if (aRequest.manifest && aRequest.metadata &&
|
||||
aRequest.metadata.origin) {
|
||||
manifest = aRequest.manifest;
|
||||
metadata = aRequest.metadata;
|
||||
} else {
|
||||
try {
|
||||
appDir.remove(true);
|
||||
} catch(e) {}
|
||||
return { error: "badParameterType",
|
||||
message: "hosted app file and manifest/metadata fields are missing" };
|
||||
}
|
||||
}
|
||||
|
||||
this.installHostedApp(appDir, appId, receipts);
|
||||
this.installHostedApp(appDir, appId, receipts, manifest, metadata);
|
||||
}
|
||||
|
||||
return { appId: appId, path: appDir.path }
|
||||
@ -373,8 +562,8 @@ WebappsActor.prototype = {
|
||||
// Download the URL as a blob
|
||||
// bug 899177: there is a bug with xhr and app:// and jar:// uris
|
||||
// that ends up forcing the content type to application/xml.
|
||||
let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
|
||||
let req = new XMLHttpRequest();
|
||||
let req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
req.open("GET", iconURL, false);
|
||||
req.responseType = "blob";
|
||||
|
||||
@ -389,8 +578,8 @@ WebappsActor.prototype = {
|
||||
}
|
||||
|
||||
// Convert the blog to a base64 encoded data URI
|
||||
let FileReader = CC("@mozilla.org/files/filereader;1");
|
||||
let reader = new FileReader()
|
||||
let reader = Cc["@mozilla.org/files/filereader;1"]
|
||||
.createInstance(Ci.nsIDOMFileReader);
|
||||
reader.onload = function () {
|
||||
deferred.resolve({
|
||||
url: reader.result
|
||||
@ -636,6 +825,7 @@ WebappsActor.prototype.requestTypes = {
|
||||
// only on production devices
|
||||
if (Services.prefs.getBoolPref("devtools.debugger.enable-content-actors")) {
|
||||
let requestTypes = WebappsActor.prototype.requestTypes;
|
||||
requestTypes.uploadPackage = WebappsActor.prototype.uploadPackage;
|
||||
requestTypes.getAll = WebappsActor.prototype.getAll;
|
||||
requestTypes.launch = WebappsActor.prototype.launch;
|
||||
requestTypes.close = WebappsActor.prototype.close;
|
||||
|
@ -201,8 +201,8 @@ function finishClient(aClient)
|
||||
/**
|
||||
* Takes a relative file path and returns the absolute file url for it.
|
||||
*/
|
||||
function getFileUrl(aName) {
|
||||
let file = do_get_file(aName);
|
||||
function getFileUrl(aName, aAllowMissing=false) {
|
||||
let file = do_get_file(aName, aAllowMissing);
|
||||
return Services.io.newFileURI(file).spec;
|
||||
}
|
||||
|
||||
@ -210,9 +210,9 @@ function getFileUrl(aName) {
|
||||
* Returns the full path of the file with the specified name in a
|
||||
* platform-independent and URL-like form.
|
||||
*/
|
||||
function getFilePath(aName)
|
||||
function getFilePath(aName, aAllowMissing=false)
|
||||
{
|
||||
let file = do_get_file(aName);
|
||||
let file = do_get_file(aName, aAllowMissing);
|
||||
let path = Services.io.newFileURI(file).spec;
|
||||
let filePrePath = "file://";
|
||||
if ("nsILocalFileWin" in Ci &&
|
||||
|
102
toolkit/devtools/server/tests/unit/test_sourcemaps-13.js
Normal file
@ -0,0 +1,102 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we don't permanently cache source maps.
|
||||
*/
|
||||
|
||||
var gDebuggee;
|
||||
var gClient;
|
||||
var gThreadClient;
|
||||
|
||||
Components.utils.import("resource:///modules/devtools/SourceMap.jsm");
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-source-map");
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function() {
|
||||
attachTestTabAndResume(gClient, "test-source-map", function(aResponse, aTabClient, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
setup_code();
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
// The MAP_FILE_NAME is .txt so that the OS will definitely have an extension ->
|
||||
// content type mapping for the extension. If it doesn't (like .map or .json),
|
||||
// it logs console errors, which cause the test to fail. See bug 907839.
|
||||
const MAP_FILE_NAME = "temporary-generated.txt";
|
||||
|
||||
const TEMP_FILE_1 = "temporary1.js";
|
||||
const TEMP_FILE_2 = "temporary2.js";
|
||||
const TEMP_GENERATED_SOURCE = "temporary-generated.js";
|
||||
|
||||
function setup_code() {
|
||||
let node = new SourceNode(1, 0,
|
||||
getFileUrl(TEMP_FILE_1, true),
|
||||
"function temporary1() {}\n");
|
||||
let { code, map } = node.toStringWithSourceMap({
|
||||
file: getFileUrl(TEMP_GENERATED_SOURCE, true)
|
||||
});
|
||||
|
||||
code += "//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true);
|
||||
writeFile(MAP_FILE_NAME, map.toString());
|
||||
|
||||
Cu.evalInSandbox(code,
|
||||
gDebuggee,
|
||||
"1.8",
|
||||
getFileUrl(TEMP_GENERATED_SOURCE, true),
|
||||
1);
|
||||
|
||||
test_initial_sources();
|
||||
}
|
||||
|
||||
function test_initial_sources() {
|
||||
gThreadClient.getSources(function ({ error, sources }) {
|
||||
do_check_true(!error);
|
||||
do_check_eq(sources.length, 1);
|
||||
do_check_eq(sources[0].url, getFileUrl(TEMP_FILE_1, true));
|
||||
setup_new_code();
|
||||
});
|
||||
}
|
||||
|
||||
function setup_new_code() {
|
||||
let node = new SourceNode(1, 0,
|
||||
getFileUrl(TEMP_FILE_2, true),
|
||||
"function temporary2() {}\n");
|
||||
let { code, map } = node.toStringWithSourceMap({
|
||||
file: getFileUrl(TEMP_GENERATED_SOURCE, true)
|
||||
});
|
||||
|
||||
code += "\n//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true);
|
||||
writeFile(MAP_FILE_NAME, map.toString());
|
||||
|
||||
Cu.evalInSandbox(code,
|
||||
gDebuggee,
|
||||
"1.8",
|
||||
getFileUrl(TEMP_GENERATED_SOURCE, true),
|
||||
1);
|
||||
|
||||
gClient.addOneTimeListener("newSource", test_new_sources);
|
||||
}
|
||||
|
||||
function test_new_sources() {
|
||||
gThreadClient.getSources(function ({ error, sources }) {
|
||||
do_check_true(!error);
|
||||
|
||||
// Should now have TEMP_FILE_2 as a source.
|
||||
do_check_eq(sources.length, 2);
|
||||
let s = sources.filter(s => s.url === getFileUrl(TEMP_FILE_2, true))[0];
|
||||
do_check_true(!!s);
|
||||
|
||||
finish_test();
|
||||
});
|
||||
}
|
||||
|
||||
function finish_test() {
|
||||
do_get_file(MAP_FILE_NAME).remove(false);
|
||||
finishClient(gClient);
|
||||
}
|
@ -119,6 +119,7 @@ reason = bug 820380
|
||||
[test_sourcemaps-09.js]
|
||||
[test_sourcemaps-10.js]
|
||||
[test_sourcemaps-11.js]
|
||||
[test_sourcemaps-13.js]
|
||||
[test_objectgrips-01.js]
|
||||
[test_objectgrips-02.js]
|
||||
[test_objectgrips-03.js]
|
||||
|