Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-09-11 21:48:20 -04:00
commit ed0259103b
114 changed files with 2570 additions and 777 deletions

View File

@ -1067,7 +1067,8 @@ pref("devtools.gcli.allowSet", false);
pref("devtools.commands.dir", "");
// Disable the app manager
pref("devtools.appmanager.enabled", false);
pref("devtools.appmanager.enabled", true);
pref("devtools.appmanager.simulatorInstallPage", "https://addons.mozilla.org/firefox/addon/firefox-os-simulator/");
// Toolbox preferences
pref("devtools.toolbox.footer.height", 250);

View File

@ -46,7 +46,7 @@
accesskey="&saveLinkCmd.accesskey;"
oncommand="gContextMenu.saveLink();"/>
<menu id="context-marklinkMenu" label="&social.marklinkMenu.label;"
accesskey="&social.marklink.accesskey;">
accesskey="&social.marklinkMenu.accesskey;">
<menupopup/>
</menu>
<menuitem id="context-copyemail"
@ -252,7 +252,7 @@
accesskey="&savePageCmd.accesskey2;"
oncommand="gContextMenu.savePageAs();"/>
<menu id="context-markpageMenu" label="&social.markpageMenu.label;"
accesskey="&social.markpage.accesskey;">
accesskey="&social.markpageMenu.accesskey;">
<menupopup/>
</menu>
<menuseparator id="context-sep-viewbgimage"/>

View File

@ -1658,12 +1658,12 @@ SocialMarks = {
{
type: "link",
id: "context-marklinkMenu",
label: "social.marklink.label"
label: "social.marklinkMenu.label"
},
{
type: "page",
id: "context-markpageMenu",
label: "social.markpage.label"
label: "social.markpageMenu.label"
}
];
for (let cfg of contextMenus) {

View File

@ -252,12 +252,12 @@ function checkSocialUI(win) {
{
type: "link",
id: "context-marklinkMenu",
label: "social.marklink.label"
label: "social.marklinkMenu.label"
},
{
type: "page",
id: "context-markpageMenu",
label: "social.markpage.label"
label: "social.markpageMenu.label"
}
];

View File

@ -90,6 +90,8 @@ static RedirEntry kRedirMap[] = {
{ "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
#endif
{ "app-manager", "chrome://browser/content/devtools/app-manager/index.xul",
nsIAboutModule::ALLOW_SCRIPT },
};
static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap);

View File

@ -109,6 +109,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
#ifdef MOZ_SERVICES_HEALTHREPORT
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "app-manager", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#if defined(XP_WIN)
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
#elif defined(XP_MACOSX)

View File

@ -678,12 +678,12 @@ let SessionStoreInternal = {
break;
case "TabPinned":
// If possible, update cached data without having to invalidate it
TabStateCache.update(aEvent.originalTarget, "pinned", true);
TabStateCache.updateField(aEvent.originalTarget, "pinned", true);
this.saveStateDelayed(win);
break;
case "TabUnpinned":
// If possible, update cached data without having to invalidate it
TabStateCache.update(aEvent.originalTarget, "pinned", false);
TabStateCache.updateField(aEvent.originalTarget, "pinned", false);
this.saveStateDelayed(win);
break;
}
@ -1330,7 +1330,7 @@ let SessionStoreInternal = {
}
// If possible, update cached data without having to invalidate it
TabStateCache.update(aTab, "hidden", false);
TabStateCache.updateField(aTab, "hidden", false);
// Default delay of 2 seconds gives enough time to catch multiple TabShow
// events due to changing groups in Panorama.
@ -1345,7 +1345,7 @@ let SessionStoreInternal = {
}
// If possible, update cached data without having to invalidate it
TabStateCache.update(aTab, "hidden", true);
TabStateCache.updateField(aTab, "hidden", true);
// Default delay of 2 seconds gives enough time to catch multiple TabHide
// events due to changing groups in Panorama.
@ -1665,7 +1665,6 @@ let SessionStoreInternal = {
},
setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
TabStateCache.delete(aTab);
// If the tab hasn't been restored, then set the data there, otherwise we
// could lose newly added data.
let saveTo;
@ -1679,12 +1678,13 @@ let SessionStoreInternal = {
aTab.__SS_extdata = {};
saveTo = aTab.__SS_extdata;
}
saveTo[aKey] = aStringValue;
TabStateCache.updateField(aTab, "extData", saveTo);
this.saveStateDelayed(aTab.ownerDocument.defaultView);
},
deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
TabStateCache.delete(aTab);
// We want to make sure that if data is accessed early, we attempt to delete
// that data from __SS_data as well. Otherwise we'll throw in cases where
// data can be set or read.
@ -1696,9 +1696,19 @@ let SessionStoreInternal = {
deleteFrom = aTab.linkedBrowser.__SS_data.extData;
}
if (deleteFrom && deleteFrom[aKey])
if (deleteFrom && aKey in deleteFrom) {
delete deleteFrom[aKey];
this.saveStateDelayed(aTab.ownerDocument.defaultView);
// Keep the extData object only if it is not empty, to save
// a little disk space when serializing the tab state later.
if (Object.keys(deleteFrom).length) {
TabStateCache.updateField(aTab, "extData", deleteFrom);
} else {
TabStateCache.removeField(aTab, "extData");
}
this.saveStateDelayed(aTab.ownerDocument.defaultView);
}
},
persistTabAttribute: function ssi_persistTabAttribute(aName) {
@ -4639,7 +4649,7 @@ let TabStateCache = {
* @param {string} aField The field to update.
* @param {*} aValue The new value to place in the field.
*/
update: function(aKey, aField, aValue) {
updateField: function(aKey, aField, aValue) {
let key = this._normalizeToBrowser(aKey);
let data = this._data.get(key);
if (data) {
@ -4648,6 +4658,22 @@ let TabStateCache = {
TabStateCacheTelemetry.recordAccess(!!data);
},
/**
* Remove a given field from a cached tab state.
*
* @param {XULElement} aKey The tab or the associated browser.
* If the tab/browser is not present, do nothing.
* @param {string} aField The field to remove.
*/
removeField: function(aKey, aField) {
let key = this._normalizeToBrowser(aKey);
let data = this._data.get(key);
if (data && aField in data) {
delete data[aField];
}
TabStateCacheTelemetry.recordAccess(!!data);
},
_normalizeToBrowser: function(aKey) {
let nodeName = aKey.localName;
if (nodeName == "tab") {

View File

@ -3,6 +3,7 @@ const ObservableObject = require("devtools/shared/observable-object");
const promise = require("sdk/core/promise");
const {EventEmitter} = Cu.import("resource:///modules/devtools/shared/event-emitter.js");
const {generateUUID} = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
/**
* IndexedDB wrapper that just save project objects
@ -96,7 +97,13 @@ const AppProjects = {
addPackaged: function(folder) {
let project = {
type: "packaged",
location: folder.path
location: folder.path,
// We need a unique id, that is the app origin,
// in order to identify the app when being installed on the device.
// The packaged app local path is a valid id, but only on the client.
// This origin will be used to generate the true id of an app:
// its manifest URL.
packagedAppOrigin: generateUUID().toString().slice(1, -1)
};
return IDB.add(project).then(function () {
store.object.projects.push(project);

View File

@ -7,12 +7,14 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/devtools/gDevTools.jsm");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.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");
const simulatorsStore = require("devtools/app-manager/simulators-store");
let UI = {
init: function() {
@ -43,6 +45,7 @@ let UI = {
this.store = Utils.mergeStores({
"device": new DeviceStore(this.connection),
"connection": new ConnectionStore(this.connection),
"simulators": simulatorsStore,
});
let pre = document.querySelector("#logs > pre");
@ -96,4 +99,47 @@ let UI = {
Services.prefs.setCharPref("devtools.debugger.remote-host", host);
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
},
showSimulatorList: function() {
document.body.classList.add("show-simulators");
},
cancelShowSimulatorList: function() {
document.body.classList.remove("show-simulators");
},
installSimulator: function() {
let url = Services.prefs.getCharPref("devtools.appmanager.simulatorInstallPage");
window.open(url);
},
startSimulator: function(version) {
let port = ConnectionManager.getFreeTCPPort();
let simulator = Simulator.getByVersion(version);
if (!simulator) {
this.connection.log("Error: can't find simulator: " + version);
return;
}
if (!simulator.launch) {
this.connection.log("Error: invalid simulator: " + version);
return;
}
this.connection.log("Found simulator: " + version);
this.connection.log("Starting simulator...");
this.simulator = simulator;
this.simulator.launch({ port: port })
.then(() => {
this.connection.log("Simulator ready. Connecting.");
this.connection.port = port;
this.connection.host = "localhost";
this.connection.once("connected", function() {
this.connection.log("Connected to simulator.");
this.connection.keepConnecting = false;
});
this.connection.keepConnecting = true;
this.connection.connect();
});
document.body.classList.remove("show-simulators");
},
}

View File

@ -38,9 +38,9 @@
<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"}'>
<div id="start-simulator-box">
<span>&connection.or;</span>
<button id="start-simulator-button" class="action-primary" onclick="UI.startSimulator()">&connection.startSimulator;</button>
<button id="start-simulator-button" class="action-primary" onclick="UI.showSimulatorList()">&connection.startSimulator;</button>
</div>
</div>
</div>
@ -81,6 +81,25 @@
</div>
</div>
<!-- Simulator -->
<div id="banner-simulators" class="banner" template='{"type":"attribute","path":"simulators.versions.length","name":"simulator-count"}'>
<div class="connected-indicator"></div>
<div class="banner-box">
<div class="banner-content">
<div class="no-simulator">
<span>&connection.noSimulatorInstalled;</span>
<button class="action-primary" onclick="UI.installSimulator()">&connection.installFirstSimulator;</button>
</div>
<div class="found-simulator">
<span template-loop='{"arrayPath":"simulators.versions","childSelector":"#simulator-item-template"}'></span>
<button class="action-primary" onclick="UI.installSimulator()">&connection.installAnotherSimulator;</button>
</div>
<button class="action-cancel" onclick="UI.cancelShowSimulatorList()">&connection.cancel;</button>
</div>
</div>
</div>
<!-- Logs -->
<div id="banner-logs">
<div id="logs" class="banner-box">
@ -92,6 +111,14 @@
</div>
</body>
<template id="simulator-item-template">
<span>
<button class="simulator-item" onclick="UI.startSimulator(this.dataset.version)" template='{"type":"attribute","path":"version","name":"data-version"}'>
<span template='{"type":"textContent", "path":"version"}'></span>
</button>
</span>
</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="connection-footer.js"></script>

View File

@ -167,7 +167,13 @@ let UI = {
openToolbox: function(manifest) {
this._getTargetForApp(manifest).then((target) => {
gDevTools.showToolbox(target, "webconsole", devtools.Toolbox.HostType.WINDOW);
gDevTools.showToolbox(target,
null,
devtools.Toolbox.HostType.WINDOW).then(toolbox => {
this.connection.once(Connection.Events.DISCONNECTED, () => {
toolbox.destroy();
});
});
}, console.error);
},

View File

@ -14,6 +14,8 @@ 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 {installHosted, installPackaged} = require("devtools/app-actor-front");
const promise = require("sdk/core/promise");
window.addEventListener("message", function(event) {
@ -163,6 +165,39 @@ let UI = {
}
},
install: function(button, location) {
button.dataset.originalTextContent = button.textContent;
button.textContent = Utils.l10n("project.installing");
button.disabled = true;
let project = AppProjects.get(location);
let install;
if (project.type == "packaged") {
install = installPackaged(this.connection.client, this.listTabsResponse.webappsActor, project.location, project.packagedAppOrigin);
} else {
let manifestURLObject = Services.io.newURI(project.location, null, null);
let origin = Services.io.newURI(manifestURLObject.prePath, null, null);
let appId = origin.host;
let metadata = {
origin: origin.spec,
manifestURL: project.location
};
install = installHosted(this.connection.client, this.listTabsResponse.webappsActor, appId, metadata, project.manifest);
}
install.then(function () {
button.disabled = false;
button.textContent = Utils.l10n("project.installed");
setTimeout(function() {
button.textContent = button.dataset.originalTextContent;
}, 1500);
},
function (res) {
button.disabled = false;
let message = res.error + ": " + res.message;
alert(message);
this.connection.log(message);
});
},
start: function(location) {
let project = AppProjects.get(location);
let request = {
@ -223,8 +258,11 @@ let UI = {
this._getTargetForApp(manifest).then((target) => {
gDevTools.showToolbox(target,
null,
devtools.Toolbox.HostType.WINDOW,
this.connection.uid);
devtools.Toolbox.HostType.WINDOW).then(toolbox => {
this.connection.once(Connection.Events.DISCONNECTED, () => {
toolbox.destroy();
});
});
}, console.error);
},

View File

@ -25,7 +25,7 @@
<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" />
<input value="" id="url-input" type="url" pattern="https?://.+" placeholder="&projects.hostedManifestPlaceHolder2;" size="50" />
<div onclick="UI.addHosted()" id="new-hosted-project-click"></div>
<input type="submit" hidden="true"></input>
</form>
@ -71,14 +71,10 @@
</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>

View File

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {Cu} = require("chrome");
const ObservableObject = require("devtools/shared/observable-object");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
let store = new ObservableObject({versions:[]});
function feedStore() {
store.object.versions = Simulator.availableVersions().map(v => {
return {version:v}
});
}
Simulator.on("register", feedStore);
Simulator.on("unregister", feedStore);
feedStore();
module.exports = store;

View File

@ -378,7 +378,7 @@ let gDevToolsBrowser = {
* Open the App Manager
*/
openAppManager: function(gBrowser) {
gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/app-manager/index.xul");
gBrowser.selectedTab = gBrowser.addTab("about:app-manager");
},
/**

View File

@ -1,14 +1,21 @@
<!DOCTYPE html>
<div id="id1"></div>
<div id="id2"></div>
<div id="id3">
<ul class="aList">
<li class="item"></li>
<li class="item"></li>
<li class="item"></li>
<li class="item">
<span id="id4"></span>
</li>
</ul>
</div>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>select last selected test</title>
</head>
<body>
<div id="id1"></div>
<div id="id2"></div>
<div id="id3">
<ul class="aList">
<li class="item"></li>
<li class="item"></li>
<li class="item"></li>
<li class="item">
<span id="id4"></span>
</li>
</ul>
</div>
</body>
</html>

View File

@ -30,23 +30,39 @@ function test() {
}
function endTests() {
toolbox.destroy();
toolbox = inspector = page1 = page2 = null;
gBrowser.removeCurrentTab();
finish();
executeSoon(() => {
toolbox.destroy();
toolbox = inspector = page1 = page2 = null;
gBrowser.removeCurrentTab();
finish();
});
}
function testReSelectingAnElement(id, callback) {
function loadPageAnd(page, callback) {
inspector.once("markuploaded", () => {
executeSoon(callback);
});
if (page) {
content.location = page;
} else {
content.location.reload();
}
}
function reloadAndReselect(id, callback) {
let div = content.document.getElementById(id);
inspector.selection.setNode(div);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, div);
inspector.once("markuploaded", () => {
loadPageAnd(false, () => {
is(inspector.selection.node.id, id, "Node re-selected after reload");
callback();
executeSoon(callback);
});
content.location.reload();
});
inspector.selection.setNode(div);
}
// Test that nodes selected on the test page remain selected after reload
@ -54,10 +70,10 @@ function test() {
{
// Select a few nodes and check they are re-selected after reload of the same
// page
testReSelectingAnElement("id1", () => {
testReSelectingAnElement("id2", () => {
testReSelectingAnElement("id3", () => {
testReSelectingAnElement("id4", testBodySelectedOnNavigate);
reloadAndReselect("id1", () => {
reloadAndReselect("id2", () => {
reloadAndReselect("id3", () => {
reloadAndReselect("id4", testBodySelectedOnNavigate);
});
});
});
@ -68,15 +84,16 @@ function test() {
function testBodySelectedOnNavigate() {
// Last node selected was id4, go to a different page and check body is
// selected
inspector.once("markuploaded", () => {
is(
inspector.selection.node.tagName.toLowerCase(),
"body",
"Node not found, selecting body"
);
testSameNodeSelectedOnNavigateAwayAndBack();
loadPageAnd(page2, () => {
executeSoon(() => {
is(
inspector.selection.node.tagName.toLowerCase(),
"body",
"Node not found, body selected"
);
executeSoon(testSameNodeSelectedOnNavigateAwayAndBack);
});
});
content.location = page2;
}
// Test that the node selected on page 1 gets selected again after a navigation
@ -85,19 +102,25 @@ function test() {
// On page2, select id5
let id = "id5";
let div = content.document.getElementById(id);
inspector.selection.setNode(div);
inspector.once("inspector-updated", () => {
is(inspector.selection.node.id, id);
// go to page1 but do not select anything
inspector.once("markuploaded", () => {
// go back to page2 and check id5 is still the current selection
inspector.once("markuploaded", () => {
is(inspector.selection.node.id, id, "Node re-selected after navigation");
endTests();
executeSoon(() => {
// go to page1 but do not select anything
loadPageAnd(page1, () => {
executeSoon(() => {
// go back to page2 and check id5 is still the current selection
loadPageAnd(page2, () => {
is(inspector.selection.node.id, id, "Node re-selected after navigation");
executeSoon(endTests);
});
});
});
content.location = page2;
});
content.location = page1;
});
inspector.selection.setNode(div);
}
}

View File

@ -1,3 +1,10 @@
<!DOCTYPE html>
<div id="id5"></div>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>select last selected test</title>
</head>
<body>
<div id="id5"></div>
</body>
</html>

View File

@ -234,9 +234,11 @@ ResponsiveUI.prototype = {
onPageUnload: function() {
if (this.closing)
return;
this.touchEnableBefore = this.touchEventHandler.enabled;
this.disableTouch();
delete this.touchEventHandler;
if (this.touchEventHandler) {
this.touchEnableBefore = this.touchEventHandler.enabled;
this.disableTouch();
delete this.touchEventHandler;
}
},
/**

View File

@ -1041,7 +1041,9 @@ CssRuleView.prototype = {
this.element.parentNode.removeChild(this.element);
}
this.elementStyle.destroy();
if (this.elementStyle) {
this.elementStyle.destroy();
}
this.popup.destroy();
},
@ -1210,6 +1212,11 @@ CssRuleView.prototype = {
return this._showPseudoElements;
},
_getRuleViewHeaderClassName: function(isPseudo) {
let baseClassName = "theme-gutter ruleview-header";
return isPseudo ? baseClassName + " ruleview-expandable-header" : baseClassName;
},
/**
* Creates editor UI for each of the rules in _elementStyle.
*/
@ -1230,7 +1237,7 @@ CssRuleView.prototype = {
if (seenPseudoElement && !seenNormalElement && !rule.pseudoElement) {
seenNormalElement = true;
let div = this.doc.createElementNS(HTML_NS, "div");
div.className = "theme-gutter ruleview-header";
div.className = this._getRuleViewHeaderClassName();
div.textContent = this.selectedElementLabel;
this.element.appendChild(div);
}
@ -1238,7 +1245,7 @@ CssRuleView.prototype = {
let inheritedSource = rule.inheritedSource;
if (inheritedSource != lastInheritedSource) {
let div = this.doc.createElementNS(HTML_NS, "div");
div.className = "theme-gutter ruleview-header";
div.className = this._getRuleViewHeaderClassName();
div.textContent = inheritedSource;
lastInheritedSource = inheritedSource;
this.element.appendChild(div);
@ -1248,8 +1255,11 @@ CssRuleView.prototype = {
seenPseudoElement = true;
let div = this.doc.createElementNS(HTML_NS, "div");
div.className = "theme-gutter ruleview-header";
div.className = this._getRuleViewHeaderClassName(true);
div.textContent = this.pseudoElementLabel;
div.addEventListener("dblclick", () => {
this.togglePseudoElementVisibility(!this.showPseudoElements);
}, false);
let twisty = this.pseudoElementTwisty =
this.doc.createElementNS(HTML_NS, "span");

View File

@ -50,7 +50,11 @@
}
.ruleview-header {
vertical-align:middle;
vertical-align: middle;
height: 1.5em;
line-height: 1.5em;
}
}
.ruleview-header.ruleview-expandable-header {
cursor: pointer;
}

View File

@ -48,7 +48,10 @@ function testTopLeft()
ok (!view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are collapsed by twisty");
expander.click();
ok (view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are expanded again");
expander.click();
// Make sure that dblclicking on the header container also toggles the pseudo elements
EventUtils.synthesizeMouseAtCenter(gutters[0], {clickCount: 2}, inspector.sidebar.getWindowForTab("ruleview"));
ok (!view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are collapsed by dblclicking");
let defaultView = element.ownerDocument.defaultView;
let elementRule = elementRules[0];

View File

@ -645,9 +645,9 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY social.chatBar.label "Focus chats">
<!ENTITY social.chatBar.accesskey "c">
<!ENTITY social.markpage.accesskey "P">
<!ENTITY social.markpageMenu.accesskey "P">
<!ENTITY social.markpageMenu.label "Save Page To…">
<!ENTITY social.marklink.accesskey "L">
<!ENTITY social.marklinkMenu.accesskey "L">
<!ENTITY social.marklinkMenu.label "Save Link To…">
<!ENTITY getUserMedia.selectCamera.label "Camera to share:">

View File

@ -430,10 +430,10 @@ social.turnOff.accesskey=T
social.turnOn.label=Turn on %S
social.turnOn.accesskey=T
# LOCALIZATION NOTE (social.markpage.label): %S is the name of the social provider
social.markpage.label=Save Page to %S
# LOCALIZATION NOTE (social.marklink.label): %S is the name of the social provider
social.marklink.label=Save Link to %S
# LOCALIZATION NOTE (social.markpageMenu.label): %S is the name of the social provider
social.markpageMenu.label=Save Page to %S
# LOCALIZATION NOTE (social.marklinkMenu.label): %S is the name of the social provider
social.marklinkMenu.label=Save Link to %S
# LOCALIZATION NOTE (social.error.message): %1$S is brandShortName (e.g. Firefox), %2$S is the name of the social provider
social.error.message=%1$S is unable to connect with %2$S right now.

View File

@ -32,6 +32,9 @@
<!ENTITY connection.disconnecting "Disconnecting…">
<!ENTITY connection.cancel "Cancel">
<!ENTITY connection.or "or">
<!ENTITY connection.noSimulatorInstalled "No simulator installed.">
<!ENTITY connection.installFirstSimulator "Install simulator.">
<!ENTITY connection.installAnotherSimulator "Add">
<!ENTITY projects.localApps "Local Apps">
<!ENTITY projects.addApp "Add">
@ -45,5 +48,5 @@
<!ENTITY projects.startApp "Start">
<!ENTITY projects.stopApp "Stop">
<!ENTITY projects.debugApp "Debug">
<!ENTITY projects.hostedManifestPlaceHolder "http://example.com/app/webapp.manifest">
<!ENTITY projects.hostedManifestPlaceHolder2 "http://example.com/app/manifest.webapp">
<!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

@ -11,6 +11,8 @@ 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
project.installing=Installing...
project.installed=Installed!
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

View File

@ -20,6 +20,8 @@ var ContextUI = {
init: function init() {
Elements.browsers.addEventListener('URLChanged', this, true);
Elements.browsers.addEventListener("AlertActive", this, true);
Elements.browsers.addEventListener("AlertClose", this, true);
Elements.tabList.addEventListener('TabSelect', this, true);
Elements.panelUI.addEventListener('ToolPanelShown', this, false);
Elements.panelUI.addEventListener('ToolPanelHidden', this, false);
@ -325,6 +327,10 @@ var ContextUI = {
case "ToolPanelHidden":
this.dismiss();
break;
case "AlertActive":
case "AlertClose":
ContentAreaObserver.updateContentArea();
break;
case "touchstart":
if (!BrowserUI.isStartTabVisible) {
this.dismiss();

View File

@ -53,6 +53,7 @@
// Fire notification closed event.
let event = new Event('AlertClose');
event.notification = aItem;
this.dispatchEvent(event);
return aItem;

View File

@ -1143,9 +1143,12 @@ Browser.MainDragger.prototype = {
},
_hideScrollbars: function _hideScrollbars() {
this._scrollScales.x = 0, this._scrollScales.y = 0;
this._scrollScales.x = 0;
this._scrollScales.y = 0;
this._horizontalScrollbar.removeAttribute("panning");
this._verticalScrollbar.removeAttribute("panning");
this._horizontalScrollbar.removeAttribute("width");
this._verticalScrollbar.removeAttribute("height");
this._horizontalScrollbar.style.MozTransform = "";
this._verticalScrollbar.style.MozTransform = "";
}

View File

@ -57,6 +57,7 @@ var Downloads = {
Services.obs.addObserver(this, "dl-request", true);
this._notificationBox = Browser.getNotificationBox();
this._notificationBox.addEventListener('AlertClose', this.handleEvent, true);
this._progress = new DownloadProgressListener(this);
this.manager.addListener(this._progress);
@ -225,6 +226,7 @@ var Downloads = {
accessKey: "",
callback: function() {
Downloads.cancelDownload(aDownload);
Downloads._downloadProgressIndicator.reset();
}
}
];
@ -397,6 +399,7 @@ var Downloads = {
accessKey: "",
callback: function() {
Downloads.cancelDownloads();
Downloads._downloadProgressIndicator.reset();
}
}
];
@ -431,6 +434,17 @@ var Downloads = {
}
},
handleEvent: function handleEvent(aEvent) {
switch (aEvent.type) {
case "AlertClose":
if (aEvent.notification.value == "download-complete" &&
!Downloads._notificationBox.getNotificationWithValue("download-complete")) {
Downloads._downloadProgressIndicator.reset();
}
break;
}
},
observe: function (aSubject, aTopic, aData) {
let message = "";
let msgTitle = "";
@ -459,7 +473,6 @@ var Downloads = {
this._showDownloadCompleteToast(download);
this._showDownloadCompleteNotification(download);
}
this._downloadProgressIndicator.reset();
this._progressNotificationInfo.clear();
this._downloadCount = 0;
this._notificationBox.removeNotification(this._progressNotification);
@ -469,7 +482,6 @@ var Downloads = {
case "dl-failed":
download = aSubject.QueryInterface(Ci.nsIDownload);
this._showDownloadFailedNotification(download);
this._downloadProgressIndicator.reset();
break;
case "dl-request":
setTimeout(function() {

View File

@ -46,7 +46,8 @@
.ruleview-warning {
background: url("chrome://browser/skin/devtools/alerticon-warning.png");
-moz-margin-start: 5px;
vertical-align: middle;
display: inline-block;
vertical-align: top;
width: 13px;
height: 12px;
}

View File

@ -50,7 +50,8 @@
.ruleview-warning {
background: url("chrome://browser/skin/devtools/alerticon-warning.png");
-moz-margin-start: 5px;
vertical-align: middle;
display: inline-block;
vertical-align: top;
width: 13px;
height: 12px;
}

View File

@ -33,10 +33,12 @@
display: flex;
}
body.show-simulators .banner,
body.edit-connection .banner {
display: none !important;
}
body.show-simulators #banner-simulators,
body.edit-connection #banner-editing {
display: flex !important;
}
@ -70,10 +72,6 @@ body.edit-connection #banner-editing {
display: inline;
}
#start-simulator-box[simulators-count="0"] {
display: none;
}
/************** PIXELS **************/
* {
@ -193,8 +191,18 @@ button.action-cancel {
background: linear-gradient(to bottom, #69B8FF, #339FFF );
}
#banner-simulators .connected-indicator,
#banner-disconnected .connected-indicator,
#banner-editing .connected-indicator,
#banner-disconnecting .connected-indicator {
background: linear-gradient(to bottom, #375A87, #1C4375 );
}
#banner-simulators .banner-content > * {
display: inline-block;
}
#banner-simulators[simulator-count="0"] .found-simulator,
#banner-simulators:not([simulator-count="0"]) .no-simulator {
display: none;
}

View File

@ -46,7 +46,8 @@
.ruleview-warning {
background: url("chrome://browser/skin/devtools/alerticon-warning.png");
-moz-margin-start: 5px;
vertical-align: middle;
display: inline-block;
vertical-align: top;
width: 13px;
height: 12px;
}

View File

@ -21,10 +21,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=470804
Passing a null targetURL to checkLoadURIWithPrincipal shouldn't crash
**/
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
const nsIScriptSecurityManager = Components.interfaces.nsIScriptSecurityManager;
var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(nsIScriptSecurityManager);
const nsIScriptSecurityManager = SpecialPowers.Ci.nsIScriptSecurityManager;
var secMan = SpecialPowers.Services.scriptSecurityManager;
var principal = SpecialPowers.wrap(document).nodePrincipal;
isnot(principal, undefined, "Should have a principal");
isnot(principal, null, "Should have a non-null principal");

View File

@ -30,12 +30,10 @@ function getLoadContext() {
}
function testCopyPaste (isXHTML) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var suppressUnicodeCheckIfHidden = !!isXHTML;
var suppressHTMLCheck = !!isXHTML;
var webnav = window.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
var webnav = SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
.getInterface(SpecialPowers.Ci.nsIWebNavigation)
var docShell = webnav.QueryInterface(SpecialPowers.Ci.nsIDocShell);
@ -43,8 +41,7 @@ function testCopyPaste (isXHTML) {
var documentViewer = docShell.contentViewer
.QueryInterface(SpecialPowers.Ci.nsIContentViewerEdit);
var clipboard = SpecialPowers.Cc["@mozilla.org/widget/clipboard;1"]
.getService(SpecialPowers.Ci.nsIClipboard);
var clipboard = SpecialPowers.Services.clipboard;
var textarea = SpecialPowers.wrap(document.getElementById('input'));
@ -88,14 +85,14 @@ function testCopyPaste (isXHTML) {
transferable.init(getLoadContext());
transferable.addDataFlavor(mime);
clipboard.getData(transferable, 1);
var data = {};
var data = SpecialPowers.createBlankObject();
transferable.getTransferData(mime, data, {}) ;
return data;
}
function testClipboardValue(mime, expected) {
if (suppressHTMLCheck && mime == "text/html")
return null;
var data = getClipboardData(mime);
var data = SpecialPowers.wrap(getClipboardData(mime));
is (data.value == null ? data.value :
data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data,
expected,

View File

@ -12,11 +12,8 @@ function run() {
}
function forcegc(){
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
Components.utils.forceGC();
var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
wu.garbageCollect();
SpecialPowers.forceGC();
SpecialPowers.gc();
}
</script>

View File

@ -38,8 +38,6 @@ testDoc2.appendChild(testDoc2.createElement("res"));
testDoc2.documentElement.appendChild(testDoc2.createTextNode("text"));
is(testDoc2.inputEncoding, null, "wrong encoding");
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var testData = "blahblahblahblahblahblahblaaaaaaaah. blah.";
var extensions = [".txt",".png",".jpg",".gif",".xml", "noext"];
var fileTypes = ["text/plain", "image/png", "image/jpeg", "image/gif", "text/xml", null];
@ -72,14 +70,13 @@ extensions.forEach(
testFiles.push(testFile);
var fileList = document.getElementById('fileList');
fileList.value = testFile.path;
SpecialPowers.wrap(fileList).value = testFile.path;
testDOMFiles.push(fileList.files[0]);
}
);
function createFileWithDataExt(fileData, extension) {
var dirSvc = SpecialPowers.Cc["@mozilla.org/file/directory_service;1"].getService(SpecialPowers.Ci.nsIProperties);
var testFile = dirSvc.get("ProfD", SpecialPowers.Ci.nsIFile);
var testFile = SpecialPowers.Services.dirsvc.get("ProfD", SpecialPowers.Ci.nsIFile);
testFile.append("testfile" + extension);
var outStream = SpecialPowers.Cc["@mozilla.org/network/file-output-stream;1"].createInstance(SpecialPowers.Ci.nsIFileOutputStream);
outStream.init(testFile, 0x02 | 0x08 | 0x20, 0666, 0);

View File

@ -65,8 +65,6 @@ function runNextTest() {
if (tests.length > 0) {
var test = tests.shift();
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// Initialize state variables
testName = test[0]
currentState = 0;

View File

@ -33,10 +33,7 @@ function runTest() {
xhr.open("GET", "test_bug382871.html");
xhr.send();
xhr = null;
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
SpecialPowers.gc();
}
SimpleTest.waitForExplicitFinish();

View File

@ -21,14 +21,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=403852
<pre id="test">
<script class="testbody" type="text/javascript">
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var dirSvc = SpecialPowers.Cc["@mozilla.org/file/directory_service;1"].getService(SpecialPowers.Ci.nsIProperties);
var testFile = dirSvc.get("ProfD", SpecialPowers.Ci.nsIFile);
var testFile = SpecialPowers.Services.dirsvc.get("ProfD", SpecialPowers.Ci.nsIFile);
testFile.append("prefs.js");
var fileList = document.getElementById('fileList');
fileList.value = testFile.path;
SpecialPowers.wrap(fileList).value = testFile.path;
// Make sure the file is accessible with indexed notation
var domFile = fileList.files[0];

View File

@ -21,16 +21,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=416383
/** Test for Bug 416383 **/
function runTest() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var testParent = document.getElementById("test_parent");
var attrs = testParent.firstChild.attributes;
ok(attrs != null, "Element should have attributes!");
var attr = testParent.firstChild.getAttributeNode("someattr");
ok(attr.value == "foo", "The value of the attribute should be 'foo'!");
testParent.removeChild(testParent.firstChild);
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
SpecialPowers.gc();
ok(true, "Browser should not crash!")
}

View File

@ -23,13 +23,6 @@ SimpleTest.waitForExplicitFinish();
var img1loaded = false;
var img1errored = false;
function gc() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
}
// Our test image
function loadTestImage() {
var img1 = new Image();
@ -46,7 +39,7 @@ function loadTestImage() {
loadTestImage();
// Probably overkill to gc() more than once, but let's be safe
gc(); gc(); gc();
SpecialPowers.gc(); SpecialPowers.gc(); SpecialPowers.gc();
function finishTest() {
is(img1errored, true, "Image 1 should error");

View File

@ -19,13 +19,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=448993
/** Test for Bug 448993 **/
function gc() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
}
function runTest() {
var a = document.getElementById("a");
var b = document.getElementById("b");
@ -37,7 +30,7 @@ function runTest() {
r.extractContents();
var s = document.createRange();
s.setEnd(b, 0);
gc();
SpecialPowers.gc();
s.deleteContents();
ok(true, "test did not crash");
SimpleTest.finish();

View File

@ -20,14 +20,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=456262
/** Test for Bug 456262 **/
function runTest() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
document.expando = document;
document.documentElement.expando = document;
document.documentElement.setAttribute("foo", "bar");
document.documentElement.getAttributeNode("foo").expando = document;
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
SpecialPowers.gc();
ok(document.expando, "Should have preserved the expando!");
ok(document.documentElement.expando, "Should have preserved the expando!");
ok(document.documentElement.getAttributeNode('foo').expando,

View File

@ -20,16 +20,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=459424
/** Test for Bug 459424 **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var viewer =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShell)
.contentViewer
.QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
viewer.fullZoom = 2;
SpecialPowers.setFullZoom(window, 2);
is(true, true, "Gotta test something");
viewer.fullZoom = 1;
SpecialPowers.setFullZoom(window, 1);
SimpleTest.finish();
});
</script>

View File

@ -19,19 +19,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=465767
/** Test for Bug 465767 **/
function CC() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
}
function runTest() {
var node = document.createElement("div");
var e = null;
try {
document.implementation.createDocument("", "", null).adoptNode(node);
CC();
SpecialPowers.gc();
document.implementation.createDocument("", "", null).adoptNode(node);
} catch(ex) {
e = ex;

View File

@ -19,12 +19,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=592829
// If we ever change how DOMParser initilization works, just update this code
// to create a DOMParser which is not allowed to parse XUL.
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var isXUL = true;
try {
var x =
Components.classes["@mozilla.org/xmlextras/domparser;1"]
SpecialPowers.Cc
.getService(Components.interfaces.nsIDOMParser)
.parseFromString('<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>', "text/xml");
isXUL = x.documentElement.namespaceURI ==

View File

@ -209,10 +209,8 @@ function getFile(name) {
basePath = xhr.responseText;
}
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var fileList = document.getElementById('fileList');
fileList.value = basePath + name;
SpecialPowers.wrap(fileList).value = basePath + name;
return fileList.files[0];
}

View File

@ -90,10 +90,7 @@ function start() {
kOS = OS_WINDOWS;
// code borrowed from browser/modules/test/browser_taskbar_preview.js
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var version = SpecialPowers.Cc['@mozilla.org/system-info;1']
.getService(SpecialPowers.Ci.nsIPropertyBag2)
.getProperty('version');
var version = SpecialPowers.Services.sysinfo.getProperty('version');
kOSVersion = parseFloat(version);
// Version 6.0 is Vista, 6.1 is 7.
} else if (navigator.platform.indexOf('Mac') == 0) {
@ -139,7 +136,6 @@ function start() {
requestLongerTimeoutLen = 6;
function getEnv(env) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var envsvc = SpecialPowers.Cc["@mozilla.org/process/environment;1"].getService(SpecialPowers.Ci.nsIEnvironment);
var val = envsvc.get(env);
if (val == "")

View File

@ -49,8 +49,7 @@ function isRectContainedInRectFromRegion(rect, region) {
}
function paintListener(e) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
if (isRectContainedInRectFromRegion(buttonRect(), e.clientRects)) {
if (isRectContainedInRectFromRegion(buttonRect(), SpecialPowers.wrap(e).clientRects)) {
gNeedsPaint = false;
currentSnapshot = takeSnapshot();
}

View File

@ -65,8 +65,7 @@ function isRectContainedInRectFromRegion(rect, region) {
}
function paintListener(e) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
if (isRectContainedInRectFromRegion(buttonRect(), e.clientRects)) {
if (isRectContainedInRectFromRegion(buttonRect(), SpecialPowers.wrap(e).clientRects)) {
gNeedsPaint = false;
currentSnapshot = takeSnapshot();
}

View File

@ -52,9 +52,7 @@ function clickHandler(e) {
function doTest() {
window.addEventListener("click", clickHandler, true);
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
var utils = SpecialPowers.getDOMWindowUtils(window);
utils.sendMouseEvent("mousedown", 1, 1, 0, 1, 0);
utils.sendMouseEvent("mouseup", 1, 1, 0, 1, 0);

View File

@ -151,8 +151,7 @@ function runTests() {
}
function dispatchTrusted(t, o) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
t.dispatchEvent(new Event("testevent", o));
SpecialPowers.dispatchEvent(window, t, new Event("testevent", o));
}
function testAllListener() {

View File

@ -20,14 +20,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=450876
/** Test for Bug 450876 **/
function doTest() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
is(document.activeElement, document.body, "body element should be focused");
document.getElementById('a').focus();
is(document.activeElement, document.getElementById('a'), "link should have focus");
var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
is(document.hasFocus(), true, "document should be focused");
wu.sendKeyEvent('keypress', 9, 0, 0);
SpecialPowers.DOMWindowUtils.sendKeyEvent('keypress', 9, 0, 0);
is(document.activeElement, document.getElementById('a'), "body element should be focused");
is(document.hasFocus(), false, "document should not be focused");

View File

@ -23,12 +23,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=456273
/** Test for Bug 456273 **/
function doTest() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var ev = document.createEvent('KeyEvents');
ev.initKeyEvent("keypress", true, true, null, true, false,
false, false, 0, "z".charCodeAt(0));
document.getElementById('edit456273').dispatchEvent(ev);
SpecialPowers.dispatchEvent(window, document.getElementById('edit456273'), ev);
ok(true, "PASS");
SimpleTest.finish();
}

View File

@ -21,19 +21,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=605242
SimpleTest.waitForExplicitFinish();
var utils = SpecialPowers.getDOMWindowUtils(window);
function sendMouseDown(el) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var rect = el.getBoundingClientRect();
var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
utils.sendMouseEvent('mousedown', rect.left + 5, rect.top + 5, 0, 1, 0);
}
function sendMouseUp(el) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var rect = el.getBoundingClientRect();
var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
utils.sendMouseEvent('mouseup', rect.left + 5, rect.top + 5, 0, 1, 0);
}

View File

@ -34,11 +34,6 @@ function invalidEventHandler(e)
function completeValidityCheck(element, alwaysValid, isBarred)
{
if (element.type == 'file') {
// Need privileges to set a filename with .value.
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
}
// Check when pattern matches.
if (element.type == 'email') {
element.pattern = ".*@bar.com";
@ -48,7 +43,7 @@ function completeValidityCheck(element, alwaysValid, isBarred)
element.value = "http://mozilla.com";
} else {
element.pattern = "foo";
element.value = "foo";
SpecialPowers.wrap(element).value = "foo";
}
checkValidPattern(element, true, isBarred);
@ -63,7 +58,7 @@ function completeValidityCheck(element, alwaysValid, isBarred)
element.value = "http://mozilla.org";
} else {
element.pattern = "foo";
element.value = "bar";
SpecialPowers.wrap(element).value = "bar";
}
if (!alwaysValid) {

View File

@ -268,10 +268,8 @@ function checkValidityStateObjectAliveWithoutElement(element)
// Then, we make sure it is removed by the garbage collector and we check the
// ValidityState default values (it should not crash).
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var v = document.createElement(element).validity;
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils).garbageCollect();
SpecialPowers.gc();
ok(!v.valueMissing,
"When the element is not alive, it shouldn't suffer from constraint validation");

View File

@ -77,9 +77,7 @@ function link123HrefIs(href, testNum) {
var gGen;
function visitedDependentComputedStyle(win, elem, property) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
var utils = SpecialPowers.getDOMWindowUtils(window);
return utils.getVisitedDependentComputedStyle(elem, "", property);
}

View File

@ -25,9 +25,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=406596
/** Test for Bug 406596 **/
function testTabbing(click, focus, selectionOffset) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
var wu = SpecialPowers.getDOMWindowUtils(window);
var elem = document.getElementById(click);
var rect = elem.getBoundingClientRect();

View File

@ -21,9 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=421640
/** Test for Bug 421640 **/
function test(click, focus, nextFocus) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
var wu = SpecialPowers.getDOMWindowUtils(window);
var selection = window.getSelection();
var edit = document.getElementById("edit");

View File

@ -36,15 +36,14 @@ function runTest()
range.selectNodeContents(editor);
var prevStr = range.toString();
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var docShell =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShell);
var docShell = SpecialPowers.wrap(window)
.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
.getInterface(SpecialPowers.Ci.nsIWebNavigation)
.QueryInterface(SpecialPowers.Ci.nsIDocShell);
var controller =
docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsISelectionDisplay)
.QueryInterface(Components.interfaces.nsISelectionController);
docShell.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
.getInterface(SpecialPowers.Ci.nsISelectionDisplay)
.QueryInterface(SpecialPowers.Ci.nsISelectionController);
var sel = controller.getSelection(controller.SELECTION_NORMAL);
sel.collapse(anchorInEditor, 0);
synthesizeKey('a', {});

View File

@ -51,10 +51,7 @@ function checkLinkColor(aElmId, aExpectedColor, aMessage) {
// Because link coloring is asynchronous, we wait until we get the right
// result, or we will time out (resulting in a failure).
function getColor() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var utils = document.defaultView.
QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
var utils = SpecialPowers.getDOMWindowUtils(window);
return utils.getVisitedDependentComputedStyle($(aElmId), "", "color");
}
while (getColor() != aExpectedColor) {

View File

@ -152,10 +152,7 @@ function clickImage(aTarget, aX, aY)
aTarget.style.left = "0";
aTarget.offsetTop;
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var wu = aTarget.ownerDocument.defaultView
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
var wu = SpecialPowers.getDOMWindowUtils(aTarget.ownerDocument.defaultView);
wu.sendMouseEvent('mousedown', aX, aY, 0, 1, 0);
wu.sendMouseEvent('mouseup', aX, aY, 0, 0, 0);

View File

@ -20,10 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=446483
/** Test for Bug 446483 **/
function gc() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
SpecialPowers.gc();
}
function runTest() {

View File

@ -27,10 +27,7 @@ function testViewport() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
/* Grab Viewport Metadata from the document header. */
var iRequester =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
var windowUtils =
iRequester.getInterface(Components.interfaces.nsIDOMWindowUtils);
var windowUtils = SpecialPowers.getDOMWindowUtils(window);
var vpWidth =
parseInt(windowUtils.getDocumentMetadata("viewport-width"));
var vpHeight =

View File

@ -120,7 +120,7 @@ interface ScheduledGCCallback : nsISupports
/**
* interface of Components.utils
*/
[scriptable, uuid(3b3b5adc-dce0-4d4e-a04e-06b2c8b5acfe)]
[scriptable, uuid(c9ccec7a-726c-4479-9438-6f51f6ef4170)]
interface nsIXPCComponents_Utils : nsISupports
{
@ -156,6 +156,32 @@ interface nsIXPCComponents_Utils : nsISupports
[optional] in jsval filename,
[optional] in long lineNo);
/*
* getSandboxMetadata is designed to be called from JavaScript only.
*
* getSandboxMetadata retrieves the metadata associated with
* a sandbox object. It will return undefined if there
* is no metadata attached to the sandbox.
*
* var s = C.u.Sandbox(..., { metadata: "metadata" });
* var metadata = C.u.getSandboxMetadata(s);
*/
[implicit_jscontext]
jsval getSandboxMetadata(in jsval sandbox);
/*
* setSandboxMetadata is designed to be called from JavaScript only.
*
* setSandboxMetadata sets the metadata associated with
* a sandbox object.
*
* Note that the metadata object will be copied before being used.
* The copy will be performed using the structured clone algorithm.
* Note that this algorithm does not support reflectors and
* it will throw if it encounters them.
*/
[implicit_jscontext]
void setSandboxMetadata(in jsval sandbox, in jsval metadata);
/*
* import is designed to be called from JavaScript only.

View File

@ -585,9 +585,11 @@ sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue
return JS_ConvertStub(cx, obj, type, vp);
}
#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
static const JSClass SandboxClass = {
"Sandbox",
XPCONNECT_GLOBAL_FLAGS,
XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize,
NULL, NULL, NULL, NULL, TraceXPCGlobal
@ -1042,6 +1044,8 @@ xpc::CreateSandboxObject(JSContext *cx, jsval *vp, nsISupports *prinOrSop, Sandb
// about:memory may use that information
xpc::SetLocationForGlobal(sandbox, options.sandboxName);
xpc::SetSandboxMetadata(cx, sandbox, options.metadata);
JS_FireOnNewGlobalObject(cx, sandbox);
return NS_OK;
@ -1339,6 +1343,10 @@ ParseOptionsObject(JSContext *cx, jsval from, SandboxOptions &options)
NS_ENSURE_SUCCESS(rv, rv);
rv = GetDOMConstructorsFromOptions(cx, optionsObject, options);
bool found;
rv = GetPropFromOptions(cx, optionsObject,
"metadata", &options.metadata, &found);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
@ -1651,3 +1659,40 @@ xpc::NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, boo
vp.setObject(*funobj);
return true;
}
nsresult
xpc::GetSandboxMetadata(JSContext *cx, HandleObject sandbox, MutableHandleValue rval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsSandbox(sandbox));
RootedValue metadata(cx);
{
JSAutoCompartment ac(cx, sandbox);
metadata = JS_GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT);
}
if (!JS_WrapValue(cx, metadata.address()))
return NS_ERROR_UNEXPECTED;
rval.set(metadata);
return NS_OK;
}
nsresult
xpc::SetSandboxMetadata(JSContext *cx, HandleObject sandbox, HandleValue metadataArg)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsSandbox(sandbox));
RootedValue metadata(cx);
JSAutoCompartment ac(cx, sandbox);
if (!JS_StructuredClone(cx, metadataArg, metadata.address(), NULL, NULL))
return NS_ERROR_UNEXPECTED;
JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata);
return NS_OK;
}

View File

@ -2766,6 +2766,46 @@ nsXPCComponents_Utils::EvalInSandbox(const nsAString& source,
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::GetSandboxMetadata(const JS::Value &sandboxVal,
JSContext *cx, JS::Value *rval)
{
if (!sandboxVal.isObject())
return NS_ERROR_INVALID_ARG;
RootedObject sandbox(cx, &sandboxVal.toObject());
sandbox = js::CheckedUnwrap(sandbox);
if (!sandbox || !xpc::IsSandbox(sandbox))
return NS_ERROR_INVALID_ARG;
RootedValue metadata(cx);
nsresult rv = xpc::GetSandboxMetadata(cx, sandbox, &metadata);
NS_ENSURE_SUCCESS(rv, rv);
*rval = metadata;
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::SetSandboxMetadata(const JS::Value &sandboxVal,
const JS::Value &metadataVal,
JSContext *cx)
{
if (!sandboxVal.isObject())
return NS_ERROR_INVALID_ARG;
RootedObject sandbox(cx, &sandboxVal.toObject());
sandbox = js::CheckedUnwrap(sandbox);
if (!sandbox || !xpc::IsSandbox(sandbox))
return NS_ERROR_INVALID_ARG;
RootedValue metadata(cx, metadataVal);
nsresult rv = xpc::SetSandboxMetadata(cx, sandbox, metadata);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/* JSObject import (in AUTF8String registryLocation,
* [optional] in JSObject targetObj);
*/

View File

@ -3604,6 +3604,7 @@ struct SandboxOptions {
, wantExportHelpers(false)
, proto(xpc_GetSafeJSContext())
, sameZoneAs(xpc_GetSafeJSContext())
, metadata(xpc_GetSafeJSContext())
{ }
bool wantXrays;
@ -3613,6 +3614,7 @@ struct SandboxOptions {
nsCString sandboxName;
JS::RootedObject sameZoneAs;
DOMConstructors DOMConstructors;
JS::RootedValue metadata;
};
JSObject *
@ -3647,8 +3649,19 @@ EvalInSandbox(JSContext *cx, JS::HandleObject sandbox, const nsAString& source,
JSVersion jsVersion, bool returnStringOnly,
JS::MutableHandleValue rval);
// Helper for retrieving metadata stored in a reserved slot. The metadata
// is set during the sandbox creation using the "metadata" option.
nsresult
GetSandboxMetadata(JSContext *cx, JS::HandleObject sandboxArg,
JS::MutableHandleValue rval);
nsresult
SetSandboxMetadata(JSContext *cx, JS::HandleObject sandboxArg,
JS::HandleValue metadata);
} /* namespace xpc */
/***************************************************************************/
// Inlined utilities.

View File

@ -0,0 +1,45 @@
/* 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/. */
/* See https://bugzilla.mozilla.org/show_bug.cgi?id=898559 */
function run_test()
{
let sandbox = Components.utils.Sandbox("http://www.blah.com", {
metadata: "test metadata"
});
do_check_eq(Components.utils.getSandboxMetadata(sandbox), "test metadata");
let sandbox = Components.utils.Sandbox("http://www.blah.com", {
metadata: { foopy: { bar: 2 }, baz: "hi" }
});
let metadata = Components.utils.getSandboxMetadata(sandbox);
do_check_eq(metadata.baz, "hi");
do_check_eq(metadata.foopy.bar, 2);
metadata.baz = "foo";
metadata = Components.utils.getSandboxMetadata(sandbox);
do_check_eq(metadata.baz, "foo");
metadata = { foo: "bar" };
Components.utils.setSandboxMetadata(sandbox, metadata);
metadata.foo = "baz";
metadata = Components.utils.getSandboxMetadata(sandbox);
do_check_eq(metadata.foo, "bar");
let thrown = false;
let reflector = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Components.interfaces.nsIXMLHttpRequest);
try {
Components.utils.setSandboxMetadata(sandbox, { foo: reflector });
} catch(e) {
thrown = true;
}
do_check_eq(thrown, true);
}

View File

@ -51,6 +51,7 @@ fail-if = os == "android"
[test_allowedDomains.js]
[test_allowedDomainsXHR.js]
[test_nuke_sandbox.js]
[test_sandbox_metadata.js]
[test_exportFunction.js]
[test_textDecoder.js]
[test_watchdog_enable.js]

View File

@ -955,13 +955,12 @@ public class GeckoAppShell
@GeneratableAndroidBridgeTarget(stubName = "GetMimeTypeFromExtensionsWrapper")
static String getMimeTypeFromExtensions(String aFileExt) {
MimeTypeMap mtm = MimeTypeMap.getSingleton();
StringTokenizer st = new StringTokenizer(aFileExt, ".,; ");
String type = null;
String subType = null;
while (st.hasMoreElements()) {
String ext = st.nextToken();
String mt = mtm.getMimeTypeFromExtension(ext);
String mt = getMimeTypeFromExtension(ext);
if (mt == null)
continue;
int slash = mt.indexOf('/');
@ -1746,6 +1745,14 @@ public class GeckoAppShell
@GeneratableAndroidBridgeTarget
public static void scanMedia(String aFile, String aMimeType) {
// If the platform didn't give us a mimetype, try to guess one from the filename
if (TextUtils.isEmpty(aMimeType)) {
int extPosition = aFile.lastIndexOf(".");
if (extPosition > 0 && extPosition < aFile.length() - 1) {
aMimeType = getMimeTypeFromExtension(aFile.substring(extPosition+1));
}
}
Context context = getContext();
GeckoMediaScannerClient.startScan(context, aFile, aMimeType);
}
@ -1781,10 +1788,14 @@ public class GeckoAppShell
}
}
private static String getMimeTypeFromExtension(String ext) {
final MimeTypeMap mtm = MimeTypeMap.getSingleton();
return mtm.getMimeTypeFromExtension(ext);
}
private static Drawable getDrawableForExtension(PackageManager pm, String aExt) {
Intent intent = new Intent(Intent.ACTION_VIEW);
MimeTypeMap mtm = MimeTypeMap.getSingleton();
String mimeType = mtm.getMimeTypeFromExtension(aExt);
final String mimeType = getMimeTypeFromExtension(aExt);
if (mimeType != null && mimeType.length() > 0)
intent.setType(mimeType);
else

View File

@ -68,7 +68,7 @@ public final class GeckoProfile {
}
GeckoProfile guest = GeckoProfile.getGuestProfile(context);
// if the guest profile exists and is locked, return it
// if the guest profile is locked, return it
if (guest != null && guest.locked()) {
return guest;
}
@ -231,14 +231,21 @@ public final class GeckoProfile {
return mLocked == LockState.LOCKED;
}
File lockFile = new File(getDir(), LOCK_FILE_NAME);
boolean res = lockFile.exists();
mLocked = res ? LockState.LOCKED : LockState.UNLOCKED;
return res;
// Don't use getDir() as it will create a dir if none exists
if (mDir != null && mDir.exists()) {
File lockFile = new File(mDir, LOCK_FILE_NAME);
boolean res = lockFile.exists();
mLocked = res ? LockState.LOCKED : LockState.UNLOCKED;
} else {
mLocked = LockState.UNLOCKED;
}
return mLocked == LockState.LOCKED;
}
public boolean lock() {
try {
// If this dir doesn't exist getDir will create it for us
File lockFile = new File(getDir(), LOCK_FILE_NAME);
boolean result = lockFile.createNewFile();
if (result) {
@ -255,8 +262,13 @@ public final class GeckoProfile {
}
public boolean unlock() {
// Don't use getDir() as it will create a dir
if (mDir == null || !mDir.exists()) {
return true;
}
try {
File lockFile = new File(getDir(), LOCK_FILE_NAME);
File lockFile = new File(mDir, LOCK_FILE_NAME);
boolean result = delete(lockFile);
if (result) {
mLocked = LockState.UNLOCKED;

View File

@ -689,7 +689,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
Log.d(LOG_TAG, "Ignoring search without location.");
return;
}
recordSearch(message.getString("identifier"), message.getString("location"));
recordSearch(message.optString("identifier", null), message.getString("location"));
return;
}
} catch (Exception e) {

View File

@ -479,7 +479,7 @@ pref("devtools.debugger.force-local", true);
// Display a prompt when a new connection starts to accept/reject it
pref("devtools.debugger.prompt-connection", true);
// Temporary setting to enable webapps actors
pref("devtools.debugger.enable-content-actors", false);
pref("devtools.debugger.enable-content-actors", true);
// view source
pref("view_source.syntax_highlight", true);

View File

@ -0,0 +1,52 @@
/* -*- Mode: Javasript; indent-tab-mode: nil; js-indent-level: 2 -*- */
/* 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;
let gDashboard = Cc['@mozilla.org/network/dashboard;1']
.getService(Ci.nsIDashboard);
const RESOLVE_DISABLE_IPV6 = (1 << 5);
add_test(function test_http() {
gDashboard.requestHttpConnections(function(data) {
do_check_neq(data.host.indexOf("example.com"), -1);
run_next_test();
});
});
add_test(function test_dns() {
gDashboard.requestDNSInfo(function(data) {
do_check_neq(data.hostname.indexOf("example.com"), -1);
run_next_test();
});
});
add_test(function test_sockets() {
let dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
let record = dns.resolve("example.com", RESOLVE_DISABLE_IPV6);
let answer = record.getNextAddrAsString();
gDashboard.requestSockets(function(data) {
do_check_neq(data.host.indexOf(answer), -1);
run_next_test();
});
});
function run_test() {
let ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
let uri = ioService.newURI("http://example.com", null, null);
let channel = ioService.newChannelFromURI(uri);
channel.open();
run_next_test();
}

View File

@ -246,3 +246,4 @@ skip-if = os == "android"
[test_tldservice_nextsubdomain.js]
[test_about_protocol.js]
[test_bug856978.js]
[test_about_networking.js]

View File

@ -816,6 +816,10 @@ Download.prototype = {
serializable.error = { message: this.error.message };
}
if (this.startTime) {
serializable.startTime = this.startTime.toJSON();
}
// These are serialized unless they are false, null, or empty strings.
for (let property of kSerializableDownloadProperties) {
if (property != "error" && this[property]) {
@ -853,7 +857,6 @@ const kSerializableDownloadProperties = [
"succeeded",
"canceled",
"error",
"startTime",
"totalBytes",
"hasPartialData",
"tryToKeepPartialData",
@ -901,6 +904,13 @@ Download.fromSerializable = function (aSerializable) {
}
download.saver.download = download;
if ("startTime" in aSerializable) {
let time = aSerializable.startTime.getTime
? aSerializable.startTime.getTime()
: aSerializable.startTime;
download.startTime = new Date(time);
}
for (let property of kSerializableDownloadProperties) {
if (property in aSerializable) {
download[property] = aSerializable[property];

View File

@ -106,7 +106,7 @@ this.DownloadImport.prototype = {
let autoResume = false;
try {
autoResume = row.getResultByName("autoResume");
autoResume = (row.getResultByName("autoResume") == 1);
} catch (ex) {
// autoResume wasn't present in schema version 7
}
@ -153,7 +153,7 @@ this.DownloadImport.prototype = {
type: "copy",
entityID: entityID
},
startTime: startTime,
startTime: new Date(startTime / 1000),
totalBytes: maxBytes,
hasPartialData: !!tempPath,
tryToKeepPartialData: true,

View File

@ -65,31 +65,6 @@ function promiseDownloadMidway(aDownload) {
return deferred.promise;
}
/**
* Waits for a download to finish, in case it has not finished already.
*
* @param aDownload
* The Download object to wait upon.
*
* @return {Promise}
* @resolves When the download has finished successfully.
* @rejects JavaScript exception if the download failed.
*/
function promiseDownloadStopped(aDownload) {
if (!aDownload.stopped) {
// The download is in progress, wait for the current attempt to finish and
// report any errors that may occur.
return aDownload.start();
}
if (aDownload.succeeded) {
return Promise.resolve();
}
// The download failed or was canceled.
return Promise.reject(aDownload.error || new Error("Download canceled."));
}
/**
* Creates and starts a new download, configured to keep partial data, and
* returns only when the first part of "interruptible_resumable.txt" has been
@ -1624,6 +1599,25 @@ add_task(function test_contentType() {
do_check_eq("text/plain", download.contentType);
});
/**
* Tests that the serialization/deserialization of the startTime Date
* object works correctly.
*/
add_task(function test_toSerializable_startTime()
{
let download1 = yield promiseStartDownload(httpUrl("source.txt"));
yield promiseDownloadStopped(download1);
let serializable = download1.toSerializable();
let reserialized = JSON.parse(JSON.stringify(serializable));
let download2 = yield Downloads.createDownload(reserialized);
do_check_eq(download1.startTime.constructor.name, "Date");
do_check_eq(download2.startTime.constructor.name, "Date");
do_check_eq(download1.startTime.toJSON(), download2.startTime.toJSON());
});
/**
* This test will call the platform specific operations within
* DownloadPlatform::DownloadDone. While there is no test to verify the

View File

@ -464,6 +464,31 @@ function promiseStartExternalHelperAppServiceDownload(aSourceUrl) {
return deferred.promise;
}
/**
* Waits for a download to finish, in case it has not finished already.
*
* @param aDownload
* The Download object to wait upon.
*
* @return {Promise}
* @resolves When the download has finished successfully.
* @rejects JavaScript exception if the download failed.
*/
function promiseDownloadStopped(aDownload) {
if (!aDownload.stopped) {
// The download is in progress, wait for the current attempt to finish and
// report any errors that may occur.
return aDownload.start();
}
if (aDownload.succeeded) {
return Promise.resolve();
}
// The download failed or was canceled.
return Promise.reject(aDownload.error || new Error("Download canceled."));
}
/**
* Returns a new public DownloadList object.
*

View File

@ -0,0 +1,701 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the DownloadImport object.
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// Globals
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
"resource://gre/modules/DownloadImport.jsm");
// Importable states
const DOWNLOAD_NOTSTARTED = -1;
const DOWNLOAD_DOWNLOADING = 0;
const DOWNLOAD_PAUSED = 4;
const DOWNLOAD_QUEUED = 5;
// Non importable states
const DOWNLOAD_FAILED = 2;
const DOWNLOAD_CANCELED = 3;
const DOWNLOAD_BLOCKED_PARENTAL = 6;
const DOWNLOAD_SCANNING = 7;
const DOWNLOAD_DIRTY = 8;
const DOWNLOAD_BLOCKED_POLICY = 9;
// The TEST_DATA_TAINTED const is a version of TEST_DATA_SHORT in which the
// beginning of the data was changed (with the TEST_DATA_REPLACEMENT value).
// We use this to test that the entityID is properly imported and the download
// can be resumed from where it was paused.
// For simplification purposes, the test requires that TEST_DATA_SHORT and
// TEST_DATA_TAINTED have the same length.
const TEST_DATA_REPLACEMENT = "-changed- ";
const TEST_DATA_TAINTED = TEST_DATA_REPLACEMENT +
TEST_DATA_SHORT.substr(TEST_DATA_REPLACEMENT.length);
const TEST_DATA_LENGTH = TEST_DATA_SHORT.length;
// The length of the partial file that we'll write to disk as an existing
// ongoing download.
const TEST_DATA_PARTIAL_LENGTH = TEST_DATA_REPLACEMENT.length;
// The value of the "maxBytes" column stored in the DB about the downloads.
// It's intentionally different than TEST_DATA_LENGTH to test that each value
// is seen when expected.
const MAXBYTES_IN_DB = TEST_DATA_LENGTH - 10;
let gDownloadsRowToImport;
let gDownloadsRowNonImportable;
/**
* Creates a database with an empty moz_downloads table and leaves an
* open connection to it.
*
* @param aPath
* String containing the path of the database file to be created.
* @param aSchemaVersion
* Number with the version of the database schema to set.
*
* @return {Promise}
* @resolves The open connection to the database.
* @rejects If an error occurred during the database creation.
*/
function promiseEmptyDatabaseConnection({aPath, aSchemaVersion}) {
return Task.spawn(function () {
let connection = yield Sqlite.openConnection({ path: aPath });
yield connection.execute("CREATE TABLE moz_downloads ("
+ "id INTEGER PRIMARY KEY,"
+ "name TEXT,"
+ "source TEXT,"
+ "target TEXT,"
+ "tempPath TEXT,"
+ "startTime INTEGER,"
+ "endTime INTEGER,"
+ "state INTEGER,"
+ "referrer TEXT,"
+ "entityID TEXT,"
+ "currBytes INTEGER NOT NULL DEFAULT 0,"
+ "maxBytes INTEGER NOT NULL DEFAULT -1,"
+ "mimeType TEXT,"
+ "preferredApplication TEXT,"
+ "preferredAction INTEGER NOT NULL DEFAULT 0,"
+ "autoResume INTEGER NOT NULL DEFAULT 0,"
+ "guid TEXT)");
yield connection.setSchemaVersion(aSchemaVersion);
throw new Task.Result(connection);
});
}
/**
* Inserts a new entry in the database with the given columns' values.
*
* @param aConnection
* The database connection.
* @param aDownloadRow
* An object representing the values for each column of the row
* being inserted.
*
* @return {Promise}
* @resolves When the operation completes.
* @rejects If there's an error inserting the row.
*/
function promiseInsertRow(aConnection, aDownloadRow) {
// We can't use the aDownloadRow obj directly in the execute statement
// because the obj bind code in Sqlite.jsm doesn't allow objects
// with extra properties beyond those being binded. So we might as well
// use an array as it is simpler.
let values = [
aDownloadRow.source, aDownloadRow.target, aDownloadRow.tempPath,
aDownloadRow.startTime.getTime() * 1000, aDownloadRow.state,
aDownloadRow.referrer, aDownloadRow.entityID, aDownloadRow.maxBytes,
aDownloadRow.mimeType, aDownloadRow.preferredApplication,
aDownloadRow.preferredAction, aDownloadRow.autoResume
];
return aConnection.execute("INSERT INTO moz_downloads ("
+ "name, source, target, tempPath, startTime,"
+ "endTime, state, referrer, entityID, currBytes,"
+ "maxBytes, mimeType, preferredApplication,"
+ "preferredAction, autoResume, guid)"
+ "VALUES ("
+ "'', ?, ?, ?, ?, " //name,
+ "0, ?, ?, ?, 0, " //endTime, currBytes
+ " ?, ?, ?, " //
+ " ?, ?, '')", //and guid are not imported
values);
}
/**
* Retrieves the number of rows in the moz_downloads table of the
* database.
*
* @param aConnection
* The database connection.
*
* @return {Promise}
* @resolves With the number of rows.
* @rejects Never.
*/
function promiseTableCount(aConnection) {
return aConnection.execute("SELECT COUNT(*) FROM moz_downloads")
.then(res => res[0].getResultByName("COUNT(*)"))
.then(null, Cu.reportError);
}
/**
* Briefly opens a network channel to a given URL to retrieve
* the entityID of this url, as generated by the network code.
*
* @param aUrl
* The URL to retrieve the entityID.
*
* @return {Promise}
* @resolves The EntityID of the given URL.
* @rejects When there's a problem accessing the URL.
*/
function promiseEntityID(aUrl) {
let deferred = Promise.defer();
let entityID = "";
let channel = NetUtil.newChannel(NetUtil.newURI(aUrl));
channel.asyncOpen({
onStartRequest: function (aRequest) {
if (aRequest instanceof Ci.nsIResumableChannel) {
entityID = aRequest.entityID;
}
aRequest.cancel(Cr.NS_BINDING_ABORTED);
},
onStopRequest: function (aRequest, aContext, aStatusCode) {
if (aStatusCode == Cr.NS_BINDING_ABORTED) {
deferred.resolve(entityID);
} else {
deferred.reject("Unexpected status code received");
}
},
onDataAvailable: function () {}
}, null);
return deferred.promise;
}
/**
* Gets a file path to a temporary writeable download target, in the
* correct format as expected to be stored in the downloads database,
* which is file:///absolute/path/to/file
*
* @param aLeafName
* A hint leaf name for the file.
*
* @return String The path to the download target.
*/
function getDownloadTarget(aLeafName) {
return NetUtil.newURI(getTempFile(aLeafName)).spec;
}
/**
* Generates a temporary partial file to use as an in-progress
* download. The file is written to disk with a part of the total expected
* download content pre-written.
*
* @param aLeafName
* A hint leaf name for the file.
* @param aTainted
* A boolean value. When true, the partial content of the file
* will be different from the expected content of the original source
* file. See the declaration of TEST_DATA_TAINTED for more information.
*
* @return {Promise}
* @resolves When the operation completes, and returns a string with the path
* to the generated file.
* @rejects If there's an error writing the file.
*/
function getPartialFile(aLeafName, aTainted = false) {
let tempDownload = getTempFile(aLeafName);
let partialContent = aTainted
? TEST_DATA_TAINTED.substr(0, TEST_DATA_PARTIAL_LENGTH)
: TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH);
return OS.File.writeAtomic(tempDownload.path,
partialContent,
{ bytes: TEST_DATA_PARTIAL_LENGTH })
.then(() => tempDownload.path);
}
/**
* Generates a Date object to be used as the startTime for the download rows
* in the DB. A date that is obviously different from the current time is
* generated to make sure this stored data and a `new Date()` can't collide.
*
* @param aOffset
* A offset from the base generated date is used to differentiate each
* row in the database.
*
* @return A Date object.
*/
function getStartTime(aOffset) {
return new Date(1000000 + (aOffset * 10000));
}
/**
* Performs various checks on an imported Download object to make sure
* all properties are properly set as expected from the import procedure.
*
* @param aDownload
* The Download object to be checked.
* @param aDownloadRow
* An object that represents a row from the original database table,
* with extra properties describing expected values that are not
* explictly part of the database.
*
* @return {Promise}
* @resolves When the operation completes
* @rejects Never
*/
function checkDownload(aDownload, aDownloadRow) {
return Task.spawn(function() {
do_check_eq(aDownload.source.url, aDownloadRow.source);
do_check_eq(aDownload.source.referrer, aDownloadRow.referrer);
do_check_eq(aDownload.target.path,
NetUtil.newURI(aDownloadRow.target)
.QueryInterface(Ci.nsIFileURL).file.path);
do_check_eq(aDownload.target.partFilePath, aDownloadRow.tempPath);
if (aDownloadRow.expectedResume) {
do_check_true(!aDownload.stopped || aDownload.succeeded);
yield promiseDownloadStopped(aDownload);
do_check_true(aDownload.succeeded);
do_check_eq(aDownload.progress, 100);
// If the download has resumed, a new startTime will be set.
// By calling toJSON we're also testing that startTime is a Date object.
do_check_neq(aDownload.startTime.toJSON(),
aDownloadRow.startTime.toJSON());
} else {
do_check_false(aDownload.succeeded);
do_check_eq(aDownload.startTime.toJSON(),
aDownloadRow.startTime.toJSON());
}
do_check_eq(aDownload.stopped, true);
let serializedSaver = aDownload.saver.toSerializable();
if (typeof(serializedSaver) == "object") {
do_check_eq(serializedSaver.type, "copy");
} else {
do_check_eq(serializedSaver, "copy");
}
if (aDownloadRow.entityID) {
do_check_eq(aDownload.saver.entityID, aDownloadRow.entityID);
}
do_check_eq(aDownload.currentBytes, aDownloadRow.expectedCurrentBytes);
do_check_eq(aDownload.totalBytes, aDownloadRow.expectedTotalBytes);
if (aDownloadRow.expectedContent) {
let fileToCheck = aDownloadRow.expectedResume
? aDownload.target.path
: aDownload.target.partFilePath;
yield promiseVerifyContents(fileToCheck, aDownloadRow.expectedContent);
}
do_check_eq(aDownload.contentType, aDownloadRow.expectedContentType);
do_check_eq(aDownload.launcherPath, aDownloadRow.preferredApplication);
do_check_eq(aDownload.launchWhenSucceeded,
aDownloadRow.preferredAction != Ci.nsIMIMEInfo.saveToDisk);
});
}
////////////////////////////////////////////////////////////////////////////////
//// Preparation tasks
/**
* Prepares the list of downloads to be added to the database that should
* be imported by the import procedure.
*/
add_task(function prepareDownloadsToImport() {
let sourceUrl = httpUrl("source.txt");
let sourceEntityId = yield promiseEntityID(sourceUrl);
gDownloadsRowToImport = [
// Paused download with autoResume and a partial file. By
// setting the correct entityID the download can resume from
// where it stopped, and to test that this works properly we
// intentionally set different data in the beginning of the
// partial file to make sure it was not replaced.
{
source: sourceUrl,
target: getDownloadTarget("inprogress1.txt"),
tempPath: yield getPartialFile("inprogress1.txt.part", true),
startTime: getStartTime(1),
state: DOWNLOAD_PAUSED,
referrer: httpUrl("referrer1"),
entityID: sourceEntityId,
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType1",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication1",
autoResume: 1,
// Even though the information stored in the DB said
// maxBytes was MAXBYTES_IN_DB, the download turned out to be
// a different length. Here we make sure the totalBytes property
// was correctly set with the actual value. The same consideration
// applies to the contentType.
expectedCurrentBytes: TEST_DATA_LENGTH,
expectedTotalBytes: TEST_DATA_LENGTH,
expectedResume: true,
expectedContentType: "text/plain",
expectedContent: TEST_DATA_TAINTED,
},
// Paused download with autoResume and a partial file,
// but missing entityID. This means that the download will
// start from beginning, and the entire original content of the
// source file should replace the different data that was stored
// in the partial file.
{
source: sourceUrl,
target: getDownloadTarget("inprogress2.txt"),
tempPath: yield getPartialFile("inprogress2.txt.part", true),
startTime: getStartTime(2),
state: DOWNLOAD_PAUSED,
referrer: httpUrl("referrer2"),
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType2",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication2",
autoResume: 1,
expectedCurrentBytes: TEST_DATA_LENGTH,
expectedTotalBytes: TEST_DATA_LENGTH,
expectedResume: true,
expectedContentType: "text/plain",
expectedContent: TEST_DATA_SHORT
},
// Paused download with no autoResume and a partial file.
{
source: sourceUrl,
target: getDownloadTarget("inprogress3.txt"),
tempPath: yield getPartialFile("inprogress3.txt.part"),
startTime: getStartTime(3),
state: DOWNLOAD_PAUSED,
referrer: httpUrl("referrer3"),
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType3",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication3",
autoResume: 0,
// Since this download has not been resumed, the actual data
// about its total size and content type is not known.
// Therefore, we're going by the information imported from the DB.
expectedCurrentBytes: TEST_DATA_PARTIAL_LENGTH,
expectedTotalBytes: MAXBYTES_IN_DB,
expectedResume: false,
expectedContentType: "mimeType3",
expectedContent: TEST_DATA_SHORT.substr(0, TEST_DATA_PARTIAL_LENGTH),
},
// Paused download with autoResume and no partial file.
{
source: sourceUrl,
target: getDownloadTarget("inprogress4.txt"),
tempPath: "",
startTime: getStartTime(4),
state: DOWNLOAD_PAUSED,
referrer: httpUrl("referrer4"),
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "text/plain",
preferredAction: Ci.nsIMIMEInfo.useHelperApp,
preferredApplication: "prerredApplication4",
autoResume: 1,
expectedCurrentBytes: TEST_DATA_LENGTH,
expectedTotalBytes: TEST_DATA_LENGTH,
expectedResume: true,
expectedContentType: "text/plain",
expectedContent: TEST_DATA_SHORT
},
// Paused download with no autoResume and no partial file.
{
source: sourceUrl,
target: getDownloadTarget("inprogress5.txt"),
tempPath: "",
startTime: getStartTime(5),
state: DOWNLOAD_PAUSED,
referrer: httpUrl("referrer4"),
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "text/plain",
preferredAction: Ci.nsIMIMEInfo.useSystemDefault,
preferredApplication: "prerredApplication5",
autoResume: 0,
expectedCurrentBytes: 0,
expectedTotalBytes: MAXBYTES_IN_DB,
expectedResume: false,
expectedContentType: "text/plain",
},
// Queued download with no autoResume and no partial file.
// Even though autoResume=0, queued downloads always autoResume.
{
source: sourceUrl,
target: getDownloadTarget("inprogress6.txt"),
tempPath: "",
startTime: getStartTime(6),
state: DOWNLOAD_QUEUED,
referrer: httpUrl("referrer6"),
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "text/plain",
preferredAction: Ci.nsIMIMEInfo.useHelperApp,
preferredApplication: "prerredApplication6",
autoResume: 0,
expectedCurrentBytes: TEST_DATA_LENGTH,
expectedTotalBytes: TEST_DATA_LENGTH,
expectedResume: true,
expectedContentType: "text/plain",
expectedContent: TEST_DATA_SHORT
},
// Notstarted download with no autoResume and no partial file.
// Even though autoResume=0, notstarted downloads always autoResume.
{
source: sourceUrl,
target: getDownloadTarget("inprogress7.txt"),
tempPath: "",
startTime: getStartTime(7),
state: DOWNLOAD_NOTSTARTED,
referrer: httpUrl("referrer7"),
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "text/plain",
preferredAction: Ci.nsIMIMEInfo.useHelperApp,
preferredApplication: "prerredApplication7",
autoResume: 0,
expectedCurrentBytes: TEST_DATA_LENGTH,
expectedTotalBytes: TEST_DATA_LENGTH,
expectedResume: true,
expectedContentType: "text/plain",
expectedContent: TEST_DATA_SHORT
},
// Downloading download with no autoResume and a partial file.
// Even though autoResume=0, downloading downloads always autoResume.
{
source: sourceUrl,
target: getDownloadTarget("inprogress8.txt"),
tempPath: yield getPartialFile("inprogress8.txt.part", true),
startTime: getStartTime(8),
state: DOWNLOAD_DOWNLOADING,
referrer: httpUrl("referrer8"),
entityID: sourceEntityId,
maxBytes: MAXBYTES_IN_DB,
mimeType: "text/plain",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication8",
autoResume: 0,
expectedCurrentBytes: TEST_DATA_LENGTH,
expectedTotalBytes: TEST_DATA_LENGTH,
expectedResume: true,
expectedContentType: "text/plain",
expectedContent: TEST_DATA_TAINTED
},
];
});
/**
* Prepares the list of downloads to be added to the database that should
* *not* be imported by the import procedure.
*/
add_task(function prepareNonImportableDownloads()
{
gDownloadsRowNonImportable = [
// Download with no source (should never happen in normal circumstances).
{
source: "",
target: "nonimportable1.txt",
tempPath: "",
startTime: getStartTime(1),
state: DOWNLOAD_PAUSED,
referrer: "",
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType1",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication1",
autoResume: 1
},
// state = DOWNLOAD_FAILED
{
source: httpUrl("source.txt"),
target: "nonimportable2.txt",
tempPath: "",
startTime: getStartTime(2),
state: DOWNLOAD_FAILED,
referrer: "",
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType2",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication2",
autoResume: 1
},
// state = DOWNLOAD_CANCELED
{
source: httpUrl("source.txt"),
target: "nonimportable3.txt",
tempPath: "",
startTime: getStartTime(3),
state: DOWNLOAD_CANCELED,
referrer: "",
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType3",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication3",
autoResume: 1
},
// state = DOWNLOAD_BLOCKED_PARENTAL
{
source: httpUrl("source.txt"),
target: "nonimportable4.txt",
tempPath: "",
startTime: getStartTime(4),
state: DOWNLOAD_BLOCKED_PARENTAL,
referrer: "",
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType4",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication4",
autoResume: 1
},
// state = DOWNLOAD_SCANNING
{
source: httpUrl("source.txt"),
target: "nonimportable5.txt",
tempPath: "",
startTime: getStartTime(5),
state: DOWNLOAD_SCANNING,
referrer: "",
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType5",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication5",
autoResume: 1
},
// state = DOWNLOAD_DIRTY
{
source: httpUrl("source.txt"),
target: "nonimportable6.txt",
tempPath: "",
startTime: getStartTime(6),
state: DOWNLOAD_DIRTY,
referrer: "",
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType6",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication6",
autoResume: 1
},
// state = DOWNLOAD_BLOCKED_POLICY
{
source: httpUrl("source.txt"),
target: "nonimportable7.txt",
tempPath: "",
startTime: getStartTime(7),
state: DOWNLOAD_BLOCKED_POLICY,
referrer: "",
entityID: "",
maxBytes: MAXBYTES_IN_DB,
mimeType: "mimeType7",
preferredAction: Ci.nsIMIMEInfo.saveToDisk,
preferredApplication: "prerredApplication7",
autoResume: 1
},
];
});
////////////////////////////////////////////////////////////////////////////////
//// Test
/**
* Creates a temporary Sqlite database with download data and perform an
* import of that data to the new Downloads API to verify that the import
* worked correctly.
*/
add_task(function test_downloadImport()
{
let connection = null;
let downloadsSqlite = getTempFile("downloads.sqlite").path;
try {
// Set up the database.
connection = yield promiseEmptyDatabaseConnection({
aPath: downloadsSqlite,
aSchemaVersion: 9
});
// Insert both the importable and non-importable
// downloads together.
for (let downloadRow of gDownloadsRowToImport) {
yield promiseInsertRow(connection, downloadRow);
}
for (let downloadRow of gDownloadsRowNonImportable) {
yield promiseInsertRow(connection, downloadRow);
}
// Check that every item was inserted.
do_check_eq((yield promiseTableCount(connection)),
gDownloadsRowToImport.length +
gDownloadsRowNonImportable.length);
} finally {
// Close the connection so that DownloadImport can open it.
yield connection.close();
}
// Import items.
let list = yield promiseNewDownloadList();
yield new DownloadImport(list, downloadsSqlite).import();
let items = yield list.getAll();
do_check_eq(items.length, gDownloadsRowToImport.length);
for (let i = 0; i < gDownloadsRowToImport.length; i++) {
yield checkDownload(items[i], gDownloadsRowToImport[i]);
}
})

View File

@ -3,6 +3,7 @@ head = head.js
tail = tail.js
[test_DownloadCore.js]
[test_DownloadImport.js]
[test_DownloadIntegration.js]
[test_DownloadLegacy.js]
[test_DownloadList.js]

View File

@ -11,6 +11,7 @@ MOCHITEST_BROWSER_FILES = \
echo.sjs \
worker_xhr.js \
browser_frameworker.js \
browser_frameworker_sandbox.js \
worker_relative.js \
relative_import.js \
browser_workerAPI.js \

View File

@ -2,6 +2,9 @@
* 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/. */
// This file tests message ports and semantics of the frameworker which aren't
// directly related to the sandbox. See also browser_frameworker_sandbox.js.
function makeWorkerUrl(runner) {
let prefix = "http://example.com/browser/toolkit/components/social/test/browser/echo.sjs?";
if (typeof runner == "function") {
@ -136,263 +139,6 @@ let tests = {
worker.port.postMessage({topic: "hello", data: [1,2,3]});
},
testArrayUsingBuffer: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function(e) {
if (e.data.topic == "go") {
let buffer = new ArrayBuffer(10);
// this one has always worked in the past, but worth checking anyway...
if (new Uint8Array(buffer).length != 10) {
port.postMessage({topic: "result", reason: "first length was not 10"});
return;
}
let reader = new FileReader();
reader.onload = function(event) {
if (new Uint8Array(buffer).length != 10) {
port.postMessage({topic: "result", reason: "length in onload handler was not 10"});
return;
}
// all seems good!
port.postMessage({topic: "result", reason: "ok"});
}
let blob = new Blob([buffer], {type: "binary"});
reader.readAsArrayBuffer(blob);
}
}
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testArray");
worker.port.onmessage = function(e) {
if (e.data.topic == "result") {
is(e.data.reason, "ok", "check the array worked");
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "go"});
},
testArrayUsingReader: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function(e) {
if (e.data.topic == "go") {
let buffer = new ArrayBuffer(10);
let reader = new FileReader();
reader.onload = function(event) {
try {
if (new Uint8Array(reader.result).length != 10) {
port.postMessage({topic: "result", reason: "length in onload handler was not 10"});
return;
}
// all seems good!
port.postMessage({topic: "result", reason: "ok"});
} catch (ex) {
port.postMessage({topic: "result", reason: ex.toString()});
}
}
let blob = new Blob([buffer], {type: "binary"});
reader.readAsArrayBuffer(blob);
}
}
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testArray");
worker.port.onmessage = function(e) {
if (e.data.topic == "result") {
is(e.data.reason, "ok", "check the array worked");
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "go"});
},
testXHR: function(cbnext) {
// NOTE: this url MUST be in the same origin as worker_xhr.js fetches from!
let url = "https://example.com/browser/toolkit/components/social/test/browser/worker_xhr.js";
let worker = getFrameWorkerHandle(url, undefined, "testXHR");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check the xhr test worked");
worker.terminate();
cbnext();
}
}
},
testLocalStorage: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
try {
localStorage.setItem("foo", "1");
} catch(e) {
port.postMessage({topic: "done", result: "FAILED to set localStorage, " + e.toString() });
return;
}
var ok;
try {
ok = localStorage["foo"] == 1;
} catch (e) {
port.postMessage({topic: "done", result: "FAILED to read localStorage, " + e.toString() });
return;
}
port.postMessage({topic: "done", result: "ok"});
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testLocalStorage", null, true);
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check the localStorage test worked");
worker.terminate();
cbnext();
}
}
},
testNoLocalStorage: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
try {
localStorage.setItem("foo", "1");
} catch(e) {
port.postMessage({topic: "done", result: "ok"});
return;
}
port.postMessage({topic: "done", result: "FAILED because localStorage was exposed" });
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testNoLocalStorage");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check that retrieving localStorage fails by default");
worker.terminate();
cbnext();
}
}
},
testBase64: function (cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
var ok = false;
try {
ok = btoa("1234") == "MTIzNA==";
} catch(e) {
port.postMessage({topic: "done", result: "FAILED to call btoa, " + e.toString() });
return;
}
if (!ok) {
port.postMessage({topic: "done", result: "FAILED calling btoa"});
return;
}
try {
ok = atob("NDMyMQ==") == "4321";
} catch (e) {
port.postMessage({topic: "done", result: "FAILED to call atob, " + e.toString() });
return;
}
if (!ok) {
port.postMessage({topic: "done", result: "FAILED calling atob"});
return;
}
port.postMessage({topic: "done", result: "ok"});
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testBase64");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check the atob/btoa test worked");
worker.terminate();
cbnext();
}
}
},
testTimeouts: function (cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
var timeout;
try {
timeout = setTimeout(function () {
port.postMessage({topic: "done", result: "FAILED cancelled timeout was called"});
}, 100);
} catch (ex) {
port.postMessage({topic: "done", result: "FAILED calling setTimeout: " + ex});
return;
}
try {
clearTimeout(timeout);
} catch (ex) {
port.postMessage({topic: "done", result: "FAILED calling clearTimeout: " + ex});
return;
}
var counter = 0;
try {
timeout = setInterval(function () {
if (++counter == 2) {
clearInterval(timeout);
setTimeout(function () {
port.postMessage({topic: "done", result: "ok"});
return;
}, 0);
}
}, 100);
} catch (ex) {
port.postMessage({topic: "done", result: "FAILED calling setInterval: " + ex});
return;
}
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testTimeouts");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check that timeouts worked");
worker.terminate();
cbnext();
}
}
},
testWebSocket: function (cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
try {
var exampleSocket = new WebSocket("ws://mochi.test:8888/socketserver");
} catch (e) {
port.postMessage({topic: "done", result: "FAILED calling WebSocket constructor: " + e});
return;
}
port.postMessage({topic: "done", result: "ok"});
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testWebSocket");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check that websockets worked");
worker.terminate();
cbnext();
}
}
},
testSameOriginImport: function(cbnext) {
let run = function() {
onconnect = function(e) {
@ -422,7 +168,6 @@ let tests = {
worker.port.postMessage({topic: "ping"})
},
testRelativeImport: function(cbnext) {
let url = "https://example.com/browser/toolkit/components/social/test/browser/worker_relative.js";
let worker = getFrameWorkerHandle(url, undefined, "testSameOriginImport");
@ -633,68 +378,4 @@ let tests = {
}
worker.port.postMessage({topic: "get-ready"});
},
testEventSource: function(cbnext) {
let worker = getFrameWorkerHandle("https://example.com/browser/toolkit/components/social/test/browser/worker_eventsource.js", undefined, "testEventSource");
worker.port.onmessage = function(e) {
let m = e.data;
if (m.topic == "eventSourceTest") {
if (m.result.ok != undefined)
ok(m.result.ok, e.data.result.msg);
if (m.result.is != undefined)
is(m.result.is, m.result.match, m.result.msg);
if (m.result.info != undefined)
info(m.result.info);
} else if (e.data.topic == "pong") {
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "ping"})
},
testIndexedDB: function(cbnext) {
let worker = getFrameWorkerHandle("https://example.com/browser/toolkit/components/social/test/browser/worker_social.js", undefined, "testIndexedDB");
worker.port.onmessage = function(e) {
let m = e.data;
if (m.topic == "social.indexeddb-result") {
is(m.data.result, "ok", "created indexeddb");
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "test-indexeddb-create"})
},
testSubworker: function(cbnext) {
// the main "frameworker"...
let mainworker = function() {
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function(e) {
if (e.data.topic == "go") {
let suburl = e.data.data;
let worker = new Worker(suburl);
worker.onmessage = function(sube) {
port.postMessage({topic: "sub-message", data: sube.data});
}
}
}
}
}
// The "subworker" that is actually a real, bona-fide worker.
let subworker = function() {
postMessage("hello");
}
let worker = getFrameWorkerHandle(makeWorkerUrl(mainworker), undefined, "testSubWorker");
worker.port.onmessage = function(e) {
if (e.data.topic == "sub-message" && e.data.data == "hello") {
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "go", data: makeWorkerUrl(subworker)});
}
}

View File

@ -0,0 +1,347 @@
/* 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/. */
// This file tests features made available to the frameworker via the sandbox.
// For other frameworker tests, see browser_frameworker.js
function makeWorkerUrl(runner) {
let prefix = "http://example.com/browser/toolkit/components/social/test/browser/echo.sjs?";
if (typeof runner == "function") {
runner = "var run=" + runner.toSource() + ";run();";
}
return prefix + encodeURI(runner);
}
var getFrameWorkerHandle;
function test() {
waitForExplicitFinish();
let scope = {};
Cu.import("resource://gre/modules/FrameWorker.jsm", scope);
getFrameWorkerHandle = scope.getFrameWorkerHandle;
runTests(tests);
}
let tests = {
testArrayUsingBuffer: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function(e) {
if (e.data.topic == "go") {
let buffer = new ArrayBuffer(10);
// this one has always worked in the past, but worth checking anyway...
if (new Uint8Array(buffer).length != 10) {
port.postMessage({topic: "result", reason: "first length was not 10"});
return;
}
let reader = new FileReader();
reader.onload = function(event) {
if (new Uint8Array(buffer).length != 10) {
port.postMessage({topic: "result", reason: "length in onload handler was not 10"});
return;
}
// all seems good!
port.postMessage({topic: "result", reason: "ok"});
}
let blob = new Blob([buffer], {type: "binary"});
reader.readAsArrayBuffer(blob);
}
}
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testArray");
worker.port.onmessage = function(e) {
if (e.data.topic == "result") {
is(e.data.reason, "ok", "check the array worked");
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "go"});
},
testArrayUsingReader: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function(e) {
if (e.data.topic == "go") {
let buffer = new ArrayBuffer(10);
let reader = new FileReader();
reader.onload = function(event) {
try {
if (new Uint8Array(reader.result).length != 10) {
port.postMessage({topic: "result", reason: "length in onload handler was not 10"});
return;
}
// all seems good!
port.postMessage({topic: "result", reason: "ok"});
} catch (ex) {
port.postMessage({topic: "result", reason: ex.toString()});
}
}
let blob = new Blob([buffer], {type: "binary"});
reader.readAsArrayBuffer(blob);
}
}
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testArray");
worker.port.onmessage = function(e) {
if (e.data.topic == "result") {
is(e.data.reason, "ok", "check the array worked");
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "go"});
},
testXHR: function(cbnext) {
// NOTE: this url MUST be in the same origin as worker_xhr.js fetches from!
let url = "https://example.com/browser/toolkit/components/social/test/browser/worker_xhr.js";
let worker = getFrameWorkerHandle(url, undefined, "testXHR");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check the xhr test worked");
worker.terminate();
cbnext();
}
}
},
testLocalStorage: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
try {
localStorage.setItem("foo", "1");
} catch(e) {
port.postMessage({topic: "done", result: "FAILED to set localStorage, " + e.toString() });
return;
}
var ok;
try {
ok = localStorage["foo"] == 1;
} catch (e) {
port.postMessage({topic: "done", result: "FAILED to read localStorage, " + e.toString() });
return;
}
port.postMessage({topic: "done", result: "ok"});
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testLocalStorage", null, true);
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check the localStorage test worked");
worker.terminate();
cbnext();
}
}
},
testNoLocalStorage: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
try {
localStorage.setItem("foo", "1");
} catch(e) {
port.postMessage({topic: "done", result: "ok"});
return;
}
port.postMessage({topic: "done", result: "FAILED because localStorage was exposed" });
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testNoLocalStorage");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check that retrieving localStorage fails by default");
worker.terminate();
cbnext();
}
}
},
testBase64: function (cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
var ok = false;
try {
ok = btoa("1234") == "MTIzNA==";
} catch(e) {
port.postMessage({topic: "done", result: "FAILED to call btoa, " + e.toString() });
return;
}
if (!ok) {
port.postMessage({topic: "done", result: "FAILED calling btoa"});
return;
}
try {
ok = atob("NDMyMQ==") == "4321";
} catch (e) {
port.postMessage({topic: "done", result: "FAILED to call atob, " + e.toString() });
return;
}
if (!ok) {
port.postMessage({topic: "done", result: "FAILED calling atob"});
return;
}
port.postMessage({topic: "done", result: "ok"});
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testBase64");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check the atob/btoa test worked");
worker.terminate();
cbnext();
}
}
},
testTimeouts: function (cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
var timeout;
try {
timeout = setTimeout(function () {
port.postMessage({topic: "done", result: "FAILED cancelled timeout was called"});
}, 100);
} catch (ex) {
port.postMessage({topic: "done", result: "FAILED calling setTimeout: " + ex});
return;
}
try {
clearTimeout(timeout);
} catch (ex) {
port.postMessage({topic: "done", result: "FAILED calling clearTimeout: " + ex});
return;
}
var counter = 0;
try {
timeout = setInterval(function () {
if (++counter == 2) {
clearInterval(timeout);
setTimeout(function () {
port.postMessage({topic: "done", result: "ok"});
return;
}, 0);
}
}, 100);
} catch (ex) {
port.postMessage({topic: "done", result: "FAILED calling setInterval: " + ex});
return;
}
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testTimeouts");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check that timeouts worked");
worker.terminate();
cbnext();
}
}
},
testWebSocket: function (cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
try {
var exampleSocket = new WebSocket("ws://mochi.test:8888/socketserver");
} catch (e) {
port.postMessage({topic: "done", result: "FAILED calling WebSocket constructor: " + e});
return;
}
port.postMessage({topic: "done", result: "ok"});
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testWebSocket");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check that websockets worked");
worker.terminate();
cbnext();
}
}
},
testEventSource: function(cbnext) {
let worker = getFrameWorkerHandle("https://example.com/browser/toolkit/components/social/test/browser/worker_eventsource.js", undefined, "testEventSource");
worker.port.onmessage = function(e) {
let m = e.data;
if (m.topic == "eventSourceTest") {
if (m.result.ok != undefined)
ok(m.result.ok, e.data.result.msg);
if (m.result.is != undefined)
is(m.result.is, m.result.match, m.result.msg);
if (m.result.info != undefined)
info(m.result.info);
} else if (e.data.topic == "pong") {
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "ping"})
},
testIndexedDB: function(cbnext) {
let worker = getFrameWorkerHandle("https://example.com/browser/toolkit/components/social/test/browser/worker_social.js", undefined, "testIndexedDB");
worker.port.onmessage = function(e) {
let m = e.data;
if (m.topic == "social.indexeddb-result") {
is(m.data.result, "ok", "created indexeddb");
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "test-indexeddb-create"})
},
testSubworker: function(cbnext) {
// the main "frameworker"...
let mainworker = function() {
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function(e) {
if (e.data.topic == "go") {
let suburl = e.data.data;
let worker = new Worker(suburl);
worker.onmessage = function(sube) {
port.postMessage({topic: "sub-message", data: sube.data});
}
}
}
}
}
// The "subworker" that is actually a real, bona-fide worker.
let subworker = function() {
postMessage("hello");
}
let worker = getFrameWorkerHandle(makeWorkerUrl(mainworker), undefined, "testSubWorker");
worker.port.onmessage = function(e) {
if (e.data.topic == "sub-message" && e.data.data == "hello") {
worker.terminate();
cbnext();
}
}
worker.port.postMessage({topic: "go", data: makeWorkerUrl(subworker)});
}
}

View File

@ -363,6 +363,9 @@ nsAppStartup::Quit(uint32_t aMode)
}
if (mRestart) {
// Mark the next startup as a restart.
PR_SetEnv("MOZ_APP_RESTART=1");
/* Firefox-restarts reuse the process so regular process start-time isn't
a useful indicator of startup time anymore. */
TimeStamp::RecordProcessRestart();
@ -522,6 +525,19 @@ nsAppStartup::GetRestarting(bool *aResult)
return NS_OK;
}
NS_IMETHODIMP
nsAppStartup::GetWasRestarted(bool *aResult)
{
char *mozAppRestart = PR_GetEnv("MOZ_APP_RESTART");
/* When calling PR_SetEnv() with an empty value the existing variable may
* be unset or set to the empty string depending on the underlying platform
* thus we have to check if the variable is present and not empty. */
*aResult = mozAppRestart && (strcmp(mozAppRestart, "") != 0);
return NS_OK;
}
NS_IMETHODIMP
nsAppStartup::SetInterrupted(bool aInterrupted)
{

View File

@ -7,7 +7,7 @@
interface nsICmdLineService;
[scriptable, uuid(380618f8-479a-435b-b58e-7398ab937531)]
[scriptable, uuid(744d6ec0-115f-11e3-9c94-68fd99890b3c)]
interface nsIAppStartup : nsISupports
{
/**
@ -140,6 +140,12 @@ interface nsIAppStartup : nsISupports
*/
readonly attribute boolean restarting;
/**
* True if this is the startup following restart, i.e. if the application
* was restarted using quit(eRestart*).
*/
readonly attribute boolean wasRestarted;
/**
* Returns an object with main, process, firstPaint, sessionRestored properties.
* Properties may not be available depending on platform or application

View File

@ -362,9 +362,6 @@ Capture.prototype = {
// notify, since it calls destroy, which cancels the timeout timer and
// removes the didCapture message listener.
this.captureCallback(this);
this.destroy();
if (data && data.telemetry) {
// Telemetry is currently disabled in the content process (bug 680508).
for (let id in data.telemetry) {
@ -372,7 +369,9 @@ Capture.prototype = {
}
}
let callOnDones = function callOnDonesFn() {
let done = () => {
this.captureCallback(this);
this.destroy();
for (let callback of this.doneCallbacks) {
try {
callback.call(this.options, this.url);
@ -381,15 +380,15 @@ Capture.prototype = {
Cu.reportError(err);
}
}
}.bind(this);
};
if (!data) {
callOnDones();
done();
return;
}
PageThumbs._store(this.url, data.finalURL, data.imageData, data.wasErrorResponse)
.then(callOnDones);
.then(done, done);
},
};

View File

@ -169,3 +169,7 @@ MOCHITEST_CHROME_FILES += $(filter disabled-temporarily, test_cursorsnap.xul) \
window_cursorsnap_dialog.xul \
window_cursorsnap_wizard.xul
endif
MOCHITEST_CHROME_FILES += test_about_networking.html \
file_about_networking_wsh.py

View File

@ -0,0 +1,9 @@
from mod_pywebsocket import msgutil
def web_socket_do_extra_handshake(request):
pass
def web_socket_transfer_data(request):
while not request.client_terminated:
msgutil.receive_message(request)

View File

@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=912103
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
function runTest() {
const Cc = Components.classes;
const Ci = Components.interfaces;
var dashboard = Cc['@mozilla.org/network/dashboard;1']
.getService(Ci.nsIDashboard);
dashboard.enableLogging = true;
var wsURI = "ws://mochi.test:8888/chrome/toolkit/content/tests/chrome/file_about_networking";
var websocket = new WebSocket(wsURI);
websocket.addEventListener("open", function() {
dashboard.requestWebsocketConnections(function(data) {
isnot(data.hostport.indexOf("mochi.test:8888"), -1,
"tested websocket entry not found");
websocket.close();
SimpleTest.finish();
});
});
}
window.addEventListener("DOMContentLoaded", function run() {
window.removeEventListener("DOMContentLoaded", run);
runTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912103">Mozilla Bug </a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -55,6 +55,7 @@ var BuiltinProvider = {
"devtools": "resource:///modules/devtools",
"devtools/server": "resource://gre/modules/devtools/server",
"devtools/toolkit/webconsole": "resource://gre/modules/devtools/toolkit/webconsole",
"devtools/app-actor-front": "resource://gre/modules/devtools/app-actor-front.js",
"devtools/styleinspector/css-logic": "resource://gre/modules/devtools/styleinspector/css-logic",
"devtools/client": "resource://gre/modules/devtools/client",
@ -95,6 +96,7 @@ var SrcdirProvider = {
let toolkitURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools"));
let serverURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "server"));
let webconsoleURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "webconsole"));
let appActorURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "apps", "app-actor-front.js"));
let cssLogicURI = this.fileURI(OS.Path.join(toolkitURI, "styleinspector", "css-logic"));
let clientURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "client"));
let mainURI = this.fileURI(OS.Path.join(srcdir, "browser", "devtools", "main.js"));
@ -107,6 +109,7 @@ var SrcdirProvider = {
"": "resource://gre/modules/commonjs/",
"devtools/server": serverURI,
"devtools/toolkit/webconsole": webconsoleURI,
"devtools/app-actor-front": appActorURI,
"devtools/client": clientURI,
"devtools": devtoolsURI,
"devtools/styleinspector/css-logic": cssLogicURI,

View File

@ -0,0 +1,33 @@
/* 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/. */
"use strict";
Components.utils.import("resource:///modules/devtools/shared/event-emitter.js");
const EXPORTED_SYMBOLS = ["Simulator"];
const Simulator = {
_simulators: {},
register: function (version, simulator) {
this._simulators[version] = simulator;
this.emit("register");
},
unregister: function (version) {
delete this._simulators[version];
this.emit("unregister");
},
availableVersions: function () {
return Object.keys(this._simulators).sort();
},
getByVersion: function (version) {
return this._simulators[version];
}
};
EventEmitter.decorate(Simulator);

View File

@ -0,0 +1,216 @@
const {Ci, Cc, Cu, Cr} = require("chrome");
Cu.import("resource://gre/modules/osfile.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
const promise = require("sdk/core/promise");
// XXX: bug 912476 make this module a real protocol.js front
// by converting webapps actor to protocol.js
const PR_USEC_PER_MSEC = 1000;
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_TRUNCATE = 0x20;
const CHUNK_SIZE = 10000;
function addDirToZip(writer, dir, basePath) {
let files = dir.directoryEntries;
while (files.hasMoreElements()) {
let file = files.getNext().QueryInterface(Ci.nsIFile);
if (file.isHidden() ||
file.isSymlink() ||
file.isSpecial() ||
file.equals(writer.file))
{
continue;
}
if (file.isDirectory()) {
writer.addEntryDirectory(basePath + file.leafName + "/",
file.lastModifiedTime * PR_USEC_PER_MSEC,
true);
addDirToZip(writer, file, basePath + file.leafName + "/");
} else {
writer.addEntryFile(basePath + file.leafName,
Ci.nsIZipWriter.COMPRESSION_DEFAULT,
file,
true);
}
}
}
/**
* Convert an XPConnect result code to its name and message.
* We have to extract them from an exception per bug 637307 comment 5.
*/
function getResultTest(code) {
let regexp =
/^\[Exception... "(.*)" nsresult: "0x[0-9a-fA-F]* \((.*)\)" location: ".*" data: .*\]$/;
let ex = Cc["@mozilla.org/js/xpc/Exception;1"].
createInstance(Ci.nsIXPCException);
ex.initialize(null, code, null, null, null, null);
let [, message, name] = regexp.exec(ex.toString());
return { name: name, message: message };
}
function zipDirectory(zipFile, dirToArchive) {
let deferred = promise.defer();
let writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
writer.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
this.addDirToZip(writer, dirToArchive, "");
writer.processQueue({
onStartRequest: function onStartRequest(request, context) {},
onStopRequest: (request, context, status) => {
if (status == Cr.NS_OK) {
writer.close();
deferred.resolve(zipFile);
}
else {
let { name, message } = getResultText(status);
deferred.reject(name + ": " + message);
}
}
}, null);
return deferred.promise;
}
function uploadPackage(client, webappsActor, packageFile) {
let deferred = promise.defer();
let request = {
to: webappsActor,
type: "uploadPackage"
};
client.request(request, (res) => {
openFile(res.actor);
});
function openFile(actor) {
OS.File.open(packageFile.path)
.then(function (file) {
uploadChunk(actor, file);
});
}
function uploadChunk(actor, file) {
file.read(CHUNK_SIZE)
.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 chunk = String.fromCharCode.apply(null, bytes);
let request = {
to: actor,
type: "chunk",
chunk: chunk
};
client.request(request, (res) => {
if (bytes.length == CHUNK_SIZE) {
uploadChunk(actor, file);
} else {
file.close().then(function () {
endsUpload(actor);
});
}
});
});
}
function endsUpload(actor) {
let request = {
to: actor,
type: "done"
};
client.request(request, (res) => {
deferred.resolve(actor);
});
}
return deferred.promise;
}
function removeServerTemporaryFile(client, fileActor) {
let request = {
to: fileActor,
type: "remove"
};
client.request(request, function (aResponse) {
console.error("Failed removing server side temporary package file", aResponse);
});
}
function installPackaged(client, webappsActor, packagePath, appId) {
let deferred = promise.defer();
let file = FileUtils.File(packagePath);
let packagePromise;
if (file.isDirectory()) {
let tmpZipFile = FileUtils.getDir("TmpD", [], true);
tmpZipFile.append("application.zip");
tmpZipFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
packagePromise = zipDirectory(tmpZipFile, file)
} else {
packagePromise = promise.resolve(file);
}
packagePromise.then((zipFile) => {
uploadPackage(client, webappsActor, zipFile)
.then((fileActor) => {
let request = {
to: webappsActor,
type: "install",
appId: appId,
upload: fileActor
};
client.request(request, (res) => {
// If the install method immediatly fails,
// reject immediatly the installPackaged promise.
// Otherwise, wait for webappsEvent for completion
if (res.error) {
deferred.reject(res);
}
});
client.addOneTimeListener("webappsEvent", function (aState, aType, aPacket) {
if ("error" in aType)
deferred.reject({error: aType.error, message: aType.message});
else
deferred.resolve({appId: aType.appId});
});
// Ensure deleting the temporary package file, but only if that a temporary
// package created when we pass a directory as `packagePath`
if (zipFile != file)
zipFile.remove(false);
// In case of success or error, ensure deleting the temporary package file
// also created on the device, but only once install request is done
deferred.promise.then(removeServerTemporaryFile, removeServerTemporaryFile);
});
});
return deferred.promise;
}
exports.installPackaged = installPackaged;
function installHosted(client, webappsActor, appId, metadata, manifest) {
let deferred = promise.defer();
let request = {
to: webappsActor,
type: "install",
appId: appId,
metadata: metadata,
manifest: manifest
};
client.request(request, (res) => {
if (res.error) {
deferred.reject(res);
}
});
client.addOneTimeListener("webappsEvent", function (aState, aType, aPacket) {
if ("error" in aType)
deferred.reject({error: aType.error, message: aType.message});
else
deferred.resolve({appId: aType.appId});
});
return deferred.promise;
}
exports.installHosted = installHosted;

View File

@ -5,3 +5,9 @@
TEST_DIRS += ['tests']
JS_MODULES_PATH = 'modules/devtools'
EXTRA_JS_MODULES += [
'Simulator.jsm',
'app-actor-front.js'
]

View File

@ -98,12 +98,13 @@ function do_get_webappsdir() {
var webappsDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
webappsDir.append("test_webapps");
if (!webappsDir.exists())
webappsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
webappsDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("755", 8));
var coreAppsDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
coreAppsDir.append("test_coreapps");
if (!coreAppsDir.exists())
coreAppsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
coreAppsDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("755", 8));
var tmpDir = Services.dirsvc.get("TmpD", Ci.nsILocalFile);
// Register our own provider for the profile directory.
// It will return our special docshell profile directory.
@ -116,6 +117,7 @@ function do_get_webappsdir() {
else if (prop == "coreAppsDir") {
return coreAppsDir.clone();
}
return tmpDir.clone();
throw Cr.NS_ERROR_FAILURE;
},
QueryInterface: function(iid) {

View File

@ -2,6 +2,9 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/osfile.jsm");
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {require} = devtools;
const {installHosted, installPackaged} = require("devtools/app-actor-front");
let gAppId = "actor-test";
const APP_ORIGIN = "app://" + gAppId;
@ -177,105 +180,35 @@ 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) {
let packageFile = do_get_file("data/app.zip");
installPackaged(gClient, gActor, packageFile.path, gAppId)
.then(function ({ appId }) {
do_check_eq(appId, gAppId);
run_next_test();
}, function (e) {
do_throw("Failed install uploaded packaged app: " + e.error + ": " + e.message);
});
}
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"
}
let 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);
let manifest = {
name: "My hosted app"
};
installHosted(gClient, gActor, gAppId, metadata, manifest).then(
function ({ appId }) {
do_check_eq(appId, gAppId);
run_next_test();
},
function (e) {
do_throw("Failed installing hosted app: " + e.error + ": " + e.message);
}
if ("message" in aType) {
do_print("Error message: " + aType.message);
}
do_check_eq("error" in aType, false);
run_next_test();
});
);
});
add_test(function testCheckHostedApp() {

Some files were not shown because too many files have changed in this diff Show More