Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-09-05 15:37:00 -04:00
commit a7732af543
55 changed files with 3358 additions and 143 deletions

View File

@ -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);

View File

@ -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"

View File

@ -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"

View File

@ -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"/>

View File

@ -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 &&

View 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;

View 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);
},
}

View 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>

View 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;
},
}

View 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>

View 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");

View 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>

View 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);
},
}

View 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>

View File

@ -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);
}

View 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
}
})();

View File

@ -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]);
}

View File

@ -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);
});

View File

@ -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 \

View File

@ -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;
});

View File

@ -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
*

View File

@ -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)

View File

@ -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);

View File

@ -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;
},

View File

@ -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;
},
};
/**

View File

@ -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;
}
}
};

View File

@ -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;

View File

@ -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">

View File

@ -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).">

View 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.

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View 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 );
}

View 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;
}

View 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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View 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

View 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

View 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

View File

@ -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

View 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;
}

View 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;
}

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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();
},

View File

@ -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");

View File

@ -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;
}

View File

@ -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;

View File

@ -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 &&

View 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);
}

View File

@ -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]