mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 05:37:11 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1182537 - Use channel->ascynOpen2 in dom/security/nsCORSListenerProxy (r=sicking) (5c4b779a12) - Bug 1155758 - Make about:serviceworkers work in B2G. r=fabrice (195eca3894) - Bug 1162920 - JavaScript error at aboutServiceWorkers.js when updating the service worker. r=fabrice (2d3a831a8c) - Bug 1155153 - [e10s] about:serviceworkers should work in e10s mode. Update B2G implementation. r=baku (0d1c2999c1) - Bug 1171915 - about:serviceworkers in b2g should use originAttributes when calling ServiceWorkerManager. r=baku,fabrice (faa3725da9) - Bug 1179161 - originAttributes does not have such isInBrowser member (follow-up bug 1171915). r=ferjm (a217140ae5) - Bug 1171917 - Improve about:serviceworkers tests on b2g. r=ferjm (5fd9d2f478) - Bug 1179557 - Add userContextId to originAttributes with tests. r=bholley, r=tanvi (8ddf96d921) - Bug 1179557 - Add getters for userContextId. r=bholley, r=tanvi (ebec5f7c7e) - Bug 1174110 - The service worker still remains registered when uninstalling the service-worker-enabled application. r=fabrice (c1c93b1250) - Bug 1144689 - Allow setting manually a fetch time and modified time for cache entries. r=fabrice (8e9dd47425) - Bug 1150199 - Langpacks should not have to be privileged r=ferjm (d41af25648) - Bug 1111961 - Developer mode support r=ferjm,pauljt (9b523402ac) - Bug 1168300 - notify clear-cookiejar-data. r=sicking (7d88bff29d) - Bug 1136434 - RequestSync API should delete all the timers when a task is unregistered, r=ehsan (5f92977920) - Bug 1151082 - RequestSyncAPI - avoid infinite loop when processing pending messages, r=ehsan (b5afcd55e8) - Bug 1165787 - Use origin in RequestSyncService.jsm. r=ehsan (b6fad2bd68) - Bug 1182347 - Migrate existing code away from .cookieJar. r=sicking,r=allstars.chh (304cbfd660) - Bug 1118946 - API to provide localized properties r=ferjm,sicking (a28aecaf19) - Bug 1077168 - Cancel in-flight Webapp install jobs from windows that change location. r=myk. (d55dc8ff6d) - Bug 1150660 - Fix sendAsyncMessage() uses to not trigger warnings in dom/apps r=fabrice (b087adcc23) - Bug 1169344 - Allow server apps to restrict access to their IAC ports. r=ferjm (82c8570555) - Bug 1068400 - Fix devtools when morphing non-e10s tab into e10s one. r=jryans (55be5ccdf5) - Bug 1145049 - Prevent caching tab actors in child processes. r=jryans (1a3ee9f278) - Bug 1145049 - Stop leaking tab actors and root actor on disconnect. r=jryans (26f259b441) - Bug 1181930 - Refactoring: move the message broadcaster out of Webapps.jsm r=ferjm (b1f8bb8b6d) - Bu 1115619 - Use a preference to guarantee app permission loading to permissions.sqlite. r=fabrice (5689c459d7) - Bug 1191579 - Remove useless getAll() implementation in Webapps.jsm (74f0d6874a)
This commit is contained in:
@@ -14,6 +14,7 @@ ID
|
||||
|
||||
# Vim swap files.
|
||||
.*.sw[a-z]
|
||||
.sw[a-z]
|
||||
|
||||
# Emacs directory variable files.
|
||||
**/.dir-locals.el
|
||||
|
||||
@@ -30,6 +30,7 @@ Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
|
||||
Cu.import('resource://gre/modules/DownloadsAPI.jsm');
|
||||
Cu.import('resource://gre/modules/MobileIdentityManager.jsm');
|
||||
Cu.import('resource://gre/modules/PresentationDeviceInfoManager.jsm');
|
||||
Cu.import('resource://gre/modules/AboutServiceWorkers.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
|
||||
"resource://gre/modules/SystemAppProxy.jsm");
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/* 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"
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["AboutServiceWorkers"];
|
||||
|
||||
const { interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
|
||||
"resource://gre/modules/SystemAppProxy.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gServiceWorkerManager",
|
||||
"@mozilla.org/serviceworkers/manager;1",
|
||||
"nsIServiceWorkerManager");
|
||||
|
||||
function debug(aMsg) {
|
||||
dump("AboutServiceWorkers - " + aMsg + "\n");
|
||||
}
|
||||
|
||||
function serializeServiceWorkerInfo(aServiceWorkerInfo) {
|
||||
if (!aServiceWorkerInfo) {
|
||||
throw new Error("Invalid service worker information");
|
||||
}
|
||||
|
||||
let result = {};
|
||||
|
||||
Object.keys(aServiceWorkerInfo).forEach(property => {
|
||||
if (typeof aServiceWorkerInfo[property] == "function") {
|
||||
return;
|
||||
}
|
||||
if (property === "principal") {
|
||||
result.principal = {
|
||||
origin: aServiceWorkerInfo.principal.origin,
|
||||
originAttributes: aServiceWorkerInfo.principal.originAttributes
|
||||
};
|
||||
return;
|
||||
}
|
||||
result[property] = aServiceWorkerInfo[property];
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
this.AboutServiceWorkers = {
|
||||
get enabled() {
|
||||
if (this._enabled) {
|
||||
return this._enabled;
|
||||
}
|
||||
this._enabled = false;
|
||||
try {
|
||||
this._enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled");
|
||||
} catch(e) {}
|
||||
return this._enabled;
|
||||
},
|
||||
|
||||
init: function() {
|
||||
SystemAppProxy.addEventListener("mozAboutServiceWorkersContentEvent",
|
||||
AboutServiceWorkers);
|
||||
},
|
||||
|
||||
sendResult: function(aId, aResult) {
|
||||
SystemAppProxy._sendCustomEvent("mozAboutServiceWorkersChromeEvent", {
|
||||
id: aId,
|
||||
result: aResult
|
||||
});
|
||||
},
|
||||
|
||||
sendError: function(aId, aError) {
|
||||
SystemAppProxy._sendCustomEvent("mozAboutServiceWorkersChromeEvent", {
|
||||
id: aId,
|
||||
error: aError
|
||||
});
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
let message = aEvent.detail;
|
||||
|
||||
debug("Got content event " + JSON.stringify(message));
|
||||
|
||||
if (!message.id || !message.name) {
|
||||
dump("Invalid event " + JSON.stringify(message) + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
let self = AboutServiceWorkers;
|
||||
|
||||
switch(message.name) {
|
||||
case "init":
|
||||
if (!self.enabled) {
|
||||
self.sendResult(message.id, {
|
||||
enabled: false,
|
||||
registrations: []
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
let data = gServiceWorkerManager.getAllRegistrations();
|
||||
if (!data) {
|
||||
self.sendError(message.id, "NoServiceWorkersRegistrations");
|
||||
return;
|
||||
}
|
||||
|
||||
let registrations = [];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let info = data.queryElementAt(i, Ci.nsIServiceWorkerInfo);
|
||||
if (!info) {
|
||||
dump("AboutServiceWorkers: Invalid nsIServiceWorkerInfo " +
|
||||
"interface.\n");
|
||||
continue;
|
||||
}
|
||||
registrations.push(serializeServiceWorkerInfo(info));
|
||||
}
|
||||
|
||||
self.sendResult(message.id, {
|
||||
enabled: self.enabled,
|
||||
registrations: registrations
|
||||
});
|
||||
break;
|
||||
|
||||
case "update":
|
||||
if (!message.scope) {
|
||||
self.sendError(message.id, "MissingScope");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!message.principal ||
|
||||
!message.principal.originAttributes) {
|
||||
self.sendError(message.id, "MissingOriginAttributes");
|
||||
return;
|
||||
}
|
||||
|
||||
gServiceWorkerManager.propagateSoftUpdate(
|
||||
message.principal.originAttributes,
|
||||
message.scope
|
||||
);
|
||||
|
||||
self.sendResult(message.id, true);
|
||||
break;
|
||||
|
||||
case "unregister":
|
||||
if (!message.principal ||
|
||||
!message.principal.origin ||
|
||||
!message.principal.originAttributes ||
|
||||
!message.principal.originAttributes.appId ||
|
||||
(message.principal.originAttributes.inBrowser == null)) {
|
||||
self.sendError(message.id, "MissingPrincipal");
|
||||
return;
|
||||
}
|
||||
|
||||
let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
|
||||
Services.io.newURI(message.principal.origin, null, null),
|
||||
message.principal.originAttributes.appId,
|
||||
message.principal.originAttributes.inBrowser
|
||||
);
|
||||
|
||||
if (!message.scope) {
|
||||
self.sendError(message.id, "MissingScope");
|
||||
return;
|
||||
}
|
||||
|
||||
let serviceWorkerUnregisterCallback = {
|
||||
unregisterSucceeded: function() {
|
||||
self.sendResult(message.id, true);
|
||||
},
|
||||
|
||||
unregisterFailed: function() {
|
||||
self.sendError(message.id, "UnregisterError");
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIServiceWorkerUnregisterCallback
|
||||
])
|
||||
};
|
||||
gServiceWorkerManager.propagateUnregister(principal,
|
||||
serviceWorkerUnregisterCallback,
|
||||
message.scope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AboutServiceWorkers.init();
|
||||
@@ -50,6 +50,7 @@ if CONFIG['MOZ_UPDATER']:
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AboutServiceWorkers.jsm',
|
||||
'AlertsHelper.jsm',
|
||||
'Bootstraper.jsm',
|
||||
'ContentRequestHelper.jsm',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test app for bug 1171917</title>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
function onLoad() {
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
parent.postMessage({status: "callback", data: "ready"}, "*");
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="onLoad()">
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test app for bug 1171917</title>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
function ok(aCondition, aMessage) {
|
||||
if (aCondition) {
|
||||
alert("OK: " + aMessage);
|
||||
} else {
|
||||
alert("KO: " + aMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function done() {
|
||||
alert("DONE");
|
||||
}
|
||||
|
||||
function testFrame(src) {
|
||||
return new Promise((resolve) => {
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.src = src;
|
||||
window.onmessage = function(e) {
|
||||
if (e.data.status == "callback") {
|
||||
window.onmessage = null;
|
||||
var result = e.data.data;
|
||||
document.body.removeChild(iframe);
|
||||
iframe.src = "about:blank";
|
||||
iframe = null;
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
}
|
||||
|
||||
function registerServiceWorker() {
|
||||
return navigator.serviceWorker.register("sw.sjs", {scope: "."});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
var lastSeenVersion;
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// Check whether the service worker is already registered and save its
|
||||
// version.
|
||||
return navigator.serviceWorker.getRegistration(".").then((swr) => {
|
||||
if (!swr) {
|
||||
lastSeenVersion = 0;
|
||||
return registerServiceWorker();
|
||||
}
|
||||
return testFrame("version.html").then((body) => {
|
||||
lastSeenVersion = parseInt(body);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// Wait until the service worker starts controlling the client.
|
||||
return testFrame("client.html");
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve) => {
|
||||
testFrame("wait_for_update.html").then(() => {
|
||||
// Fetch current version. It should be greater than the last seen version.
|
||||
testFrame("version.html").then((body) => {
|
||||
var currentVersion = parseInt(body);
|
||||
ok(lastSeenVersion < currentVersion, "New service worker version seen");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(done)
|
||||
.catch((e) => {
|
||||
dump("Unexpected error " + e);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="runTests()">
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "App",
|
||||
"launch_path": "/index.html",
|
||||
"description": "Test app for bug 1171917"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Content-Type: application/manifest+json
|
||||
@@ -0,0 +1,28 @@
|
||||
// The update process does not complete unless the service worker script changes.
|
||||
// The service worker script is a server-side JS file which allows us to get a
|
||||
// slightly different service worker script file each time the file is fetched.
|
||||
// Everytime there is either a registration or an update the script will be
|
||||
// fetched and a slightly different version of the script will be returned.
|
||||
|
||||
function handleRequest(request, response) {
|
||||
var stateName = request.scheme + "counter";
|
||||
if (!getState(stateName)) {
|
||||
setState(stateName, "1");
|
||||
} else {
|
||||
// Make sure that we pass a string value to setState!
|
||||
setState(stateName, "" + (parseInt(getState(stateName)) + 1));
|
||||
}
|
||||
response.setHeader("Content-Type", "application/javascript", false);
|
||||
response.write(getScript(stateName));
|
||||
}
|
||||
|
||||
function getScript(stateName) {
|
||||
return "oninstall = function(evt) {" +
|
||||
"evt.waitUntil(self.skipWaiting());" +
|
||||
"}; " +
|
||||
"onfetch = function(evt) {" +
|
||||
"if (evt.request.url.indexOf('get-sw-version') > -1) {" +
|
||||
"evt.respondWith(new Response('" + getState(stateName) + "'));" +
|
||||
"}" +
|
||||
"};";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test app for bug 1171917</title>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
function onLoad() {
|
||||
fetch("get-sw-version").then(function(r) {
|
||||
return r.text();
|
||||
}).then(function(body) {
|
||||
parent.postMessage({status: "callback", data: body}, "*");
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="onLoad()">
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test app for bug 1171917</title>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
function update() {
|
||||
alert("UPDATE");
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
navigator.serviceWorker.getRegistration(".").then(function(swr) {
|
||||
swr.onupdatefound = function() {
|
||||
swr.onupdatefound = null;
|
||||
parent.postMessage({status: "callback", data: "done"}, "*");
|
||||
};
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="onLoad()">
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
skip-if = toolkit != "gonk"
|
||||
support-files =
|
||||
app/*
|
||||
|
||||
[test_aboutserviceworkers.html]
|
||||
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1171917
|
||||
|
||||
The tests being added here are about testing the logic underneath the
|
||||
about:serviceworkers panel in the settings app. That logic is a B2G dedicate
|
||||
component.
|
||||
The component let us update and unregister a Service Worker Registration. It
|
||||
communicates with content through content/chrome events. We mock them so we can
|
||||
send messages for updating/unregistering a particular Service Worker
|
||||
Registration.
|
||||
|
||||
These tests install an app and the app registers a service worker. Through the
|
||||
component described above we update the corresponding Service Worker
|
||||
Registration, we test that the Service Worker gets updated and
|
||||
then we unregister it.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1171917</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;version=1.7">
|
||||
|
||||
const ASW_CHROME_EVENT = "mozAboutServiceWorkersChromeEvent";
|
||||
const ASW_CONTENT_EVENT = "mozAboutServiceWorkersContentEvent";
|
||||
|
||||
const { utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AboutServiceWorkers",
|
||||
"resource://gre/modules/AboutServiceWorkers.jsm");
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const gOrigin = "http://test/chrome/b2g/components/test/mochitest/app";
|
||||
const appManifestURL = gOrigin + "/manifest.webapp";
|
||||
let gApp;
|
||||
|
||||
const ORIGINAL_INIT = AboutServiceWorkers.init;
|
||||
const ORIGINAL_SENDRESULT = AboutServiceWorkers.sendResult;
|
||||
const ORIGINAL_SENDERROR = AboutServiceWorkers.sendError;
|
||||
|
||||
function debug(aMsg) {
|
||||
dump("test_aboutserviceworkers.html - " + aMsg + "\n");
|
||||
}
|
||||
|
||||
function mockInit() {
|
||||
window.addEventListener(ASW_CONTENT_EVENT, AboutServiceWorkers);
|
||||
}
|
||||
|
||||
function mockSendResult(aId, aResult) {
|
||||
let result = {
|
||||
detail: {
|
||||
id: aId,
|
||||
result: aResult
|
||||
}
|
||||
};
|
||||
debug("mockSendResult, " + JSON.stringify(result));
|
||||
let event = new CustomEvent(ASW_CHROME_EVENT, result);
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function mockSendError(aId, aError) {
|
||||
let error = {
|
||||
detail: {
|
||||
id: aId,
|
||||
error: aError
|
||||
}
|
||||
};
|
||||
debug("mockSendError, " + JSON.stringify(error));
|
||||
let event = new CustomEvent(ASW_CHROME_EVENT, error);
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function attachMocks() {
|
||||
AboutServiceWorkers.init = mockInit;
|
||||
AboutServiceWorkers.sendResult = mockSendResult;
|
||||
AboutServiceWorkers.sendError = mockSendError;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function restoreMocks() {
|
||||
AboutServiceWorkers.init = ORIGINAL_INIT;
|
||||
AboutServiceWorkers.sendResult = ORIGINAL_SENDRESULT;
|
||||
AboutServiceWorkers.sendError = ORIGINAL_SENDERROR;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function sendContentEvent(detail) {
|
||||
let event = new CustomEvent(ASW_CONTENT_EVENT, {
|
||||
detail: detail
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function chromeRequest(request) {
|
||||
if (!request) {
|
||||
return Promise.reject("InternalErrorMissingEventDetail");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let id = request.id;
|
||||
window.addEventListener(ASW_CHROME_EVENT,
|
||||
function onChromeEvent(event) {
|
||||
window.removeEventListener(ASW_CHROME_EVENT, onChromeEvent);
|
||||
let message = event.detail;
|
||||
if (!message || !message.id || message.id != id) {
|
||||
return reject('InternalErrorWrongChromeEvent');
|
||||
}
|
||||
|
||||
if (message.error) {
|
||||
reject(message.error);
|
||||
} else if (message.result) {
|
||||
resolve(message.result);
|
||||
}
|
||||
});
|
||||
|
||||
sendContentEvent(request);
|
||||
});
|
||||
}
|
||||
|
||||
function uninstallApp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!gApp) {
|
||||
return reject();
|
||||
}
|
||||
let req = navigator.mozApps.mgmt.uninstall(gApp);
|
||||
req.onsuccess = resolve;
|
||||
req.onerror = reject;
|
||||
});
|
||||
}
|
||||
|
||||
function update() {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return chromeRequest({id: "0", name: "init"});
|
||||
})
|
||||
.then((result) => {
|
||||
return chromeRequest({
|
||||
id: "1",
|
||||
name: "update",
|
||||
principal: result.registrations[0].principal,
|
||||
scope: result.registrations[0].scope
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testApp() {
|
||||
if (!gApp) {
|
||||
ok(false, "No test application to launch");
|
||||
return Promise.reject();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let iframe = document.createElement("iframe");
|
||||
let domParent = document.getElementById("container");
|
||||
iframe.setAttribute("mozbrowser", "true");
|
||||
iframe.setAttribute("mozapp", gApp.manifestURL);
|
||||
iframe.addEventListener("mozbrowsershowmodalprompt", function listener(e) {
|
||||
let message = e.detail.message;
|
||||
if (/KO/.exec(message)) {
|
||||
ok(false, "Message from app: " + message);
|
||||
} else if (/OK/.exec(message)) {
|
||||
ok(true, "Message from app: " + message);
|
||||
} else if (/UPDATE/.exec(message)) {
|
||||
ok(true, "Message from app: " + message);
|
||||
update();
|
||||
} else if (/DONE/.exec(message)) {
|
||||
ok(true, "Message from app: " + message);
|
||||
iframe.src = "about:blank";
|
||||
domParent.removeChild(iframe);
|
||||
iframe = null;
|
||||
resolve();
|
||||
}
|
||||
}, false);
|
||||
domParent.appendChild(iframe);
|
||||
ok(true, "origin " + gOrigin + gApp.manifest.launch_path);
|
||||
SpecialPowers.wrap(iframe.contentWindow).location =
|
||||
gOrigin + gApp.manifest.launch_path;
|
||||
});
|
||||
}
|
||||
|
||||
function installApp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let req = navigator.mozApps.install(appManifestURL);
|
||||
req.onsuccess = function() {
|
||||
gApp = req.result;
|
||||
is(req.result.manifestURL, appManifestURL, "app installed");
|
||||
if (req.result.installState == "installed") {
|
||||
is(req.result.installState, "installed", "app downloaded");
|
||||
resolve()
|
||||
} else {
|
||||
req.result.ondownloadapplied = function() {
|
||||
is(req.result.installState, "installed", "app downloaded");
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
req.onerror = reject;
|
||||
});
|
||||
}
|
||||
|
||||
function setup() {
|
||||
info("Setting up");
|
||||
return new Promise((resolve, reject) => {
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.mozBrowserFramesEnabled", true],
|
||||
["dom.serviceWorkers.exemptFromPerDomainMax", true],
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
["dom.serviceWorkers.testing.enabled", true],
|
||||
["dom.serviceWorkers.interception.enabled", true]
|
||||
]}, () => {
|
||||
SpecialPowers.pushPermissions([
|
||||
{ "type": "webapps-manage", "allow": 1, "context": document },
|
||||
{ "type": "browser", "allow": 1, "context": document },
|
||||
{ "type": "embed-apps", "allow": 1, "context": document }
|
||||
], () => {
|
||||
SpecialPowers.autoConfirmAppInstall(() => {
|
||||
SpecialPowers.autoConfirmAppUninstall(resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
restoreMocks();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function go() {
|
||||
setup()
|
||||
.then(attachMocks)
|
||||
.then(() => {
|
||||
AboutServiceWorkers.init();
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(installApp)
|
||||
.then(testApp)
|
||||
.then(() => {
|
||||
return chromeRequest({id: "2", name: "init"});
|
||||
})
|
||||
.then((result) => {
|
||||
return chromeRequest({
|
||||
id: "3",
|
||||
name: "unregister",
|
||||
principal: result.registrations[0].principal,
|
||||
scope: result.registrations[0].scope
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return chromeRequest({id: "4", name: "init"});
|
||||
})
|
||||
.then((result) => {
|
||||
ok(result.registrations && result.registrations.length === 0,
|
||||
"Service worker registration was successfuly unregistered");
|
||||
})
|
||||
.then(uninstallApp)
|
||||
.then(cleanUp)
|
||||
.catch((e) => {
|
||||
ok(false, "Unexpected error " + e);
|
||||
cleanUp();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="go()">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1171917">Mozilla Bug 1171917</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,3 +6,4 @@
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
|
||||
MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['mochitest/chrome.ini']
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AboutServiceWorkers",
|
||||
"resource://gre/modules/AboutServiceWorkers.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gServiceWorkerManager",
|
||||
"@mozilla.org/serviceworkers/manager;1",
|
||||
"nsIServiceWorkerManager");
|
||||
|
||||
const CHROME_MSG = "mozAboutServiceWorkersChromeEvent";
|
||||
|
||||
const ORIGINAL_SENDRESULT = AboutServiceWorkers.sendResult;
|
||||
const ORIGINAL_SENDERROR = AboutServiceWorkers.sendError;
|
||||
|
||||
do_get_profile();
|
||||
|
||||
let mockSendResult = (aId, aResult) => {
|
||||
let msg = {
|
||||
id: aId,
|
||||
result: aResult
|
||||
};
|
||||
Services.obs.notifyObservers({wrappedJSObject: msg}, CHROME_MSG, null);
|
||||
};
|
||||
|
||||
let mockSendError = (aId, aError) => {
|
||||
let msg = {
|
||||
id: aId,
|
||||
result: aError
|
||||
};
|
||||
Services.obs.notifyObservers({wrappedJSObject: msg}, CHROME_MSG, null);
|
||||
};
|
||||
|
||||
function attachMocks() {
|
||||
AboutServiceWorkers.sendResult = mockSendResult;
|
||||
AboutServiceWorkers.sendError = mockSendError;
|
||||
}
|
||||
|
||||
function restoreMocks() {
|
||||
AboutServiceWorkers.sendResult = ORIGINAL_SENDRESULT;
|
||||
AboutServiceWorkers.sendError = ORIGINAL_SENDERROR;
|
||||
}
|
||||
|
||||
do_register_cleanup(restoreMocks);
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
/**
|
||||
* "init" tests
|
||||
*/
|
||||
[
|
||||
// Pref disabled, no registrations
|
||||
{
|
||||
prefEnabled: false,
|
||||
expectedMessage: {
|
||||
id: Date.now(),
|
||||
result: {
|
||||
enabled: false,
|
||||
registrations: []
|
||||
}
|
||||
}
|
||||
},
|
||||
// Pref enabled, no registrations
|
||||
{
|
||||
prefEnabled: true,
|
||||
expectedMessage: {
|
||||
id: Date.now(),
|
||||
result: {
|
||||
enabled: true,
|
||||
registrations: []
|
||||
}
|
||||
}
|
||||
}].forEach(test => {
|
||||
add_test(function() {
|
||||
Services.prefs.setBoolPref("dom.serviceWorkers.enabled", test.prefEnabled);
|
||||
|
||||
let id = test.expectedMessage.id;
|
||||
|
||||
function onMessage(subject, topic, data) {
|
||||
let message = subject.wrappedJSObject;
|
||||
let expected = test.expectedMessage;
|
||||
|
||||
do_check_true(message.id, "Message should have id");
|
||||
do_check_eq(message.id, test.expectedMessage.id,
|
||||
"Id should be the expected one");
|
||||
do_check_eq(message.result.enabled, expected.result.enabled,
|
||||
"Pref should be disabled");
|
||||
do_check_true(message.result.registrations, "Registrations should exist");
|
||||
do_check_eq(message.result.registrations.length,
|
||||
expected.result.registrations.length,
|
||||
"Registrations length should be the expected one");
|
||||
|
||||
Services.obs.removeObserver(onMessage, CHROME_MSG);
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
Services.obs.addObserver(onMessage, CHROME_MSG, false);
|
||||
|
||||
attachMocks();
|
||||
|
||||
AboutServiceWorkers.handleEvent({ detail: {
|
||||
id: id,
|
||||
name: "init"
|
||||
}});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* ServiceWorkerManager tests.
|
||||
*/
|
||||
|
||||
// We cannot register a sw via ServiceWorkerManager cause chrome
|
||||
// registrations are not allowed.
|
||||
// All we can do for now is to test the interface of the swm.
|
||||
add_test(function test_swm() {
|
||||
do_check_true(gServiceWorkerManager, "SWM exists");
|
||||
do_check_true(gServiceWorkerManager.getAllRegistrations,
|
||||
"SWM.getAllRegistrations exists");
|
||||
do_check_true(typeof gServiceWorkerManager.getAllRegistrations == "function",
|
||||
"SWM.getAllRegistrations is a function");
|
||||
do_check_true(gServiceWorkerManager.propagateSoftUpdate,
|
||||
"SWM.propagateSoftUpdate exists");
|
||||
do_check_true(typeof gServiceWorkerManager.propagateSoftUpdate == "function",
|
||||
|
||||
"SWM.propagateSoftUpdate is a function");
|
||||
do_check_true(gServiceWorkerManager.propagateUnregister,
|
||||
"SWM.propagateUnregister exists");
|
||||
do_check_true(typeof gServiceWorkerManager.propagateUnregister == "function",
|
||||
"SWM.propagateUnregister exists");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
@@ -47,6 +47,12 @@ OriginAttributes::CreateSuffix(nsACString& aStr) const
|
||||
params->Set(NS_LITERAL_STRING("addonId"), mAddonId);
|
||||
}
|
||||
|
||||
if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
|
||||
value.Truncate();
|
||||
value.AppendInt(mUserContextId);
|
||||
params->Set(NS_LITERAL_STRING("userContextId"), value);
|
||||
}
|
||||
|
||||
aStr.Truncate();
|
||||
|
||||
params->Serialize(value);
|
||||
@@ -100,6 +106,16 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aName.EqualsLiteral("userContextId")) {
|
||||
nsresult rv;
|
||||
mOriginAttributes->mUserContextId = aValue.ToInteger(&rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// No other attributes are supported.
|
||||
return false;
|
||||
}
|
||||
@@ -288,6 +304,13 @@ BasePrincipal::GetAppId(uint32_t* aAppId)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
BasePrincipal::GetUserContextId(uint32_t* aUserContextId)
|
||||
{
|
||||
*aUserContextId = UserContextId();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
BasePrincipal::GetIsInBrowserElement(bool* aIsInBrowserElement)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,8 @@ public:
|
||||
{
|
||||
return mAppId == aOther.mAppId &&
|
||||
mInBrowser == aOther.mInBrowser &&
|
||||
mAddonId == aOther.mAddonId;
|
||||
mAddonId == aOther.mAddonId &&
|
||||
mUserContextId == aOther.mUserContextId;
|
||||
}
|
||||
bool operator!=(const OriginAttributes& aOther) const
|
||||
{
|
||||
@@ -83,6 +84,10 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mUserContextId.WasPassed() && mUserContextId.Value() != aAttrs.mUserContextId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -118,6 +123,7 @@ public:
|
||||
NS_IMETHOD GetAppId(uint32_t* aAppStatus) final;
|
||||
NS_IMETHOD GetIsInBrowserElement(bool* aIsInBrowserElement) final;
|
||||
NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final;
|
||||
NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
|
||||
|
||||
virtual bool IsOnCSSUnprefixingWhitelist() override { return false; }
|
||||
|
||||
@@ -128,6 +134,7 @@ public:
|
||||
|
||||
const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
|
||||
uint32_t AppId() const { return mOriginAttributes.mAppId; }
|
||||
uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
|
||||
bool IsInBrowserElement() const { return mOriginAttributes.mInBrowser; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -20,7 +20,7 @@ interface nsIContentSecurityPolicy;
|
||||
[ptr] native JSPrincipals(JSPrincipals);
|
||||
[ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);
|
||||
|
||||
[scriptable, builtinclass, uuid(7a9aa074-7565-4567-af2f-9e3704c7af9e)]
|
||||
[scriptable, builtinclass, uuid(a083acd0-1ebf-4585-85ab-08cfdd9c96bd)]
|
||||
interface nsIPrincipal : nsISerializable
|
||||
{
|
||||
/**
|
||||
@@ -250,6 +250,13 @@ interface nsIPrincipal : nsISerializable
|
||||
*/
|
||||
[infallible] readonly attribute unsigned long appId;
|
||||
|
||||
/**
|
||||
* Gets the id of the user context this principal is inside. If this
|
||||
* principal is inside the default userContext, this returns
|
||||
* nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID.
|
||||
*/
|
||||
[infallible] readonly attribute unsigned long userContextId;
|
||||
|
||||
/**
|
||||
* Returns true iff the principal is inside a browser element. (<iframe
|
||||
* mozbrowser mozapp> does not count as a browser element.)
|
||||
|
||||
@@ -26,7 +26,7 @@ class DomainPolicyClone;
|
||||
[ptr] native JSObjectPtr(JSObject);
|
||||
[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
|
||||
|
||||
[scriptable, uuid(50418f5c-b0d8-42c3-ba5d-efffb6927e1c)]
|
||||
[scriptable, uuid(9a8f0b70-6b9f-4e19-8885-7cfe24f4a42d)]
|
||||
interface nsIScriptSecurityManager : nsISupports
|
||||
{
|
||||
/**
|
||||
@@ -255,6 +255,8 @@ interface nsIScriptSecurityManager : nsISupports
|
||||
const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX
|
||||
const unsigned long SAFEBROWSING_APP_ID = 4294967294; // UINT32_MAX - 1
|
||||
|
||||
const unsigned long DEFAULT_USER_CONTEXT_ID = 0;
|
||||
|
||||
/**
|
||||
* Returns the jar prefix for the app.
|
||||
* appId can be NO_APP_ID or a valid app id. appId should not be
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
class nsIURI;
|
||||
|
||||
#define NS_NULLPRINCIPAL_CID \
|
||||
{ 0x34a19ab6, 0xca47, 0x4098, \
|
||||
{ 0xa7, 0xb8, 0x4a, 0xfc, 0xdd, 0xcd, 0x8f, 0x88 } }
|
||||
{ 0xbd066e5f, 0x146f, 0x4472, \
|
||||
{ 0x83, 0x31, 0x7b, 0xfd, 0x05, 0xb1, 0xed, 0x90 } }
|
||||
#define NS_NULLPRINCIPAL_CONTRACTID "@mozilla.org/nullprincipal;1"
|
||||
|
||||
#define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal"
|
||||
|
||||
+2
-2
@@ -112,8 +112,8 @@ private:
|
||||
|
||||
#define NS_PRINCIPAL_CONTRACTID "@mozilla.org/principal;1"
|
||||
#define NS_PRINCIPAL_CID \
|
||||
{ 0xb02c3023, 0x5b37, 0x472a, \
|
||||
{ 0xa2, 0xcd, 0x35, 0xaa, 0x5e, 0xe2, 0xa8, 0x19 } }
|
||||
{ 0x653e0e4d, 0x3ee4, 0x45fa, \
|
||||
{ 0xb2, 0x72, 0x97, 0xc2, 0x0b, 0xc0, 0x1e, 0xb8 } }
|
||||
|
||||
#define NS_EXPANDEDPRINCIPAL_CONTRACTID "@mozilla.org/expandedprincipal;1"
|
||||
#define NS_EXPANDEDPRINCIPAL_CID \
|
||||
|
||||
@@ -93,6 +93,25 @@ function run_test() {
|
||||
checkOriginAttributes(exampleOrg_addon, { addonId: "dummy" }, '!addonId=dummy');
|
||||
do_check_eq(exampleOrg_addon.origin, 'http://example.org!addonId=dummy');
|
||||
|
||||
// Just userContext.
|
||||
var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42});
|
||||
checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42');
|
||||
do_check_eq(exampleOrg_userContext.origin, 'http://example.org^userContextId=42');
|
||||
|
||||
// UserContext and Addon.
|
||||
var exampleOrg_userContextAddon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy', userContextId: 42});
|
||||
var nullPrin_userContextAddon = ssm.createNullPrincipal({addonId: 'dummy', userContextId: 42});
|
||||
checkOriginAttributes(exampleOrg_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42');
|
||||
checkOriginAttributes(nullPrin_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42');
|
||||
do_check_eq(exampleOrg_userContextAddon.origin, 'http://example.org^addonId=dummy&userContextId=42');
|
||||
|
||||
// UserContext and App.
|
||||
var exampleOrg_userContextApp = ssm.createCodebasePrincipal(makeURI('http://example.org'), {appId: 24, userContextId: 42});
|
||||
var nullPrin_userContextApp = ssm.createNullPrincipal({appId: 24, userContextId: 42});
|
||||
checkOriginAttributes(exampleOrg_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
|
||||
checkOriginAttributes(nullPrin_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
|
||||
do_check_eq(exampleOrg_userContextApp.origin, 'http://example.org^appId=24&userContextId=42');
|
||||
|
||||
// Check that all of the above are cross-origin.
|
||||
checkCrossOrigin(exampleOrg_app, exampleOrg);
|
||||
checkCrossOrigin(exampleOrg_app, nullPrin_app);
|
||||
@@ -102,4 +121,8 @@ function run_test() {
|
||||
checkCrossOrigin(exampleOrg_appBrowser, nullPrin_appBrowser);
|
||||
checkCrossOrigin(exampleOrg_appBrowser, exampleCom_appBrowser);
|
||||
checkCrossOrigin(exampleOrg_addon, exampleOrg);
|
||||
checkCrossOrigin(exampleOrg_userContext, exampleOrg);
|
||||
checkCrossOrigin(exampleOrg_userContextAddon, exampleOrg);
|
||||
checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextAddon);
|
||||
checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextApp);
|
||||
}
|
||||
|
||||
+15
-18
@@ -49,14 +49,7 @@ mozIApplication.prototype = {
|
||||
// This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
|
||||
// specific permission. It is not checking if browsers inside |aApp| have such
|
||||
// permission.
|
||||
let principal = this.principal;
|
||||
if (this.installerIsBrowser) {
|
||||
let uri = Services.io.newURI(this.origin, null, null);
|
||||
principal =
|
||||
Services.scriptSecurityManager.getAppCodebasePrincipal(uri, this.localId,
|
||||
/*mozbrowser*/false);
|
||||
}
|
||||
let perm = Services.perms.testExactPermissionFromPrincipal(principal,
|
||||
let perm = Services.perms.testExactPermissionFromPrincipal(this.principal,
|
||||
aPermission);
|
||||
return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
},
|
||||
@@ -81,7 +74,7 @@ mozIApplication.prototype = {
|
||||
this._principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
|
||||
Services.io.newURI(this.origin, null, null),
|
||||
this.localId,
|
||||
this.installerIsBrowser
|
||||
false /* mozbrowser */
|
||||
);
|
||||
} catch(e) {
|
||||
dump("Could not create app principal " + e + "\n");
|
||||
@@ -523,21 +516,20 @@ this.AppsUtils = {
|
||||
* Checks if the app role is allowed:
|
||||
* Only certified apps can be themes.
|
||||
* Only privileged or certified apps can be addons.
|
||||
* Langpacks need to be privileged.
|
||||
* @param aRole : the role assigned to this app.
|
||||
* @param aStatus : the APP_STATUS_* for this app.
|
||||
*/
|
||||
checkAppRole: function(aRole, aStatus) {
|
||||
try {
|
||||
// Anything is possible in developer mode.
|
||||
if (Services.prefs.getBoolPref("dom.apps.developer_mode")) {
|
||||
return true;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
if (aRole == "theme" && aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
||||
return false;
|
||||
}
|
||||
if (aRole == "langpack" && aStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
|
||||
let allow = false;
|
||||
try {
|
||||
allow = Services.prefs.getBoolPref("dom.apps.allow_unsigned_langpacks");
|
||||
} catch(e) {}
|
||||
return allow;
|
||||
}
|
||||
if (!this.allowUnsignedAddons &&
|
||||
(aRole == "addon" &&
|
||||
aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
|
||||
@@ -637,7 +629,12 @@ this.AppsUtils = {
|
||||
aPrefBranch.setCharPref("gecko.mstone", mstone);
|
||||
aPrefBranch.setCharPref("gecko.buildID", buildID);
|
||||
|
||||
return ((mstone != savedmstone) || (buildID != savedBuildID));
|
||||
if ((mstone != savedmstone) || (buildID != savedBuildID)) {
|
||||
aPrefBranch.setBoolPref("dom.apps.reset-permissions", false);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
Cu.import("resource://gre/modules/MessageBroadcaster.jsm");
|
||||
|
||||
Cu.importGlobalProperties(['File']);
|
||||
|
||||
@@ -43,9 +44,9 @@ const PR_CREATE_FILE = 0x08;
|
||||
const PR_TRUNCATE = 0x20;
|
||||
|
||||
function debug(aMsg) {
|
||||
//#ifdef DEBUG
|
||||
#ifdef DEBUG
|
||||
dump("-*- ImportExport.jsm : " + aMsg + "\n");
|
||||
//#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -104,7 +105,12 @@ this.ImportExport = {
|
||||
|
||||
// Exporting certified apps is forbidden, as it is to import them.
|
||||
// We *have* to do this check in the parent process.
|
||||
if (aApp.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
||||
let devMode = false;
|
||||
try {
|
||||
devMode = Services.prefs.getBoolPref("dom.apps.developer_mode");
|
||||
} catch(e) {};
|
||||
|
||||
if (aApp.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED && !devMode) {
|
||||
throw "CertifiedAppExportForbidden";
|
||||
}
|
||||
|
||||
@@ -393,6 +399,12 @@ this.ImportExport = {
|
||||
yield DOMApplicationRegistry._openPackage(appFile, meta, false);
|
||||
let maxStatus = isSigned ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
|
||||
: Ci.nsIPrincipal.APP_STATUS_INSTALLED;
|
||||
try {
|
||||
// Anything is possible in developer mode.
|
||||
if (Services.prefs.getBoolPref("dom.apps.developer_mode")) {
|
||||
maxStatus = Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
|
||||
}
|
||||
} catch(e) {};
|
||||
meta.appStatus = AppsUtils.getAppManifestStatus(manifest);
|
||||
debug("Signed app? " + isSigned);
|
||||
if (meta.appStatus > maxStatus) {
|
||||
@@ -456,10 +468,10 @@ this.ImportExport = {
|
||||
|
||||
app = AppsUtils.cloneAppObject(meta);
|
||||
app.manifest = manifest;
|
||||
DOMApplicationRegistry.broadcastMessage("Webapps:AddApp",
|
||||
{ id: meta.id, app: app });
|
||||
DOMApplicationRegistry.broadcastMessage("Webapps:Install:Return:OK",
|
||||
{ app: app });
|
||||
MessageBroadcaster.broadcastMessage("Webapps:AddApp",
|
||||
{ id: meta.id, app: app });
|
||||
MessageBroadcaster.broadcastMessage("Webapps:Install:Return:OK",
|
||||
{ app: app });
|
||||
Services.obs.notifyObservers(null, "webapps-installed",
|
||||
JSON.stringify({ manifestURL: meta.manifestURL }));
|
||||
|
||||
|
||||
@@ -241,6 +241,7 @@ this.InterAppCommService = {
|
||||
" aDescription: " + aDescription +
|
||||
" aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
|
||||
" aRules.manifestURLs: " + aRules.manifestURLs +
|
||||
" aRules.pageURLs: " + aRules.pageURLs +
|
||||
" aRules.installOrigins: " + aRules.installOrigins);
|
||||
}
|
||||
|
||||
@@ -316,6 +317,35 @@ this.InterAppCommService = {
|
||||
return false;
|
||||
},
|
||||
|
||||
_matchPageURLs: function(aRules, aPageURL) {
|
||||
|
||||
if (!aRules || !aRules.pageURLs) {
|
||||
if (DEBUG) {
|
||||
debug("rules.pageURLs is not available. No need to match.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Array.isArray(aRules.pageURLs)) {
|
||||
aRules.pageURLs = [aRules.pageURLs];
|
||||
}
|
||||
|
||||
let pageURLs = aRules.pageURLs;
|
||||
let isAllowed = false;
|
||||
for (let i = 0, li = pageURLs.length; i < li && !isAllowed ; i++) {
|
||||
let regExpAllowedURL = new RegExp(pageURLs[i]);
|
||||
isAllowed = regExpAllowedURL.test(aPageURL);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("rules.pageURLs is " + (isAllowed ? "" : "not") + " matched!" +
|
||||
" pageURLs: " + pageURLs +
|
||||
" aPageURL: " + aPageURL);
|
||||
}
|
||||
|
||||
return isAllowed;
|
||||
},
|
||||
|
||||
_matchInstallOrigins: function(aRules, aInstallOrigin) {
|
||||
if (!aRules || !Array.isArray(aRules.installOrigins)) {
|
||||
if (DEBUG) {
|
||||
@@ -337,8 +367,28 @@ this.InterAppCommService = {
|
||||
return false;
|
||||
},
|
||||
|
||||
// A connection is allowed if all the rules are matched.
|
||||
// The publisher is matched against the rules defined by the subscriber on the
|
||||
// manifest, and the subscriber is matched against the rules defined by the
|
||||
// publisher on the call to connect.
|
||||
// The possible rules for both subscribers and publishers are:
|
||||
// * minimumAccessLevel: "privileged"|"certified"|"web"|undefined
|
||||
// The default (non existant or undefined value) is "certified".
|
||||
// That means that if an explicit minimumAccessLevel rule does not
|
||||
// exist then the peer of the connection *must* be a certified app.
|
||||
// * pageURLs: Array of regExp of URLs. If the value exists, only the pages
|
||||
// whose URLs are explicitly declared on the array (matched) can connect.
|
||||
// Otherwise all pages can connect
|
||||
// * installOrigins: Array of origin URLs. If the value exist, only the apps
|
||||
// whose origins are on the array can connect. Otherwise, all origins are
|
||||
// allowed. This is only checked for non certified apps!
|
||||
// The default value (empty or non existant rules) is:
|
||||
// * Only certified apps can connect
|
||||
// * Any originator/receiving page URLs are valid
|
||||
// * Any origin is valid.
|
||||
_matchRules: function(aPubAppManifestURL, aPubRules,
|
||||
aSubAppManifestURL, aSubRules) {
|
||||
aSubAppManifestURL, aSubRules,
|
||||
aPubPageURL, aSubPageURL) {
|
||||
let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL);
|
||||
let subApp = appsService.getAppByManifestURL(aSubAppManifestURL);
|
||||
|
||||
@@ -348,10 +398,8 @@ this.InterAppCommService = {
|
||||
let isSubAppCertified =
|
||||
(subApp.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED);
|
||||
|
||||
// TODO Bug 907068 In the initiative step, we only expose this API to
|
||||
// certified apps to meet the time line. Eventually, we need to make
|
||||
// it available for the non-certified apps as well. For now, only the
|
||||
// certified apps can match the rules.
|
||||
#ifndef NIGHTLY_BUILD
|
||||
|
||||
if (!isPubAppCertified || !isSubAppCertified) {
|
||||
if (DEBUG) {
|
||||
debug("Only certified apps are allowed to do connections.");
|
||||
@@ -359,6 +407,22 @@ this.InterAppCommService = {
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
let numSubRules = (aSubRules && Object.keys(aSubRules).length) || 0;
|
||||
let numPubRules = (aPubRules && Object.keys(aPubRules).length) || 0;
|
||||
|
||||
if ((!isSubAppCertified && !numPubRules) ||
|
||||
(!isPubAppCertified && !numSubRules)) {
|
||||
if (DEBUG) {
|
||||
debug("If there aren't rules defined only certified apps are allowed " +
|
||||
"to do connections.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (!aPubRules && !aSubRules) {
|
||||
if (DEBUG) {
|
||||
debug("No rules for publisher and subscriber. No need to match.");
|
||||
@@ -378,6 +442,12 @@ this.InterAppCommService = {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check pageURLs.
|
||||
if (!this._matchPageURLs(aPubRules, aSubPageURL) ||
|
||||
!this._matchPageURLs(aSubRules, aPubPageURL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check installOrigins. Note that we only check the install origin for the
|
||||
// non-certified app, because the certified app doesn't have install origin.
|
||||
if ((!isSubAppCertified &&
|
||||
@@ -566,7 +636,8 @@ this.InterAppCommService = {
|
||||
|
||||
let matched =
|
||||
this._matchRules(pubAppManifestURL, pubRules,
|
||||
subAppManifestURL, subRules);
|
||||
subAppManifestURL, subRules,
|
||||
pubPageURL, subscribedInfo.pageURL);
|
||||
if (!matched) {
|
||||
if (DEBUG) {
|
||||
debug("Rules are not matched. Skipping: " + subAppManifestURL);
|
||||
|
||||
+128
-23
@@ -47,14 +47,17 @@ this.Langpacks = {
|
||||
_data: {},
|
||||
_broadcaster: null,
|
||||
_appIdFromManifestURL: null,
|
||||
_appFromManifestURL: null,
|
||||
|
||||
init: function() {
|
||||
ppmm.addMessageListener("Webapps:GetLocalizationResource", this);
|
||||
ppmm.addMessageListener("Webapps:GetLocalizedValue", this);
|
||||
},
|
||||
|
||||
registerRegistryFunctions: function(aBroadcaster, aIdGetter) {
|
||||
registerRegistryFunctions: function(aBroadcaster, aIdGetter, aAppGetter) {
|
||||
this._broadcaster = aBroadcaster;
|
||||
this._appIdFromManifestURL = aIdGetter;
|
||||
this._appFromManifestURL = aAppGetter;
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
@@ -64,6 +67,9 @@ this.Langpacks = {
|
||||
case "Webapps:GetLocalizationResource":
|
||||
this.getLocalizationResource(data, mm);
|
||||
break;
|
||||
case "Webapps:GetLocalizedValue":
|
||||
this.getLocalizedValue(data, mm);
|
||||
break;
|
||||
default:
|
||||
debug("Unexpected message: " + aMessage.name);
|
||||
}
|
||||
@@ -108,6 +114,35 @@ this.Langpacks = {
|
||||
this._broadcaster("Webapps:UpdateState", message);
|
||||
},
|
||||
|
||||
_getResource: function(aURL, aResponseType) {
|
||||
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
xhr.mozBackgroundRequest = true;
|
||||
xhr.open("GET", aURL);
|
||||
|
||||
// Default to text response type, but the webidl binding takes care of
|
||||
// validating the dataType value.
|
||||
xhr.responseType = "text";
|
||||
if (aResponseType === "json") {
|
||||
xhr.responseType = "json";
|
||||
} else if (aResponseType === "binary") {
|
||||
xhr.responseType = "blob";
|
||||
}
|
||||
|
||||
return new Promise((aResolve, aReject) => {
|
||||
xhr.addEventListener("load", function() {
|
||||
debug("Success loading " + aURL);
|
||||
if (xhr.status >= 200 && xhr.status < 400) {
|
||||
aResolve(xhr.response);
|
||||
} else {
|
||||
aReject();
|
||||
}
|
||||
});
|
||||
xhr.addEventListener("error", aReject);
|
||||
xhr.send(null);
|
||||
});
|
||||
},
|
||||
|
||||
getLocalizationResource: function(aData, aMm) {
|
||||
debug("getLocalizationResource " + uneval(aData));
|
||||
|
||||
@@ -143,33 +178,103 @@ this.Langpacks = {
|
||||
let href = item.url + aData.path;
|
||||
debug("Will load " + href);
|
||||
|
||||
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
xhr.mozBackgroundRequest = true;
|
||||
xhr.open("GET", href);
|
||||
this._getResource(href, aData.dataType).then(
|
||||
(aResponse) => {
|
||||
aMm.sendAsyncMessage("Webapps:GetLocalizationResource:Return",
|
||||
{ requestID: aData.requestID, oid: aData.oid, data: aResponse });
|
||||
},
|
||||
() => { sendError("Error loading " + href, "UnavailableResource"); }
|
||||
);
|
||||
},
|
||||
|
||||
// Default to text response type, but the webidl binding takes care of
|
||||
// validating the dataType value.
|
||||
xhr.responseType = "text";
|
||||
if (aData.dataType === "json") {
|
||||
xhr.responseType = "json";
|
||||
} else if (aData.dataType === "binary") {
|
||||
xhr.responseType = "blob";
|
||||
getLocalizedValue: function(aData, aMm) {
|
||||
debug("getLocalizedValue " + aData.property);
|
||||
function sendError(aMsg, aCode) {
|
||||
debug(aMsg);
|
||||
aMm.sendAsyncMessage("Webapps:GetLocalizedValue:Return",
|
||||
{ success: false,
|
||||
requestID: aData.requestID,
|
||||
oid: aData.oid,
|
||||
error: aCode });
|
||||
}
|
||||
|
||||
xhr.addEventListener("load", function() {
|
||||
debug("Success loading " + href);
|
||||
if (xhr.status >= 200 && xhr.status < 400) {
|
||||
aMm.sendAsyncMessage("Webapps:GetLocalizationResource:Return",
|
||||
{ requestID: aData.requestID, oid: aData.oid, data: xhr.response });
|
||||
function getValueFromManifest(aManifest) {
|
||||
debug("Getting " + aData.property + " from the manifest.");
|
||||
let value = aManifest._localeProp(aData.property);
|
||||
if (!value) {
|
||||
sendError("No property " + aData.property + " in manifest", "UnknownProperty");
|
||||
} else {
|
||||
sendError("Error loading " + href, "UnavailableResource");
|
||||
aMm.sendAsyncMessage("Webapps:GetLocalizedValue:Return",
|
||||
{ success: true,
|
||||
requestID: aData.requestID,
|
||||
oid: aData.oid,
|
||||
value: value });
|
||||
}
|
||||
});
|
||||
xhr.addEventListener("error", function() {
|
||||
sendError("Error loading " + href, "UnavailableResource");
|
||||
});
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
|
||||
function getValueFromLangpack(aItem, aManifest) {
|
||||
debug("Getting value from langpack at " + aItem.url + "/manifest.json")
|
||||
let href = aItem.url + "/manifest.json";
|
||||
|
||||
function getProperty(aResponse, aProp) {
|
||||
let root = aData.entryPoint && aResponse.entry_points &&
|
||||
aResponse.entry_points[aData.entryPoint]
|
||||
? aResponse.entry_points[aData.entryPoint]
|
||||
: aResponse;
|
||||
return root[aProp];
|
||||
}
|
||||
|
||||
self._getResource(href, "json").then(
|
||||
(aResponse) => {
|
||||
let propValue = getProperty(aResponse, aData.property);
|
||||
if (propValue) {
|
||||
aMm.sendAsyncMessage("Webapps:GetLocalizedValue:Return",
|
||||
{ success: true,
|
||||
requestID: aData.requestID,
|
||||
oid: aData.oid,
|
||||
value: propValue });
|
||||
} else {
|
||||
getValueFromManifest(aManifest);
|
||||
}
|
||||
},
|
||||
() => { getValueFromManifest(aManifest); }
|
||||
);
|
||||
}
|
||||
|
||||
// We need to get the app with the manifest since the version is only
|
||||
// available in the manifest.
|
||||
this._appFromManifestURL(aData.manifestURL, aData.entryPoint)
|
||||
.then(aApp => {
|
||||
let manifest = aApp.manifest;
|
||||
|
||||
// No langpack for this app or we have langpack(s) for this app, but
|
||||
// not for this language.
|
||||
// Fallback to the manifest values.
|
||||
if (!this._data[aData.manifestURL] ||
|
||||
!this._data[aData.manifestURL].langs[aData.lang]) {
|
||||
getValueFromManifest(manifest);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!manifest.version) {
|
||||
getValueFromManifest(manifest);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we have the langpack for the right app version.
|
||||
let item = this._data[aData.manifestURL].langs[aData.lang];
|
||||
// Only keep x.y in the manifest's version in case it's x.y.z
|
||||
let manVersion = manifest.version.split('.').slice(0, 2).join('.');
|
||||
if (item.target == manVersion) {
|
||||
getValueFromLangpack(item, manifest);
|
||||
return;
|
||||
}
|
||||
// Fallback on getting the value from the manifest.
|
||||
getValueFromManifest(manifest);
|
||||
})
|
||||
.catch(aError => { sendError("No app!", "NoSuchApp") });
|
||||
},
|
||||
|
||||
// Validates the langpack part of a manifest.
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/* 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";
|
||||
|
||||
// Manages registration of message managers from child processes and
|
||||
// broadcasting messages to them.
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MessageBroadcaster"];
|
||||
|
||||
this.MessageBroadcaster = {
|
||||
appGetter: null,
|
||||
children: [],
|
||||
|
||||
init: function(aAppGetter) {
|
||||
if (!aAppGetter || typeof aAppGetter !== "function") {
|
||||
throw "MessageBroadcaster.init needs a function parameter";
|
||||
}
|
||||
this.appGetter = aAppGetter;
|
||||
},
|
||||
|
||||
// We manage refcounting of listeners per message manager.
|
||||
addMessageListener: function(aMsgNames, aApp, aMm) {
|
||||
aMsgNames.forEach(aMsgName => {
|
||||
let manifestURL = aApp && aApp.manifestURL;
|
||||
if (!(aMsgName in this.children)) {
|
||||
this.children[aMsgName] = [];
|
||||
}
|
||||
|
||||
let mmFound = this.children[aMsgName].some(mmRef => {
|
||||
if (mmRef.mm === aMm) {
|
||||
mmRef.refCount++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!mmFound) {
|
||||
this.children[aMsgName].push({
|
||||
mm: aMm,
|
||||
refCount: 1
|
||||
});
|
||||
}
|
||||
|
||||
// If the state reported by the registration is outdated, update it now.
|
||||
if (manifestURL && ((aMsgName === 'Webapps:FireEvent') ||
|
||||
(aMsgName === 'Webapps:UpdateState'))) {
|
||||
let app = this.appGetter(aApp.manifestURL);
|
||||
if (app && ((aApp.installState !== app.installState) ||
|
||||
(aApp.downloading !== app.downloading))) {
|
||||
debug("Got a registration from an outdated app: " +
|
||||
manifestURL);
|
||||
let aEvent ={
|
||||
type: app.installState,
|
||||
app: app,
|
||||
manifestURL: app.manifestURL,
|
||||
manifest: app.manifest
|
||||
};
|
||||
aMm.sendAsyncMessage(aMsgName, aEvent);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeMessageListener: function(aMsgNames, aMm) {
|
||||
if (aMsgNames.length === 1 &&
|
||||
aMsgNames[0] === "Webapps:Internal:AllMessages") {
|
||||
for (let msgName in this.children) {
|
||||
let msg = this.children[msgName];
|
||||
|
||||
for (let mmI = msg.length - 1; mmI >= 0; mmI -= 1) {
|
||||
let mmRef = msg[mmI];
|
||||
if (mmRef.mm === aMm) {
|
||||
msg.splice(mmI, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.length === 0) {
|
||||
delete this.children[msgName];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
aMsgNames.forEach(aMsgName => {
|
||||
if (!(aMsgName in this.children)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let removeIndex;
|
||||
this.children[aMsgName].some((mmRef, index) => {
|
||||
if (mmRef.mm === aMm) {
|
||||
mmRef.refCount--;
|
||||
if (mmRef.refCount === 0) {
|
||||
removeIndex = index;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (removeIndex) {
|
||||
this.children[aMsgName].splice(removeIndex, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Some messages can be listened by several content processes:
|
||||
// Webapps:AddApp
|
||||
// Webapps:RemoveApp
|
||||
// Webapps:Install:Return:OK
|
||||
// Webapps:Uninstall:Return:OK
|
||||
// Webapps:Uninstall:Broadcast:Return:OK
|
||||
// Webapps:FireEvent
|
||||
// Webapps:checkForUpdate:Return:OK
|
||||
// Webapps:UpdateState
|
||||
broadcastMessage: function(aMsgName, aContent) {
|
||||
if (!(aMsgName in this.children)) {
|
||||
return;
|
||||
}
|
||||
this.children[aMsgName].forEach((mmRef) => {
|
||||
mmRef.mm.sendAsyncMessage(aMsgName, this.formatMessage(aContent));
|
||||
});
|
||||
},
|
||||
|
||||
formatMessage: function(aData) {
|
||||
let msg = aData;
|
||||
delete msg["mm"];
|
||||
return msg;
|
||||
},
|
||||
}
|
||||
@@ -46,32 +46,43 @@ function enableOfflineCacheForApp(aPrincipal) {
|
||||
}
|
||||
|
||||
|
||||
function storeCache(applicationCache, url, file, itemType) {
|
||||
function storeCache(applicationCache, url, file, itemType, metadata) {
|
||||
let storage =
|
||||
Services.cache2.appCacheStorage(LoadContextInfo.default, applicationCache);
|
||||
let uri = Services.io.newURI(url, null, null);
|
||||
let nowGMT = new Date().toGMTString();
|
||||
metadata = metadata || {};
|
||||
metadata.lastFetched = metadata.lastFetched || nowGMT;
|
||||
metadata.lastModified = metadata.lastModified || nowGMT;
|
||||
storage.asyncOpenURI(uri, "", nsICacheStorage.OPEN_TRUNCATE, {
|
||||
onCacheEntryAvailable:
|
||||
function (cacheEntry, isNew, appCache, result) {
|
||||
cacheEntry.setMetaDataElement('request-method', 'GET');
|
||||
cacheEntry.setMetaDataElement('response-head', 'HTTP/1.1 200 OK\r\n');
|
||||
cacheEntry.setMetaDataElement("request-method", "GET");
|
||||
cacheEntry.setMetaDataElement("response-head",
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Date: " + metadata.lastFetched + "\r\n" +
|
||||
"Last-Modified: " + metadata.lastModified + "\r\n" +
|
||||
"Cache-Control: no-cache\r\n");
|
||||
|
||||
let outputStream = cacheEntry.openOutputStream(0);
|
||||
|
||||
// Input-Output stream machinery in order to push nsIFile content into cache
|
||||
let inputStream = Cc['@mozilla.org/network/file-input-stream;1']
|
||||
// Input-Output stream machinery in order to push nsIFile content into
|
||||
// cache
|
||||
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
inputStream.init(file, 1, -1, null);
|
||||
let bufferedOutputStream = Cc['@mozilla.org/network/buffered-output-stream;1']
|
||||
.createInstance(Ci.nsIBufferedOutputStream);
|
||||
let bufferedOutputStream =
|
||||
Cc["@mozilla.org/network/buffered-output-stream;1"]
|
||||
.createInstance(Ci.nsIBufferedOutputStream);
|
||||
bufferedOutputStream.init(outputStream, 1024);
|
||||
bufferedOutputStream.writeFrom(inputStream, inputStream.available());
|
||||
bufferedOutputStream.flush();
|
||||
bufferedOutputStream.close();
|
||||
inputStream.close();
|
||||
|
||||
cacheEntry.setExpirationTime(0);
|
||||
cacheEntry.markValid();
|
||||
debug (file.path + ' -> ' + url + ' (' + itemType + ')');
|
||||
debug (file.path + " -> " + url + " (" + itemType + ")");
|
||||
applicationCache.markEntry(url, itemType);
|
||||
cacheEntry.close();
|
||||
}
|
||||
@@ -206,8 +217,12 @@ function installCache(app) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cacheDir = makeFile(app.cachePath)
|
||||
let cacheDir = makeFile(app.cachePath);
|
||||
cacheDir.append(app.appId);
|
||||
|
||||
let resourcesMetadata = cacheDir.clone();
|
||||
resourcesMetadata.append('resources_metadata.json');
|
||||
|
||||
cacheDir.append('cache');
|
||||
if (!cacheDir.exists())
|
||||
return;
|
||||
@@ -218,55 +233,72 @@ function installCache(app) {
|
||||
return;
|
||||
|
||||
let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
|
||||
app.origin, app.localId, false);
|
||||
app.origin, app.localId, false);
|
||||
|
||||
enableOfflineCacheForApp(principal);
|
||||
// If the build has been correctly configured, this should not happen!
|
||||
// If we install the cache anyway, it won't be updateable. If we don't install
|
||||
// it, the application won't be useable offline.
|
||||
let metadataLoaded;
|
||||
if (!resourcesMetadata.exists()) {
|
||||
// Not debug, since this is something that should be logged always!
|
||||
dump("OfflineCacheInstaller: App " + app.appId + " does have an app cache" +
|
||||
" but does not have a resources_metadata.json file!");
|
||||
metadataLoaded = Promise.resolve({});
|
||||
} else {
|
||||
metadataLoaded = new Promise(
|
||||
(resolve, reject) =>
|
||||
readFile(resourcesMetadata, principal, content => resolve(JSON.parse(content))));
|
||||
}
|
||||
|
||||
// Get the url for the manifest.
|
||||
let appcacheURL = app.appcache_path;
|
||||
metadataLoaded.then(function(metadata) {
|
||||
enableOfflineCacheForApp(principal);
|
||||
|
||||
// The group ID contains application id and 'f' for not being hosted in
|
||||
// a browser element, but a mozbrowser iframe.
|
||||
// See netwerk/cache/nsDiskCacheDeviceSQL.cpp: AppendJARIdentifier
|
||||
let groupID = appcacheURL + '#' + app.localId+ '+f';
|
||||
let applicationCache = applicationCacheService.createApplicationCache(groupID);
|
||||
applicationCache.activate();
|
||||
// Get the url for the manifest.
|
||||
let appcacheURL = app.appcache_path;
|
||||
|
||||
readFile(cacheManifest, principal, function readAppCache(content) {
|
||||
let entries = parseAppCache(app, cacheManifest.path, content);
|
||||
// The group ID contains application id and 'f' for not being hosted in
|
||||
// a browser element, but a mozbrowser iframe.
|
||||
// See netwerk/cache/nsDiskCacheDeviceSQL.cpp: AppendJARIdentifier
|
||||
let groupID = appcacheURL + '#' + app.localId+ '+f';
|
||||
let applicationCache = applicationCacheService.createApplicationCache(groupID);
|
||||
applicationCache.activate();
|
||||
|
||||
entries.urls.forEach(function processCachedFile(url) {
|
||||
// Get this nsIFile from cache folder for this URL
|
||||
// We have absolute urls, so remove the origin part to locate the
|
||||
// files.
|
||||
let path = url.replace(app.origin.spec, '');
|
||||
let file = cacheDir.clone();
|
||||
let paths = path.split('/');
|
||||
paths.forEach(file.append);
|
||||
readFile(cacheManifest, principal, function readAppCache(content) {
|
||||
let entries = parseAppCache(app, cacheManifest.path, content);
|
||||
|
||||
if (!file.exists()) {
|
||||
let msg = 'File ' + file.path + ' exists in the manifest but does ' +
|
||||
'not points to a real file.';
|
||||
throw new Error(msg);
|
||||
}
|
||||
entries.urls.forEach(function processCachedFile(url) {
|
||||
// Get this nsIFile from cache folder for this URL
|
||||
// We have absolute urls, so remove the origin part to locate the
|
||||
// files.
|
||||
let path = url.replace(app.origin.spec, '');
|
||||
let file = cacheDir.clone();
|
||||
let paths = path.split('/');
|
||||
paths.forEach(file.append);
|
||||
|
||||
let itemType = nsIApplicationCache.ITEM_EXPLICIT;
|
||||
if (entries.fallbacks.indexOf(url) > -1) {
|
||||
debug('add fallback: ' + url + '\n');
|
||||
itemType |= nsIApplicationCache.ITEM_FALLBACK;
|
||||
}
|
||||
storeCache(applicationCache, url, file, itemType);
|
||||
if (!file.exists()) {
|
||||
let msg = 'File ' + file.path + ' exists in the manifest but does ' +
|
||||
'not points to a real file.';
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
let itemType = nsIApplicationCache.ITEM_EXPLICIT;
|
||||
if (entries.fallbacks.indexOf(url) > -1) {
|
||||
debug('add fallback: ' + url + '\n');
|
||||
itemType |= nsIApplicationCache.ITEM_FALLBACK;
|
||||
}
|
||||
storeCache(applicationCache, url, file, itemType, metadata[path]);
|
||||
});
|
||||
|
||||
let array = new MutableArray();
|
||||
entries.namespaces.forEach(function processNamespace([type, spec, data]) {
|
||||
debug('add namespace: ' + type + ' - ' + spec + ' - ' + data + '\n');
|
||||
array.appendElement(new Namespace(type, spec, data), false);
|
||||
});
|
||||
applicationCache.addNamespaces(array);
|
||||
|
||||
storeCache(applicationCache, appcacheURL, cacheManifest,
|
||||
nsIApplicationCache.ITEM_MANIFEST);
|
||||
});
|
||||
|
||||
let array = new MutableArray();
|
||||
entries.namespaces.forEach(function processNamespace([type, spec, data]) {
|
||||
debug('add namespace: ' + type + ' - ' + spec + ' - ' + data + '\n');
|
||||
array.appendElement(new Namespace(type, spec, data), false);
|
||||
});
|
||||
applicationCache.addNamespaces(array);
|
||||
|
||||
storeCache(applicationCache, appcacheURL, cacheManifest,
|
||||
nsIApplicationCache.ITEM_MANIFEST);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,8 @@ this.PermissionsInstaller = {
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
dump("Caught webapps install permissions error for " + aApp.origin);
|
||||
dump("Caught webapps install permissions error for " + aApp.origin +
|
||||
" : " + ex + "\n");
|
||||
Cu.reportError(ex);
|
||||
if (aOnError) {
|
||||
aOnError();
|
||||
|
||||
+39
-1
@@ -267,6 +267,7 @@ WebappsRegistry.prototype = {
|
||||
cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
|
||||
["Webapps:Install:Return:OK",
|
||||
"Webapps:AdditionalLanguageChange"]);
|
||||
this._window.removeEventListener("pagehide", this);
|
||||
},
|
||||
|
||||
installPackage: function(aURL, aParams) {
|
||||
@@ -337,6 +338,7 @@ WebappsRegistry.prototype = {
|
||||
const prefs = new Preferences();
|
||||
|
||||
this._window = aWindow;
|
||||
this._window.addEventListener("pagehide", this);
|
||||
|
||||
this.initDOMRequestHelper(aWindow, ["Webapps:Install:Return:OK",
|
||||
"Webapps:AdditionalLanguageChange"]);
|
||||
@@ -375,6 +377,15 @@ WebappsRegistry.prototype = {
|
||||
this.hasFullMgmtPrivilege = hasWebappsPermission;
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type == "pagehide" &&
|
||||
event.target.defaultView == this._window) {
|
||||
cpmm.sendAsyncMessage("Webapps:LocationChange", {
|
||||
oid: this._id,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
|
||||
@@ -688,6 +699,24 @@ WebappsApplication.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
getLocalizedValue: function(aProperty, aLang, aEntryPoint) {
|
||||
this.addMessageListeners(["Webapps:GetLocalizedValue:Return"]);
|
||||
return this.createPromise((aResolve, aReject) => {
|
||||
cpmm.sendAsyncMessage("Webapps:GetLocalizedValue",
|
||||
{ manifestURL: this.manifestURL,
|
||||
oid: this._id,
|
||||
topId: this._topId,
|
||||
property: aProperty,
|
||||
lang: aLang,
|
||||
entryPoint: aEntryPoint,
|
||||
requestID: this.getPromiseResolverId({
|
||||
resolve: aResolve,
|
||||
reject: aReject
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_prepareForContent: function() {
|
||||
if (this.__DOM_IMPL__) {
|
||||
return this.__DOM_IMPL__;
|
||||
@@ -725,7 +754,8 @@ WebappsApplication.prototype = {
|
||||
if (aMessage.name == "Webapps:Connect:Return:OK" ||
|
||||
aMessage.name == "Webapps:Connect:Return:KO" ||
|
||||
aMessage.name == "Webapps:GetConnections:Return:OK" ||
|
||||
aMessage.name == "Webapps:Export:Return") {
|
||||
aMessage.name == "Webapps:Export:Return" ||
|
||||
aMessage.name == "Webapps:GetLocalizedValue:Return") {
|
||||
req = this.takePromiseResolver(msg.requestID);
|
||||
} else {
|
||||
req = this.takeRequest(msg.requestID);
|
||||
@@ -821,6 +851,14 @@ WebappsApplication.prototype = {
|
||||
req.reject(new this._window.DOMError(msg.error || ""));
|
||||
}
|
||||
break;
|
||||
case "Webapps:GetLocalizedValue:Return":
|
||||
this.removeMessageListeners(["Webapps:GetLocalizedValue:Return"]);
|
||||
if (msg.success) {
|
||||
req.resolve(msg.value);
|
||||
} else {
|
||||
req.reject(new this._window.DOMError(msg.error || ""));
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+248
-240
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -33,8 +33,8 @@ EXTRA_JS_MODULES += [
|
||||
'AppDownloadManager.jsm',
|
||||
'AppsServiceChild.jsm',
|
||||
'FreeSpaceWatcher.jsm',
|
||||
'InterAppCommService.jsm',
|
||||
'Langpacks.jsm',
|
||||
'MessageBroadcaster.jsm',
|
||||
'OfflineCacheInstaller.jsm',
|
||||
'PermissionsInstaller.jsm',
|
||||
'PermissionsTable.jsm',
|
||||
@@ -45,6 +45,7 @@ EXTRA_JS_MODULES += [
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
'AppsUtils.jsm',
|
||||
'ImportExport.jsm',
|
||||
'InterAppCommService.jsm',
|
||||
'OperatorApps.jsm',
|
||||
'ScriptPreloader.jsm',
|
||||
'TrustedHostedAppsUtils.jsm',
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Certified hosted app",
|
||||
"description": "An app that can't only be installed in dev mode.",
|
||||
"launch_path": "/tests/dom/apps/tests/file_app.sjs?apptype=hosted",
|
||||
"type": "certified"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Content-Type: application/manifest+json
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name" : "Version française.",
|
||||
"entry_points": {
|
||||
"dialer": {
|
||||
"name": "téléphone"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
"revision": 201411051234,
|
||||
"name": "Français",
|
||||
"apps": {
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/fr/"
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/fr/",
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_no_version.webapp": "tests/dom/apps/tests/langpack/fr/",
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_version_xyz.webapp": "tests/dom/apps/tests/langpack/fr/"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
"revision": 201411051234,
|
||||
"name": "Deutsch",
|
||||
"apps": {
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/de/"
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/de/",
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_no_version.webapp": "tests/dom/apps/tests/langpack/de/",
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_version_xyz.webapp": "tests/dom/apps/tests/langpack/de/"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"revision": 201411051234,
|
||||
"name": "Polski",
|
||||
"apps": {
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/pl/"
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/pl/",
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_no_version.webapp": "tests/dom/apps/tests/langpack/pl/",
|
||||
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_version_xyz.webapp": "tests/dom/apps/tests/langpack/pl/"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Langpack Test : localized values</title>
|
||||
<script>
|
||||
function success(data) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
alert(data);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function error(domError) {
|
||||
console.log(domError.name);
|
||||
return new Promise(function(resolve, reject) {
|
||||
alert(domError.name);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
var app;
|
||||
|
||||
function getApp() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var req = navigator.mozApps.getSelf();
|
||||
req.onsuccess = () => { app = req.result; resolve(); }
|
||||
req.onerror = () => { reject(req.error); }
|
||||
});
|
||||
}
|
||||
|
||||
// Success: unknown language, fallback to manifest.
|
||||
function test1() {
|
||||
return app.getLocalizedValue("name", "es")
|
||||
.then(success, error);
|
||||
}
|
||||
|
||||
// Error : unknown property.
|
||||
function test2() {
|
||||
return app.getLocalizedValue("foobar", "fr")
|
||||
.then(success, error);
|
||||
}
|
||||
|
||||
// Success, value from the langpack.
|
||||
function test3() {
|
||||
return app.getLocalizedValue("name", "fr")
|
||||
.then(success, error);
|
||||
}
|
||||
|
||||
// Success, value from the manifest's entry point.
|
||||
function test4() {
|
||||
return app.getLocalizedValue("name", "es", "dialer")
|
||||
.then(success, error);
|
||||
}
|
||||
|
||||
// Success, value from the langpack's entry point.
|
||||
function test5() {
|
||||
return app.getLocalizedValue("name", "fr", "dialer")
|
||||
.then(success, error);
|
||||
}
|
||||
|
||||
function run() {
|
||||
getApp().then(test1)
|
||||
.then(test2)
|
||||
.then(test3)
|
||||
.then(test4)
|
||||
.then(test5)
|
||||
.catch(error);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="run()">
|
||||
<h1>Langpack Test : localized values</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,9 @@
|
||||
{
|
||||
"name": "Localization test app"
|
||||
"name": "Localization test app",
|
||||
"version": "2.2",
|
||||
"entry_points": {
|
||||
"dialer": {
|
||||
"name": "dialer"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Localization test app",
|
||||
"entry_points": {
|
||||
"dialer": {
|
||||
"name": "dialer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Localization test app",
|
||||
"version": "2.2.1",
|
||||
"entry_points": {
|
||||
"dialer": {
|
||||
"name": "dialer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ support-files =
|
||||
file_cached_app.template.appcache
|
||||
file_cached_app.template.webapp
|
||||
file_hosted_app.template.webapp
|
||||
file_hosted_certified.webapp
|
||||
file_hosted_certified.webapp^headers^
|
||||
file_manifest.json
|
||||
file_manifest.json^headers^
|
||||
file_trusted_app.template.webapp
|
||||
@@ -39,7 +41,10 @@ skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in moch
|
||||
[test_app_update.html]
|
||||
skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
|
||||
[test_bug_795164.html]
|
||||
[test_bug_1168300.html]
|
||||
skip-if = toolkit == "gonk" # see bug 1175784
|
||||
[test_import_export.html]
|
||||
[test_install_dev_mode.html]
|
||||
[test_install_multiple_apps_origin.html]
|
||||
[test_install_receipts.html]
|
||||
[test_langpacks.html]
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1168300
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1168300</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
/** Test for Bug 1168300 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var url = "http://test/tests/dom/apps/tests/file_manifest.json";
|
||||
|
||||
var gGenerator = runTest();
|
||||
|
||||
function go() {
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "webapps-manage", "allow": 1, "context": document }],
|
||||
function() { gGenerator.next() });
|
||||
}
|
||||
|
||||
function continueTest() {
|
||||
try {
|
||||
gGenerator.next();
|
||||
} catch (e if e instanceof StopIteration) {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
function mozAppsError() {
|
||||
ok(false, "mozApps error: " + this.error.name);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
// Set up.
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
SpecialPowers.autoConfirmAppInstall(continueTest);
|
||||
yield undefined;
|
||||
|
||||
SpecialPowers.autoConfirmAppUninstall(continueTest);
|
||||
yield undefined;
|
||||
|
||||
let request = navigator.mozApps.install(url);
|
||||
request.onerror = mozAppsError;
|
||||
request.onsuccess = continueTest;
|
||||
yield undefined;
|
||||
let app = request.result;
|
||||
|
||||
let _topic = "clear-origin-data";
|
||||
let observer = new Observer(_topic);
|
||||
observer.onobserve = function(subject, topic, data, count) {
|
||||
ok(topic == _topic, "unknown topic " + topic);
|
||||
|
||||
let props = Object.getOwnPropertyNames(JSON.parse(data));
|
||||
is(props.length, 1, "pattern should have one property");
|
||||
is(props[0], 'appId', "pattern property should be appId");
|
||||
|
||||
observer.shutdown();
|
||||
continueTest();
|
||||
};
|
||||
|
||||
request = navigator.mozApps.mgmt.uninstall(app);
|
||||
request.onerror = mozAppsError;
|
||||
navigator.mozApps.mgmt.onuninstall = function(evt) {
|
||||
if (evt.application.manifestURL != url) {
|
||||
return;
|
||||
}
|
||||
|
||||
ok(true, "got onuninstall event");
|
||||
continueTest();
|
||||
};
|
||||
|
||||
// we now wait for "clear-origin-data" notifications and onuninstall
|
||||
// callback.
|
||||
yield undefined;
|
||||
yield undefined;
|
||||
|
||||
navigator.mozApps.mgmt.onuninstall = null;
|
||||
}
|
||||
|
||||
function Observer(topic) {
|
||||
this.topic = topic;
|
||||
SpecialPowers.addObserver(this, topic, false);
|
||||
}
|
||||
Observer.prototype = {
|
||||
topic: null,
|
||||
|
||||
count: 1,
|
||||
|
||||
callback: null,
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
if (this.callback) this.callback(subject, topic, data, this.count++);
|
||||
},
|
||||
|
||||
shutdown: function() {
|
||||
SpecialPowers.removeObserver(this, this.topic);
|
||||
},
|
||||
|
||||
set onobserve (callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
};
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
</head>
|
||||
<body onload="go()">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1168300">Mozilla Bug 1168300</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id={1111961}
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug {1111961}</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1111961}">Mozilla Bug {1111961}</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
||||
var gManifestURL = "http://test/tests/dom/apps/tests/file_hosted_certified.webapp";
|
||||
var gGenerator = runTest();
|
||||
|
||||
function go() {
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "webapps-manage", "allow": 1, "context": document }],
|
||||
function() { gGenerator.next() });
|
||||
}
|
||||
|
||||
function continueTest() {
|
||||
try {
|
||||
gGenerator.next();
|
||||
} catch (e if e instanceof StopIteration) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function cbError(aEvent) {
|
||||
ok(false, "Error callback invoked " +
|
||||
aEvent.target.error.name + " " + aEvent.target.error.message);
|
||||
finish();
|
||||
}
|
||||
|
||||
function cbSuccess(aMsg) {
|
||||
return function(aEvent) {
|
||||
ok(true, aMsg);
|
||||
continueTest();
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
/**
|
||||
* Install 2 apps from the same origin and uninstall them.
|
||||
*/
|
||||
function runTest() {
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
|
||||
SpecialPowers.autoConfirmAppInstall(continueTest);
|
||||
yield undefined;
|
||||
|
||||
SpecialPowers.autoConfirmAppUninstall(continueTest);
|
||||
yield undefined;
|
||||
|
||||
request = navigator.mozApps.mgmt.getAll();
|
||||
request.onerror = cbError;
|
||||
request.onsuccess = continueTest;
|
||||
yield undefined;
|
||||
var initialAppsCount = request.result.length;
|
||||
info("Starting with " + initialAppsCount + " apps installed.");
|
||||
|
||||
// We are not in dev mode, so this install will fail.
|
||||
var request = navigator.mozApps.install(gManifestURL, { });
|
||||
request.onerror = cbSuccess("Can't install certified app without dev mode");
|
||||
request.onsuccess = cbError;
|
||||
yield undefined;
|
||||
|
||||
// Turn on dev mode.
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.apps.developer_mode", true]]},
|
||||
continueTest);
|
||||
yield undefined;
|
||||
|
||||
// Installation should succeed now.
|
||||
request = navigator.mozApps.install(gManifestURL, { });
|
||||
request.onerror = cbError;
|
||||
request.onsuccess = cbSuccess("Install certified app in dev mode");;
|
||||
yield undefined;
|
||||
|
||||
// Uninstall and check we cleaned up.
|
||||
var app = request.result;
|
||||
|
||||
navigator.mozApps.mgmt.onuninstall = function(event) {
|
||||
var app = event.application;
|
||||
is(app.manifestURL, gManifestURL, "App uninstall event ok.");
|
||||
continueTest();
|
||||
}
|
||||
request = navigator.mozApps.mgmt.uninstall(app);
|
||||
request.onerror = cbError;
|
||||
request.onsuccess = continueTest;
|
||||
yield undefined;
|
||||
yield undefined;
|
||||
is(request.result, gManifestURL, "App uninstalled.");
|
||||
navigator.mozApps.mgmt.onuninstall = null;
|
||||
|
||||
request = navigator.mozApps.mgmt.getAll();
|
||||
request.onerror = cbError;
|
||||
request.onsuccess = continueTest;
|
||||
yield undefined;
|
||||
is(request.result.length, initialAppsCount, "All apps are uninstalled.");
|
||||
}
|
||||
|
||||
addLoadEvent(go);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -206,6 +206,101 @@ function runTest() {
|
||||
req.onerror = mozAppsError;
|
||||
yield undefined;
|
||||
}
|
||||
|
||||
// Test localized values with a x.y manifest version.
|
||||
installApp(appManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Install the fr langpack.
|
||||
installApp(lang1ManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Install the de and pl langpack.
|
||||
installApp(lang2ManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Opens the iframe to the localized values test page.
|
||||
openPage("localizedvalues.html",
|
||||
["Localization test app",
|
||||
"UnknownProperty",
|
||||
"Version française.",
|
||||
"dialer",
|
||||
"téléphone"]);
|
||||
yield undefined;
|
||||
|
||||
// Clean up after ourselves by uninstalling apps.
|
||||
info(apps.length + " applications to uninstall.");
|
||||
while (apps.length) {
|
||||
let app = apps.pop();
|
||||
req = navigator.mozApps.mgmt.uninstall(app);
|
||||
req.onsuccess = continueTest;
|
||||
req.onerror = mozAppsError;
|
||||
yield undefined;
|
||||
}
|
||||
|
||||
// Test localized values with versionless manifest.
|
||||
appManifestURL = uriPrefix + "manifest_no_version.webapp";
|
||||
installApp(appManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Install the fr langpack.
|
||||
installApp(lang1ManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Install the de and pl langpack.
|
||||
installApp(lang2ManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Opens the iframe to the localized values test page.
|
||||
openPage("localizedvalues.html",
|
||||
["Localization test app",
|
||||
"UnknownProperty",
|
||||
"Localization test app",
|
||||
"dialer",
|
||||
"dialer"]);
|
||||
yield undefined;
|
||||
|
||||
// Clean up after ourselves by uninstalling apps.
|
||||
info(apps.length + " applications to uninstall.");
|
||||
while (apps.length) {
|
||||
let app = apps.pop();
|
||||
req = navigator.mozApps.mgmt.uninstall(app);
|
||||
req.onsuccess = continueTest;
|
||||
req.onerror = mozAppsError;
|
||||
yield undefined;
|
||||
}
|
||||
|
||||
// Test localized values with a x.y.z manifest version.
|
||||
appManifestURL = uriPrefix + "manifest_version_xyz.webapp"
|
||||
installApp(appManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Install the fr langpack.
|
||||
installApp(lang1ManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Install the de and pl langpack.
|
||||
installApp(lang2ManifestURL);
|
||||
yield undefined;
|
||||
|
||||
// Opens the iframe to the localized values test page.
|
||||
openPage("localizedvalues.html",
|
||||
["Localization test app",
|
||||
"UnknownProperty",
|
||||
"Version française.",
|
||||
"dialer",
|
||||
"téléphone"]);
|
||||
yield undefined;
|
||||
|
||||
// Clean up after ourselves by uninstalling apps.
|
||||
info(apps.length + " applications to uninstall.");
|
||||
while (apps.length) {
|
||||
let app = apps.pop();
|
||||
req = navigator.mozApps.mgmt.uninstall(app);
|
||||
req.onsuccess = continueTest;
|
||||
req.onerror = mozAppsError;
|
||||
yield undefined;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -55,7 +55,7 @@ add_test(() => {
|
||||
Assert.equal(mozapp.principal.origin, expectedPrincipalOrigin,
|
||||
"app principal origin ok");
|
||||
Assert.equal(mozapp.principal.appId, app.localId, "app principal appId ok");
|
||||
Assert.equal(mozapp.principal.isInBrowserElement, app.installerIsBrowser,
|
||||
Assert.equal(mozapp.principal.isInBrowserElement, false,
|
||||
"app principal isInBrowserElement ok");
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
@@ -16,4 +16,5 @@ interface mozIApplicationClearPrivateDataParams : nsISupports
|
||||
|
||||
%{C++
|
||||
#define TOPIC_WEB_APP_CLEAR_DATA "webapps-clear-data"
|
||||
#define TOPIC_CLEAR_ORIGIN_DATA "clear-origin-data"
|
||||
%}
|
||||
|
||||
@@ -20,6 +20,7 @@ const RSYNC_STATE_ENABLED = "enabled";
|
||||
const RSYNC_STATE_DISABLED = "disabled";
|
||||
const RSYNC_STATE_WIFIONLY = "wifiOnly";
|
||||
|
||||
Cu.import("resource://gre/modules/BrowserUtils.jsm");
|
||||
Cu.import('resource://gre/modules/IndexedDBHelper.jsm');
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@@ -84,7 +85,7 @@ this.RequestSyncService = {
|
||||
}).bind(this));
|
||||
|
||||
Services.obs.addObserver(this, 'xpcom-shutdown', false);
|
||||
Services.obs.addObserver(this, 'webapps-clear-data', false);
|
||||
Services.obs.addObserver(this, 'clear-origin-data', false);
|
||||
Services.obs.addObserver(this, 'wifi-state-changed', false);
|
||||
|
||||
this.initDBHelper("requestSync", RSYNCDB_VERSION, [RSYNCDB_NAME]);
|
||||
@@ -120,7 +121,7 @@ this.RequestSyncService = {
|
||||
}).bind(this));
|
||||
|
||||
Services.obs.removeObserver(this, 'xpcom-shutdown');
|
||||
Services.obs.removeObserver(this, 'webapps-clear-data');
|
||||
Services.obs.removeObserver(this, 'clear-origin-data');
|
||||
Services.obs.removeObserver(this, 'wifi-state-changed');
|
||||
|
||||
this.close();
|
||||
@@ -141,8 +142,8 @@ this.RequestSyncService = {
|
||||
this.shutdown();
|
||||
break;
|
||||
|
||||
case 'webapps-clear-data':
|
||||
this.clearData(aSubject);
|
||||
case 'clear-origin-data':
|
||||
this.clearData(aData);
|
||||
break;
|
||||
|
||||
case 'wifi-state-changed':
|
||||
@@ -163,19 +164,12 @@ this.RequestSyncService = {
|
||||
return;
|
||||
}
|
||||
|
||||
let params =
|
||||
aData.QueryInterface(Ci.mozIApplicationClearPrivateDataParams);
|
||||
if (!params) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point we don't have the origin, so we cannot create the full
|
||||
// key. Using the partial one is enough to detect the uninstalled app.
|
||||
let partialKey = params.appId + '|' + params.browserOnly + '|';
|
||||
let pattern = JSON.parse(aData);
|
||||
let dbKeys = [];
|
||||
|
||||
for (let key in this._registrations) {
|
||||
if (key.indexOf(partialKey) != 0) {
|
||||
for (let key in this._registrations) {
|
||||
let prin = BrowserUtils.principalFromOrigin(key);
|
||||
if (!ChromeUtils.originAttributesMatchPattern(prin.originAttributes, pattern)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -210,9 +204,7 @@ this.RequestSyncService = {
|
||||
|
||||
// This method generates the key for the indexedDB object storage.
|
||||
principalToKey: function(aPrincipal) {
|
||||
return aPrincipal.appId + '|' +
|
||||
aPrincipal.isInBrowserElement + '|' +
|
||||
aPrincipal.origin;
|
||||
return aPrincipal.origin;
|
||||
},
|
||||
|
||||
// Add a task to the _registrations map and create the timer if it's needed.
|
||||
@@ -252,9 +244,8 @@ this.RequestSyncService = {
|
||||
debug('removeRegistrationInternal');
|
||||
|
||||
let obj = this._registrations[aKey][aTaskName];
|
||||
if (obj.timer) {
|
||||
obj.timer.cancel();
|
||||
}
|
||||
|
||||
this.removeTimer(obj);
|
||||
|
||||
// It can be that this task has been already schedulated.
|
||||
this.removeTaskFromQueue(obj);
|
||||
@@ -386,10 +377,7 @@ this.RequestSyncService = {
|
||||
|
||||
aData.params.overwrittenMinInterval = 0;
|
||||
|
||||
let dbKey = aData.task + "|" +
|
||||
aPrincipal.appId + '|' +
|
||||
aPrincipal.isInBrowserElement + '|' +
|
||||
aPrincipal.origin;
|
||||
let dbKey = aData.task + "|" + key;
|
||||
|
||||
let data = { principal: aPrincipal,
|
||||
dbKey: dbKey,
|
||||
@@ -805,7 +793,7 @@ this.RequestSyncService = {
|
||||
this._pendingOperation = false;
|
||||
|
||||
// managing the pending messages now that the initialization is completed.
|
||||
while (this._pendingMessages.length) {
|
||||
while (this._pendingMessages.length && !this._pendingOperation) {
|
||||
this.receiveMessage(this._pendingMessages.shift());
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,3 +17,4 @@ run-if = buildapp == 'b2g' && toolkit == 'gonk'
|
||||
[test_runNow.html]
|
||||
run-if = buildapp == 'b2g' && toolkit == 'gonk'
|
||||
[test_promise.html]
|
||||
[test_bug1151082.html]
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for RequestSync bug 1151082</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="common_basic.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
|
||||
var tests = [
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
|
||||
["dom.requestSync.minInterval", 1],
|
||||
["dom.ignore_webidl_scope_checks", true]]}, runTests);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "requestsync-manager", "allow": 1, "context": document } ], runTests);
|
||||
},
|
||||
|
||||
function() {
|
||||
if (SpecialPowers.isMainProcess()) {
|
||||
SpecialPowers.Cu.import("resource://gre/modules/RequestSyncService.jsm");
|
||||
}
|
||||
runTests();
|
||||
},
|
||||
|
||||
function() {
|
||||
counter = 2;
|
||||
function registerCb() {
|
||||
if (!--counter) {
|
||||
ok(true, "All the registrations are done.");
|
||||
runTests();
|
||||
}
|
||||
}
|
||||
|
||||
navigator.sync.register('foobar', { minInterval: 5, wakeUpPage:'/' }).then(registerCb, genericError);
|
||||
navigator.sync.register('barfoo', { minInterval: 5, wakeUpPage:'/' }).then(registerCb, genericError);
|
||||
},
|
||||
|
||||
function() {
|
||||
counter = 2;
|
||||
function unregisterCb() {
|
||||
if (!--counter) {
|
||||
ok(true, "All the unregistrations are done.");
|
||||
runTests();
|
||||
}
|
||||
}
|
||||
|
||||
navigator.sync.unregister('foobar').then(unregisterCb, genericError);
|
||||
navigator.sync.unregister('barfoo').then(unregisterCb, genericError);
|
||||
}
|
||||
];
|
||||
|
||||
function runTests() {
|
||||
if (!tests.length) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
test();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
runTests();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -54,6 +54,38 @@ function confirmNextPopup() {
|
||||
popupPanel.addEventListener("popupshown", onPopupShown, false);
|
||||
}
|
||||
|
||||
function promiseNoPopup() {
|
||||
var Ci = SpecialPowers.Ci;
|
||||
|
||||
var popupNotifications = SpecialPowers.wrap(window).top.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIWebNavigation).
|
||||
QueryInterface(Ci.nsIDocShell).
|
||||
chromeEventHandler.ownerDocument.defaultView.
|
||||
PopupNotifications;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
var tries = 0;
|
||||
var interval = setInterval(function() {
|
||||
if (tries >= 30) {
|
||||
ok(true, "The webapps-install notification didn't appear");
|
||||
moveOn();
|
||||
}
|
||||
|
||||
if (popupNotifications.getNotification("webapps-install")) {
|
||||
ok(false, "Found the webapps-install notification");
|
||||
moveOn();
|
||||
}
|
||||
tries++;
|
||||
}, 100);
|
||||
|
||||
var moveOn = () => {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// We need to mock the Alerts service, otherwise the alert that is shown
|
||||
// at the end of an installation makes the test leak the app's icon.
|
||||
|
||||
|
||||
@@ -24,40 +24,22 @@ SpecialPowers.setAllAppsLaunchable(true);
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PopupNotifications.jsm");
|
||||
|
||||
let blocked = true;
|
||||
|
||||
function blockedListener() {
|
||||
blocked = false;
|
||||
}
|
||||
|
||||
let panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler.ownerDocument.defaultView
|
||||
.PopupNotifications.panel;
|
||||
|
||||
panel.addEventListener("popupshowing", blockedListener, false);
|
||||
|
||||
Services.obs.addObserver(
|
||||
function observeInstalling() {
|
||||
Services.obs.removeObserver(observeInstalling, "webapps-ask-install");
|
||||
// Spin the event loop before running the test to give the registry time
|
||||
// to process the install request and (hopefully not) show the doorhanger.
|
||||
setTimeout(function verify() {
|
||||
ok(blocked, "Install panel was blocked after immediate redirect");
|
||||
panel.removeEventListener("popupshowing", blockedListener);
|
||||
SimpleTest.finish();
|
||||
}, 0);
|
||||
},
|
||||
"webapps-ask-install",
|
||||
false
|
||||
);
|
||||
|
||||
let PopupNotifications = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler.ownerDocument.defaultView
|
||||
.PopupNotifications;
|
||||
addEventListener("DOMContentLoaded", () => {
|
||||
let iframe = document.getElementById("iframe");
|
||||
iframe.addEventListener("load", function(e) {
|
||||
promiseNoPopup().then(SimpleTest.finish);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Load a page that initiates an app installation and then immediately
|
||||
- redirects to a page at a different origin. We can't do this directly
|
||||
- inside this test page, because that would cause the test to hang. -->
|
||||
<iframe src="http://test/chrome/dom/tests/mochitest/webapps/install_and_redirect_helper.xul"/>
|
||||
<iframe id="iframe" src="http://test/chrome/dom/tests/mochitest/webapps/install_and_redirect_helper.xul"/>
|
||||
|
||||
</window>
|
||||
|
||||
@@ -106,6 +106,12 @@ interface DOMApplication : EventTarget {
|
||||
|
||||
// Export this app as a shareable Blob.
|
||||
Promise<Blob> export();
|
||||
|
||||
// Returns the localized value of a property, using either the manifest or
|
||||
// a langpack if one is available.
|
||||
Promise<DOMString> getLocalizedValue(DOMString property,
|
||||
DOMString locale,
|
||||
optional DOMString entryPoint);
|
||||
};
|
||||
|
||||
[JSImplementation="@mozilla.org/webapps/manager;1",
|
||||
|
||||
@@ -46,11 +46,13 @@ interface ChromeUtils : ThreadSafeChromeUtils {
|
||||
*/
|
||||
dictionary OriginAttributesDictionary {
|
||||
unsigned long appId = 0;
|
||||
unsigned long userContextId = 0;
|
||||
boolean inBrowser = false;
|
||||
DOMString addonId = "";
|
||||
};
|
||||
dictionary OriginAttributesPatternDictionary {
|
||||
unsigned long appId;
|
||||
unsigned long userContextId;
|
||||
boolean inBrowser;
|
||||
DOMString addonId;
|
||||
};
|
||||
|
||||
@@ -4772,6 +4772,9 @@ pref("dom.idle-observers-api.fuzz_time.disabled", true);
|
||||
// Lowest localId for apps.
|
||||
pref("dom.mozApps.maxLocalId", 1000);
|
||||
|
||||
// Reset apps permissions
|
||||
pref("dom.apps.reset-permissions", false);
|
||||
|
||||
// XXX Security: You CANNOT safely add a new app store for
|
||||
// installing privileged apps just by modifying this pref and
|
||||
// adding the signing cert for that store to the cert trust
|
||||
|
||||
@@ -1206,7 +1206,24 @@ nsCORSPreflightListener::OnStartRequest(nsIRequest *aRequest,
|
||||
// Everything worked, try to cache and then fire off the actual request.
|
||||
AddResultToCache(aRequest);
|
||||
|
||||
rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = mOuterChannel->GetLoadInfo();
|
||||
MOZ_ASSERT(loadInfo, "can not perform CORS preflight without a loadInfo");
|
||||
if (!loadInfo) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
|
||||
|
||||
MOZ_ASSERT(securityMode == 0 ||
|
||||
securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
|
||||
"how did we end up here?");
|
||||
|
||||
if (securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
|
||||
MOZ_ASSERT(!mOuterContext, "AsyncOpen(2) does not take context as a second arg");
|
||||
rv = mOuterChannel->AsyncOpen2(mOuterListener);
|
||||
}
|
||||
else {
|
||||
rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
@@ -1284,6 +1301,18 @@ NS_StartCORSPreflight(nsIChannel* aRequestChannel,
|
||||
nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = aRequestChannel->GetLoadInfo();
|
||||
MOZ_ASSERT(loadInfo, "can not perform CORS preflight without a loadInfo");
|
||||
if (!loadInfo) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
|
||||
|
||||
MOZ_ASSERT(securityMode == 0 ||
|
||||
securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
|
||||
"how did we end up here?");
|
||||
|
||||
nsPreflightCache::CacheEntry* entry =
|
||||
sPreflightCache ?
|
||||
sPreflightCache->GetEntry(uri, aPrincipal, aWithCredentials, false) :
|
||||
@@ -1291,6 +1320,9 @@ NS_StartCORSPreflight(nsIChannel* aRequestChannel,
|
||||
|
||||
if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
|
||||
// We have a cached preflight result, just start the original channel
|
||||
if (securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
|
||||
return aRequestChannel->AsyncOpen2(aListener);
|
||||
}
|
||||
return aRequestChannel->AsyncOpen(aListener, nullptr);
|
||||
}
|
||||
|
||||
@@ -1305,29 +1337,13 @@ NS_StartCORSPreflight(nsIChannel* aRequestChannel,
|
||||
rv = aRequestChannel->GetLoadFlags(&loadFlags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsILoadInfo> loadInfo;
|
||||
rv = aRequestChannel->GetLoadInfo(getter_AddRefs(loadInfo));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIChannel> preflightChannel;
|
||||
if (loadInfo) {
|
||||
rv = NS_NewChannelInternal(getter_AddRefs(preflightChannel),
|
||||
uri,
|
||||
loadInfo,
|
||||
loadGroup,
|
||||
nullptr, // aCallbacks
|
||||
loadFlags);
|
||||
}
|
||||
else {
|
||||
rv = NS_NewChannel(getter_AddRefs(preflightChannel),
|
||||
uri,
|
||||
nsContentUtils::GetSystemPrincipal(),
|
||||
nsILoadInfo::SEC_NORMAL,
|
||||
nsIContentPolicy::TYPE_OTHER,
|
||||
loadGroup,
|
||||
nullptr, // aCallbacks
|
||||
loadFlags);
|
||||
}
|
||||
rv = NS_NewChannelInternal(getter_AddRefs(preflightChannel),
|
||||
uri,
|
||||
loadInfo,
|
||||
loadGroup,
|
||||
nullptr, // aCallbacks
|
||||
loadFlags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel);
|
||||
@@ -1353,16 +1369,19 @@ NS_StartCORSPreflight(nsIChannel* aRequestChannel,
|
||||
method, aWithCredentials);
|
||||
NS_ENSURE_TRUE(preflightListener, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
nsRefPtr<nsCORSListenerProxy> corsListener =
|
||||
new nsCORSListenerProxy(preflightListener, aPrincipal,
|
||||
aWithCredentials, method,
|
||||
aUnsafeHeaders);
|
||||
rv = corsListener->Init(preflightChannel, DataURIHandling::Disallow);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
preflightListener = corsListener;
|
||||
|
||||
// Start preflight
|
||||
rv = preflightChannel->AsyncOpen(preflightListener, nullptr);
|
||||
if (securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
|
||||
rv = preflightChannel->AsyncOpen2(preflightListener);
|
||||
}
|
||||
else {
|
||||
nsRefPtr<nsCORSListenerProxy> corsListener =
|
||||
new nsCORSListenerProxy(preflightListener, aPrincipal,
|
||||
aWithCredentials, method,
|
||||
aUnsafeHeaders);
|
||||
rv = corsListener->Init(preflightChannel, DataURIHandling::Disallow);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = preflightChannel->AsyncOpen(corsListener, nullptr);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Return newly created preflight channel
|
||||
|
||||
@@ -21,28 +21,29 @@ let { TabActor } = require("devtools/server/actors/webbrowser");
|
||||
* The conection to the client.
|
||||
* @param chromeGlobal
|
||||
* The content script global holding |content| and |docShell| properties for a tab.
|
||||
* @param prefix
|
||||
* the prefix used in protocol to create IDs for each actor.
|
||||
* Used as ID identifying this particular TabActor from the parent process.
|
||||
*/
|
||||
function ContentActor(connection, chromeGlobal)
|
||||
function ContentActor(connection, chromeGlobal, prefix)
|
||||
{
|
||||
this._chromeGlobal = chromeGlobal;
|
||||
this._prefix = prefix;
|
||||
TabActor.call(this, connection, chromeGlobal);
|
||||
this.traits.reconfigure = false;
|
||||
this._sendForm = this._sendForm.bind(this);
|
||||
this._chromeGlobal.addMessageListener("debug:form", this._sendForm);
|
||||
|
||||
Object.defineProperty(this, "docShell", {
|
||||
value: this._chromeGlobal.docShell,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
ContentActor.prototype = Object.create(TabActor.prototype);
|
||||
|
||||
ContentActor.prototype.constructor = ContentActor;
|
||||
|
||||
Object.defineProperty(ContentActor.prototype, "docShell", {
|
||||
get: function() {
|
||||
return this._chromeGlobal.docShell;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(ContentActor.prototype, "title", {
|
||||
get: function() {
|
||||
return this.window.document.title;
|
||||
@@ -52,32 +53,11 @@ Object.defineProperty(ContentActor.prototype, "title", {
|
||||
});
|
||||
|
||||
ContentActor.prototype.exit = function() {
|
||||
this._chromeGlobal.removeMessageListener("debug:form", this._sendForm);
|
||||
this._sendForm = null;
|
||||
TabActor.prototype.exit.call(this);
|
||||
};
|
||||
|
||||
// Override form just to rename this._tabActorPool to this._tabActorPool2
|
||||
// in order to prevent it to be cleaned on detach.
|
||||
// We have to keep tab actors alive as we keep the ContentActor
|
||||
// alive after detach and reuse it for multiple debug sessions.
|
||||
ContentActor.prototype.form = function () {
|
||||
let response = {
|
||||
"actor": this.actorID,
|
||||
"title": this.title,
|
||||
"url": this.url
|
||||
};
|
||||
|
||||
// Walk over tab actors added by extensions and add them to a new ActorPool.
|
||||
let actorPool = new ActorPool(this.conn);
|
||||
this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
|
||||
if (!actorPool.isEmpty()) {
|
||||
this._tabActorPool2 = actorPool;
|
||||
this.conn.addActorPool(this._tabActorPool2);
|
||||
if (this._sendForm) {
|
||||
this._chromeGlobal.removeMessageListener("debug:form", this._sendForm);
|
||||
this._sendForm = null;
|
||||
}
|
||||
|
||||
this._appendExtraActors(response);
|
||||
return response;
|
||||
return TabActor.prototype.exit.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,7 +111,7 @@ const DirectorRegistry = exports.DirectorRegistry = {
|
||||
|
||||
let gTrackedMessageManager = new Set();
|
||||
|
||||
exports.setupParentProcess = function setupParentProcess({mm, childID}) {
|
||||
exports.setupParentProcess = function setupParentProcess({mm, prefix}) {
|
||||
// prevents multiple subscriptions on the same messagemanager
|
||||
if (gTrackedMessageManager.has(mm)) {
|
||||
return;
|
||||
@@ -121,7 +121,7 @@ exports.setupParentProcess = function setupParentProcess({mm, childID}) {
|
||||
// listen for director-script requests from the child process
|
||||
mm.addMessageListener("debug:director-registry-request", handleChildRequest);
|
||||
|
||||
DebuggerServer.once("disconnected-from-child:" + childID, handleMessageManagerDisconnected);
|
||||
DebuggerServer.once("disconnected-from-child:" + prefix, handleMessageManagerDisconnected);
|
||||
|
||||
/* parent process helpers */
|
||||
|
||||
|
||||
@@ -196,6 +196,11 @@ RootActor.prototype = {
|
||||
this._parameters.onShutdown();
|
||||
}
|
||||
this._extraActors = null;
|
||||
this.conn = null;
|
||||
this._tabActorPool = null;
|
||||
this._globalActorPool = null;
|
||||
this._parameters = null;
|
||||
this._chromeActor = null;
|
||||
},
|
||||
|
||||
/* The 'listTabs' request and the 'tabListChanged' notification. */
|
||||
|
||||
@@ -212,10 +212,14 @@ function WebappsActor(aConnection) {
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/MessageBroadcaster.jsm");
|
||||
|
||||
// Keep reference of already created app actors.
|
||||
// key: app frame message manager, value: ContentActor's grip() value
|
||||
this._appActorsMap = new Map();
|
||||
this.appsChild = {};
|
||||
Cu.import("resource://gre/modules/AppsServiceChild.jsm", this.appsChild);
|
||||
|
||||
// Keep reference of already connected app processes.
|
||||
// values: app frame message manager
|
||||
this._connectedApps = new Set();
|
||||
|
||||
this.conn = aConnection;
|
||||
this._uploads = [];
|
||||
@@ -289,17 +293,17 @@ WebappsActor.prototype = {
|
||||
// Needed to evict manifest cache on content side
|
||||
// (has to be dispatched first, otherwise other messages like
|
||||
// Install:Return:OK are going to use old manifest version)
|
||||
reg.broadcastMessage("Webapps:UpdateState", {
|
||||
MessageBroadcaster.broadcastMessage("Webapps:UpdateState", {
|
||||
app: aApp,
|
||||
manifest: manifest,
|
||||
id: aApp.id
|
||||
});
|
||||
reg.broadcastMessage("Webapps:FireEvent", {
|
||||
MessageBroadcaster.broadcastMessage("Webapps:FireEvent", {
|
||||
eventType: ["downloadsuccess", "downloadapplied"],
|
||||
manifestURL: aApp.manifestURL
|
||||
});
|
||||
reg.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp });
|
||||
reg.broadcastMessage("Webapps:Install:Return:OK", {
|
||||
MessageBroadcaster.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp });
|
||||
MessageBroadcaster.broadcastMessage("Webapps:Install:Return:OK", {
|
||||
app: aApp,
|
||||
oid: "foo",
|
||||
requestID: "bar"
|
||||
@@ -687,8 +691,7 @@ WebappsActor.prototype = {
|
||||
debug("getAll");
|
||||
|
||||
let deferred = promise.defer();
|
||||
let reg = DOMApplicationRegistry;
|
||||
reg.getAll(apps => {
|
||||
this.appsChild.DOMApplicationRegistry.getAll(apps => {
|
||||
deferred.resolve({ apps: this._filterAllowedApps(apps) });
|
||||
});
|
||||
|
||||
@@ -960,24 +963,33 @@ WebappsActor.prototype = {
|
||||
|
||||
// Only create a new actor, if we haven't already
|
||||
// instanciated one for this connection.
|
||||
let map = this._appActorsMap;
|
||||
let set = this._connectedApps;
|
||||
let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader
|
||||
.messageManager;
|
||||
let actor = map.get(mm);
|
||||
if (!actor) {
|
||||
if (!set.has(mm)) {
|
||||
let onConnect = actor => {
|
||||
map.set(mm, actor);
|
||||
set.add(mm);
|
||||
return { actor: actor };
|
||||
};
|
||||
let onDisconnect = mm => {
|
||||
map.delete(mm);
|
||||
set.delete(mm);
|
||||
};
|
||||
return DebuggerServer.connectToChild(this.conn, appFrame, onDisconnect)
|
||||
.then(onConnect);
|
||||
}
|
||||
|
||||
return { actor: actor };
|
||||
// We have to update the form as it may have changed
|
||||
// if we detached the TabActor
|
||||
let deferred = promise.defer();
|
||||
let onFormUpdate = msg => {
|
||||
mm.removeMessageListener("debug:form", onFormUpdate);
|
||||
deferred.resolve({ actor: msg.json });
|
||||
};
|
||||
mm.addMessageListener("debug:form", onFormUpdate);
|
||||
mm.sendAsyncMessage("debug:form");
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
watchApps: function () {
|
||||
|
||||
@@ -799,10 +799,7 @@ TabActor.prototype = {
|
||||
* Called when the actor is removed from the connection.
|
||||
*/
|
||||
disconnect: function BTA_disconnect() {
|
||||
this._detach();
|
||||
this._extraActors = null;
|
||||
this._styleSheetActors.clear();
|
||||
this._exited = true;
|
||||
this.exit();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -824,6 +821,14 @@ TabActor.prototype = {
|
||||
type: "tabDetached" });
|
||||
}
|
||||
|
||||
Object.defineProperty(this, "docShell", {
|
||||
value: null,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
this._extraActors = null;
|
||||
this._styleSheetActors.clear();
|
||||
|
||||
this._exited = true;
|
||||
},
|
||||
|
||||
@@ -1635,24 +1640,17 @@ function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
|
||||
TabActor.call(this, aConnection, aBrowser);
|
||||
this._browser = aBrowser;
|
||||
this._tabbrowser = aTabBrowser;
|
||||
|
||||
Object.defineProperty(this, "docShell", {
|
||||
value: this._browser.docShell,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
BrowserTabActor.prototype = Object.create(TabActor.prototype);
|
||||
|
||||
BrowserTabActor.prototype.constructor = BrowserTabActor;
|
||||
|
||||
Object.defineProperty(BrowserTabActor.prototype, "docShell", {
|
||||
get: function() {
|
||||
if (this._browser) {
|
||||
return this._browser.docShell;
|
||||
}
|
||||
// The tab is closed.
|
||||
return null;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(BrowserTabActor.prototype, "title", {
|
||||
get: function() {
|
||||
// On Fennec, we can check the session store data for zombie tabs
|
||||
@@ -1735,7 +1733,10 @@ function RemoteBrowserTabActor(aConnection, aBrowser)
|
||||
|
||||
RemoteBrowserTabActor.prototype = {
|
||||
connect: function() {
|
||||
let connect = DebuggerServer.connectToChild(this._conn, this._browser);
|
||||
let onDestroy = () => {
|
||||
this._form = null;
|
||||
};
|
||||
let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy);
|
||||
return connect.then(form => {
|
||||
this._form = form;
|
||||
return this;
|
||||
@@ -1748,15 +1749,21 @@ RemoteBrowserTabActor.prototype = {
|
||||
},
|
||||
|
||||
update: function() {
|
||||
let deferred = promise.defer();
|
||||
let onFormUpdate = msg => {
|
||||
this._mm.removeMessageListener("debug:form", onFormUpdate);
|
||||
this._form = msg.json;
|
||||
deferred.resolve(this);
|
||||
};
|
||||
this._mm.addMessageListener("debug:form", onFormUpdate);
|
||||
this._mm.sendAsyncMessage("debug:form");
|
||||
return deferred.promise;
|
||||
// If the child happens to be crashed/close/detach, it won't have _form set,
|
||||
// so only request form update if some code is still listening on the other side.
|
||||
if (this._form) {
|
||||
let deferred = promise.defer();
|
||||
let onFormUpdate = msg => {
|
||||
this._mm.removeMessageListener("debug:form", onFormUpdate);
|
||||
this._form = msg.json;
|
||||
deferred.resolve(this);
|
||||
};
|
||||
this._mm.addMessageListener("debug:form", onFormUpdate);
|
||||
this._mm.sendAsyncMessage("debug:form");
|
||||
return deferred.promise;
|
||||
} else {
|
||||
return this.connect();
|
||||
}
|
||||
},
|
||||
|
||||
form: function() {
|
||||
|
||||
@@ -17,10 +17,6 @@ let chromeGlobal = this;
|
||||
const { dumpn } = DevToolsUtils;
|
||||
const { DebuggerServer, ActorPool } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
|
||||
if (!DebuggerServer.childID) {
|
||||
DebuggerServer.childID = 1;
|
||||
}
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
|
||||
@@ -44,17 +40,16 @@ let chromeGlobal = this;
|
||||
|
||||
let mm = msg.target;
|
||||
let prefix = msg.data.prefix;
|
||||
let id = DebuggerServer.childID++;
|
||||
|
||||
let conn = DebuggerServer.connectToParent(prefix, mm);
|
||||
connections.set(id, conn);
|
||||
connections.set(prefix, conn);
|
||||
|
||||
let actor = new DebuggerServer.ContentActor(conn, chromeGlobal);
|
||||
let actor = new DebuggerServer.ContentActor(conn, chromeGlobal, prefix);
|
||||
let actorPool = new ActorPool(conn);
|
||||
actorPool.addActor(actor);
|
||||
conn.addActorPool(actorPool);
|
||||
|
||||
sendAsyncMessage("debug:actor", {actor: actor.form(), childID: id});
|
||||
sendAsyncMessage("debug:actor", {actor: actor.form(), prefix: prefix});
|
||||
});
|
||||
|
||||
addMessageListener("debug:connect", onConnect);
|
||||
@@ -95,11 +90,11 @@ let chromeGlobal = this;
|
||||
// Call DebuggerServerConnection.close to destroy all child actors
|
||||
// (It should end up calling DebuggerServerConnection.onClosed
|
||||
// that would actually cleanup all actor pools)
|
||||
let childID = msg.data.childID;
|
||||
let conn = connections.get(childID);
|
||||
let prefix = msg.data.prefix;
|
||||
let conn = connections.get(prefix);
|
||||
if (conn) {
|
||||
conn.close();
|
||||
connections.delete(childID);
|
||||
connections.delete(prefix);
|
||||
}
|
||||
});
|
||||
addMessageListener("debug:disconnect", onDisconnect);
|
||||
|
||||
@@ -76,7 +76,7 @@ connected to the child process as parameter, e.g. in the **director-registry**:
|
||||
|
||||
let gTrackedMessageManager = new Set();
|
||||
|
||||
exports.setupParentProcess = function setupParentProcess({ mm, childID }) {
|
||||
exports.setupParentProcess = function setupParentProcess({ mm, prefix }) {
|
||||
if (gTrackedMessageManager.has(mm)) { return; }
|
||||
gTrackedMessageManager.add(mm);
|
||||
|
||||
@@ -84,7 +84,7 @@ exports.setupParentProcess = function setupParentProcess({ mm, childID }) {
|
||||
mm.addMessageListener("debug:director-registry-request", handleChildRequest);
|
||||
|
||||
// time to unsubscribe from the disconnected message manager
|
||||
DebuggerServer.once("disconnected-from-child:" + childID, handleMessageManagerDisconnected);
|
||||
DebuggerServer.once("disconnected-from-child:" + prefix, handleMessageManagerDisconnected);
|
||||
|
||||
function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) {
|
||||
...
|
||||
@@ -109,8 +109,8 @@ In the child process:
|
||||
In the parent process:
|
||||
- The DebuggerServer receives the DebuggerServer.setupInParent request
|
||||
- it tries to load the required module
|
||||
- it tries to call the **mod[setupParent]** method with the frame message manager and the childID
|
||||
in the json parameter **{ mm, childID }**
|
||||
- it tries to call the **mod[setupParent]** method with the frame message manager and the prefix
|
||||
in the json parameter **{ mm, prefix }**
|
||||
- the module setupParent helper use the mm to subscribe the messagemanager events
|
||||
- the module setupParent helper use the DebuggerServer object to subscribe *once* the
|
||||
**"disconnected-from-child:CHILDID"** event (needed to unsubscribe the messagemanager events)
|
||||
**"disconnected-from-child:PREFIX"** event (needed to unsubscribe the messagemanager events)
|
||||
|
||||
@@ -832,13 +832,15 @@ var DebuggerServer = {
|
||||
* The debugger server connection to use.
|
||||
* @param nsIDOMElement aFrame
|
||||
* The browser element that holds the child process.
|
||||
* @param function [aOnDisconnect]
|
||||
* Optional function to invoke when the child is disconnected.
|
||||
* @param function [aOnDestroy]
|
||||
* Optional function to invoke when the child process closes
|
||||
* or the connection shuts down. (Need to forget about the
|
||||
* related TabActor)
|
||||
* @return object
|
||||
* A promise object that is resolved once the connection is
|
||||
* established.
|
||||
*/
|
||||
connectToChild: function(aConnection, aFrame, aOnDisconnect) {
|
||||
connectToChild: function(aConnection, aFrame, aOnDestroy) {
|
||||
let deferred = defer();
|
||||
|
||||
let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader
|
||||
@@ -847,7 +849,6 @@ var DebuggerServer = {
|
||||
|
||||
let actor, childTransport;
|
||||
let prefix = aConnection.allocID("child");
|
||||
let childID = null;
|
||||
let netMonitor = null;
|
||||
|
||||
// provides hook to actor modules that need to exchange messages
|
||||
@@ -864,7 +865,7 @@ var DebuggerServer = {
|
||||
return false;
|
||||
}
|
||||
|
||||
m[setupParent]({ mm: mm, childID: childID });
|
||||
m[setupParent]({ mm: mm, prefix: prefix });
|
||||
|
||||
return true;
|
||||
} catch(e) {
|
||||
@@ -878,10 +879,11 @@ var DebuggerServer = {
|
||||
mm.addMessageListener("debug:setup-in-parent", onSetupInParent);
|
||||
|
||||
let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
|
||||
if (msg.json.prefix != prefix) {
|
||||
return;
|
||||
}
|
||||
mm.removeMessageListener("debug:actor", onActorCreated);
|
||||
|
||||
childID = msg.json.childID;
|
||||
|
||||
// Pipe Debugger message from/to parent/child via the message manager
|
||||
childTransport = new ChildDebuggerTransport(mm, prefix);
|
||||
childTransport.hooks = {
|
||||
@@ -905,70 +907,65 @@ var DebuggerServer = {
|
||||
}).bind(this);
|
||||
mm.addMessageListener("debug:actor", onActorCreated);
|
||||
|
||||
let onMessageManagerClose = DevToolsUtils.makeInfallible(function (subject, topic, data) {
|
||||
if (subject == mm) {
|
||||
Services.obs.removeObserver(onMessageManagerClose, topic);
|
||||
let destroy = DevToolsUtils.makeInfallible(function () {
|
||||
// provides hook to actor modules that need to exchange messages
|
||||
// between e10s parent and child processes
|
||||
DebuggerServer.emit("disconnected-from-child:" + prefix, { mm: mm, prefix: prefix });
|
||||
|
||||
// provides hook to actor modules that need to exchange messages
|
||||
// between e10s parent and child processes
|
||||
this.emit("disconnected-from-child:" + childID, { mm: mm, childID: childID });
|
||||
|
||||
mm.removeMessageListener("debug:setup-in-parent", onSetupInParent);
|
||||
|
||||
if (childTransport) {
|
||||
// If we have a child transport, the actor has already
|
||||
// been created. We need to stop using this message manager.
|
||||
childTransport.close();
|
||||
childTransport = null;
|
||||
aConnection.cancelForwarding(prefix);
|
||||
|
||||
// ... and notify the child process to clean the tab actors.
|
||||
mm.sendAsyncMessage("debug:disconnect", { childID: childID });
|
||||
} else {
|
||||
// Otherwise, the app has been closed before the actor
|
||||
// had a chance to be created, so we are not able to create
|
||||
// the actor.
|
||||
deferred.resolve(null);
|
||||
}
|
||||
if (actor) {
|
||||
// The ContentActor within the child process doesn't necessary
|
||||
// have to time to uninitialize itself when the app is closed/killed.
|
||||
// So ensure telling the client that the related actor is detached.
|
||||
aConnection.send({ from: actor.actor, type: "tabDetached" });
|
||||
actor = null;
|
||||
}
|
||||
|
||||
if (netMonitor) {
|
||||
netMonitor.destroy();
|
||||
netMonitor = null;
|
||||
}
|
||||
|
||||
if (aOnDisconnect) {
|
||||
aOnDisconnect(mm);
|
||||
}
|
||||
}
|
||||
}).bind(this);
|
||||
Services.obs.addObserver(onMessageManagerClose,
|
||||
"message-manager-close", false);
|
||||
|
||||
events.once(aConnection, "closed", () => {
|
||||
if (childTransport) {
|
||||
// When the client disconnects, we have to unplug the dedicated
|
||||
// ChildDebuggerTransport...
|
||||
// If we have a child transport, the actor has already
|
||||
// been created. We need to stop using this message manager.
|
||||
childTransport.close();
|
||||
childTransport = null;
|
||||
aConnection.cancelForwarding(prefix);
|
||||
|
||||
// ... and notify the child process to clean the tab actors.
|
||||
mm.sendAsyncMessage("debug:disconnect", { childID: childID });
|
||||
|
||||
if (netMonitor) {
|
||||
netMonitor.destroy();
|
||||
netMonitor = null;
|
||||
}
|
||||
mm.sendAsyncMessage("debug:disconnect", { prefix: prefix });
|
||||
} else {
|
||||
// Otherwise, the app has been closed before the actor
|
||||
// had a chance to be created, so we are not able to create
|
||||
// the actor.
|
||||
deferred.resolve(null);
|
||||
}
|
||||
if (actor) {
|
||||
// The ContentActor within the child process doesn't necessary
|
||||
// have time to uninitialize itself when the app is closed/killed.
|
||||
// So ensure telling the client that the related actor is detached.
|
||||
aConnection.send({ from: actor.actor, type: "tabDetached" });
|
||||
actor = null;
|
||||
}
|
||||
|
||||
if (netMonitor) {
|
||||
netMonitor.destroy();
|
||||
netMonitor = null;
|
||||
}
|
||||
|
||||
if (aOnDestroy) {
|
||||
aOnDestroy(mm);
|
||||
}
|
||||
|
||||
// Cleanup all listeners
|
||||
Services.obs.removeObserver(onMessageManagerClose, "message-manager-close");
|
||||
mm.removeMessageListener("debug:setup-in-parent", onSetupInParent);
|
||||
if (!actor) {
|
||||
mm.removeMessageListener("debug:actor", onActorCreated);
|
||||
}
|
||||
events.off(aConnection, "closed", destroy);
|
||||
});
|
||||
|
||||
// Listen for app process exit
|
||||
let onMessageManagerClose = function (subject, topic, data) {
|
||||
if (subject == mm) {
|
||||
destroy();
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(onMessageManagerClose,
|
||||
"message-manager-close", false);
|
||||
|
||||
// Listen for connection close to cleanup things
|
||||
// when user unplug the device or we lose the connection somehow.
|
||||
events.on(aConnection, "closed", destroy);
|
||||
|
||||
mm.sendAsyncMessage("debug:connect", { prefix: prefix });
|
||||
|
||||
return deferred.promise;
|
||||
@@ -1576,6 +1573,8 @@ DebuggerServerConnection.prototype = {
|
||||
this._extraPools.map(function(p) { p.cleanup(); });
|
||||
this._extraPools = null;
|
||||
|
||||
this.rootActor = null;
|
||||
this._transport = null;
|
||||
DebuggerServer._connectionClosed(this);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user