diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index c92156a382ac..7e6b2a0a5a01 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 5bedf541a9a4..bb2805d6eba5 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 5e8a59e6817a..50a1b5af94d3 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index c92156a382ac..7e6b2a0a5a01 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index eb3d067b59d1..ab8b138220f4 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index a7307d0a9e1b..3f005878d5d4 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "333f877ae4029d7cb1a0893f89da484d8e3cc14f", + "revision": "278dd1b102a39cf2c48f11fe3038eaf8f0779d7d", "repo_path": "/integration/gaia-central" } diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 2049f347b1e9..f203dc5e4db4 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 5f9fb059b432..907cdcb506bf 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 38f931fe7077..53f55c662d49 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index 6854c739fabb..29acd40091f9 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 7cf0b3ff2407..139078e538a1 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -405,6 +405,8 @@ @BINPATH@/components/DOMWifiManager.manifest @BINPATH@/components/DOMWifiP2pManager.js @BINPATH@/components/DOMWifiP2pManager.manifest +@BINPATH@/components/EthernetManager.js +@BINPATH@/components/EthernetManager.manifest @BINPATH@/components/NetworkInterfaceListService.js @BINPATH@/components/NetworkInterfaceListService.manifest @BINPATH@/components/NetworkManager.js diff --git a/dom/apps/src/AppsServiceChild.jsm b/dom/apps/src/AppsServiceChild.jsm index c7cb9890f768..5bf6a5aad746 100644 --- a/dom/apps/src/AppsServiceChild.jsm +++ b/dom/apps/src/AppsServiceChild.jsm @@ -8,10 +8,10 @@ const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; -// This module exposes a subset of the functionnalities of the parent DOM -// Registry to content processes, to be be used from the AppsService component. +// This module exposes a subset of the functionalities of the parent DOM +// Registry to content processes, to be used from the AppsService component. -this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"]; +this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"]; Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -20,54 +20,323 @@ function debug(s) { //dump("-*- AppsServiceChild.jsm: " + s + "\n"); } +const APPS_IPC_MSG_NAMES = [ + "Webapps:AddApp", + "Webapps:RemoveApp", + "Webapps:UpdateApp", + "Webapps:CheckForUpdate:Return:KO", + "Webapps:FireEvent", + "Webapps:UpdateState" +]; + +// A simple cache for the wrapped manifests. +this.WrappedManifestCache = { + _cache: { }, + + // Gets an entry from the cache, and populates the cache if needed. + get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) { + if (!aManifest) { + return; + } + + if (!(aManifestURL in this._cache)) { + this._cache[aManifestURL] = { }; + } + + let winObjs = this._cache[aManifestURL]; + if (!(aInnerWindowID in winObjs)) { + winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow); + } + + return winObjs[aInnerWindowID]; + }, + + // Invalidates an entry in the cache. + evict: function mcache_evict(aManifestURL, aInnerWindowID) { + debug("Evicting manifest " + aManifestURL + " window ID " + + aInnerWindowID); + if (aManifestURL in this._cache) { + let winObjs = this._cache[aManifestURL]; + if (aInnerWindowID in winObjs) { + delete winObjs[aInnerWindowID]; + } + + if (Object.keys(winObjs).length == 0) { + delete this._cache[aManifestURL]; + } + } + }, + + observe: function(aSubject, aTopic, aData) { + // Clear the cache on memory pressure. + this._cache = { }; + Cu.forceGC(); + }, + + init: function() { + Services.obs.addObserver(this, "memory-pressure", false); + } +}; + +this.WrappedManifestCache.init(); + + +// DOMApplicationRegistry keeps a cache containing a list of apps in the device. +// This information is updated with the data received from the main process and +// it is queried by the DOM objects to set their state. +// This module handle all the messages broadcasted from the parent process, +// including DOM events, which are dispatched to the corresponding DOM objects. + this.DOMApplicationRegistry = { + // DOMApps will hold a list of arrays of weak references to + // mozIDOMApplication objects indexed by manifest URL. + DOMApps: {}, + + ready: false, + webapps: null, + init: function init() { - debug("init"); this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] .getService(Ci.nsISyncMessageSender); - ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) { + APPS_IPC_MSG_NAMES.forEach((function(aMsgName) { this.cpmm.addMessageListener(aMsgName, this); }).bind(this)); - // We need to prime the cache with the list of apps. - // XXX shoud we do this async and block callers if it's not yet there? - this.webapps = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0]; + this.cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { + messages: APPS_IPC_MSG_NAMES + }); + // We need to prime the cache with the list of apps. + let list = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0]; + this.webapps = list.webapps; // We need a fast mapping from localId -> app, so we add an index. + // We also add the manifest to the app object. this.localIdIndex = { }; for (let id in this.webapps) { let app = this.webapps[id]; this.localIdIndex[app.localId] = app; + app.manifest = list.manifests[id]; } Services.obs.addObserver(this, "xpcom-shutdown", false); }, observe: function(aSubject, aTopic, aData) { - // cpmm.addMessageListener causes the DOMApplicationRegistry object to live - // forever if we don't clean up properly. + // cpmm.addMessageListener causes the DOMApplicationRegistry object to + // live forever if we don't clean up properly. this.webapps = null; - ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) { + this.DOMApps = null; + + APPS_IPC_MSG_NAMES.forEach((aMsgName) => { this.cpmm.removeMessageListener(aMsgName, this); - }).bind(this)); + }); + + this.cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", + APPS_IPC_MSG_NAMES) }, receiveMessage: function receiveMessage(aMessage) { debug("Received " + aMessage.name + " message."); - let msg = aMessage.json; + let msg = aMessage.data; switch (aMessage.name) { case "Webapps:AddApp": this.webapps[msg.id] = msg.app; this.localIdIndex[msg.app.localId] = msg.app; + if (msg.manifest) { + this.webapps[msg.id].manifest = msg.manifest; + } break; case "Webapps:RemoveApp": + delete this.DOMApps[this.webapps[msg.id].manifestURL]; delete this.localIdIndex[this.webapps[msg.id].localId]; delete this.webapps[msg.id]; break; + case "Webapps:UpdateApp": + let app = this.webapps[msg.oldId]; + if (!app) { + return; + } + + if (msg.app) { + for (let prop in msg.app) { + app[prop] = msg.app[prop]; + } + } + + this.webapps[msg.newId] = app; + this.localIdIndex[app.localId] = app; + delete this.webapps[msg.oldId]; + + let apps = this.DOMApps[msg.app.manifestURL]; + if (!apps) { + return; + } + for (let i = 0; i < apps.length; i++) { + let domApp = apps[i].get(); + if (!domApp) { + apps.splice(i); + continue; + } + domApp._proxy = new Proxy(domApp, { + get: function(target, prop) { + if (!DOMApplicationRegistry.webapps[msg.newId]) { + return; + } + return DOMApplicationRegistry.webapps[msg.newId][prop]; + }, + set: function(target, prop, val) { + if (!DOMApplicationRegistry.webapps[msg.newId]) { + return; + } + DOMApplicationRegistry.webapps[msg.newId][prop] = val; + return; + }, + }); + } + break; + case "Webapps:FireEvent": + this._fireEvent(aMessage); + break; + case "Webapps:UpdateState": + this._updateState(msg); + break; + case "Webapps:CheckForUpdate:Return:KO": + let DOMApps = this.DOMApps[msg.manifestURL]; + if (!DOMApps || !msg.requestID) { + return; + } + DOMApps.forEach((DOMApp) => { + let domApp = DOMApp.get(); + if (domApp && msg.requestID) { + domApp._fireRequestResult(aMessage, true /* aIsError */); + } + }); + break; } }, + /** + * mozIDOMApplication management + */ + + // Every time a DOM app is created, we save a weak reference to it that will + // be used to dispatch events and fire request results. + addDOMApp: function(aApp, aManifestURL, aId) { + let weakRef = Cu.getWeakReference(aApp); + + if (!this.DOMApps[aManifestURL]) { + this.DOMApps[aManifestURL] = []; + } + + let apps = this.DOMApps[aManifestURL]; + + // Get rid of dead weak references. + for (let i = 0; i < apps.length; i++) { + if (!apps[i].get()) { + apps.splice(i); + } + } + + apps.push(weakRef); + + // Each DOM app contains a proxy object used to build their state. We + // return the handler for this proxy object with traps to get and set + // app properties kept in the DOMApplicationRegistry app cache. + return { + get: function(target, prop) { + if (!DOMApplicationRegistry.webapps[aId]) { + return; + } + return DOMApplicationRegistry.webapps[aId][prop]; + }, + set: function(target, prop, val) { + if (!DOMApplicationRegistry.webapps[aId]) { + return; + } + DOMApplicationRegistry.webapps[aId][prop] = val; + return; + }, + }; + }, + + _fireEvent: function(aMessage) { + let msg = aMessage.data; + debug("_fireEvent " + JSON.stringify(msg)); + if (!this.DOMApps || !msg.manifestURL || !msg.eventType) { + return; + } + + let DOMApps = this.DOMApps[msg.manifestURL]; + if (!DOMApps) { + return; + } + + // The parent might ask childs to trigger more than one event in one + // shot, so in order to avoid needless IPC we allow an array for the + // 'eventType' IPC message field. + if (!Array.isArray(msg.eventType)) { + msg.eventType = [msg.eventType]; + } + + DOMApps.forEach((DOMApp) => { + let domApp = DOMApp.get(); + if (!domApp) { + return; + } + msg.eventType.forEach((aEventType) => { + if ('on' + aEventType in domApp) { + domApp._fireEvent(aEventType); + } + }); + + if (msg.requestID) { + aMessage.data.result = msg.manifestURL; + domApp._fireRequestResult(aMessage); + } + }); + }, + + _updateState: function(aMessage) { + if (!this.DOMApps || !aMessage.id) { + return; + } + + let app = this.webapps[aMessage.id]; + if (!app) { + return; + } + + if (aMessage.app) { + for (let prop in aMessage.app) { + app[prop] = aMessage.app[prop]; + } + } + + if ("error" in aMessage) { + app.downloadError = aMessage.error; + } + + if (aMessage.manifest) { + app.manifest = aMessage.manifest; + // Evict the wrapped manifest cache for all the affected DOM objects. + let DOMApps = this.DOMApps[app.manifestURL]; + if (!DOMApps) { + return; + } + DOMApps.forEach((DOMApp) => { + let domApp = DOMApp.get(); + if (!domApp) { + return; + } + WrappedManifestCache.evict(app.manifestURL, domApp.innerWindowID); + }); + } + }, + + /** + * nsIAppsService API + */ getAppByManifestURL: function getAppByManifestURL(aManifestURL) { debug("getAppByManifestURL " + aManifestURL); return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL); @@ -89,7 +358,7 @@ this.DOMApplicationRegistry = { }, getAppByLocalId: function getAppByLocalId(aLocalId) { - debug("getAppByLocalId " + aLocalId); + debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready); let app = this.localIdIndex[aLocalId]; if (!app) { debug("Ouch, No app!"); diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index 939a0db982d8..35a393907a7d 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -12,6 +12,7 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/BrowserElementPromptService.jsm"); +Cu.import("resource://gre/modules/AppsServiceChild.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "@mozilla.org/childprocessmessagemanager;1", @@ -278,50 +279,9 @@ WebappsRegistry.prototype = { * mozIDOMApplication object */ -// A simple cache for the wrapped manifests. -let manifestCache = { - _cache: { }, - - // Gets an entry from the cache, and populates the cache if needed. - get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) { - if (!(aManifestURL in this._cache)) { - this._cache[aManifestURL] = { }; - } - - let winObjs = this._cache[aManifestURL]; - if (!(aInnerWindowID in winObjs)) { - winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow); - } - - return winObjs[aInnerWindowID]; - }, - - // Invalidates an entry in the cache. - evict: function mcache_evict(aManifestURL, aInnerWindowID) { - if (aManifestURL in this._cache) { - let winObjs = this._cache[aManifestURL]; - if (aInnerWindowID in winObjs) { - delete winObjs[aInnerWindowID]; - } - - if (Object.keys(winObjs).length == 0) { - delete this._cache[aManifestURL]; - } - } - }, - - observe: function(aSubject, aTopic, aData) { - // Clear the cache on memory pressure. - this._cache = { }; - }, - - init: function() { - Services.obs.addObserver(this, "memory-pressure", false); - } -}; - function createApplicationObject(aWindow, aApp) { - let app = Cc["@mozilla.org/webapps/application;1"].createInstance(Ci.mozIDOMApplication); + let app = Cc["@mozilla.org/webapps/application;1"] + .createInstance(Ci.mozIDOMApplication); app.wrappedJSObject.init(aWindow, aApp); return app; } @@ -334,27 +294,12 @@ WebappsApplication.prototype = { __proto__: DOMRequestIpcHelper.prototype, init: function(aWindow, aApp) { + let proxyHandler = DOMApplicationRegistry.addDOMApp(this, + aApp.manifestURL, + aApp.id); + this._proxy = new Proxy(this, proxyHandler); + this._window = aWindow; - let principal = this._window.document.nodePrincipal; - this._appStatus = principal.appStatus; - this.origin = aApp.origin; - this._manifest = aApp.manifest; - this._updateManifest = aApp.updateManifest; - this.manifestURL = aApp.manifestURL; - this.receipts = aApp.receipts; - this.installOrigin = aApp.installOrigin; - this.installTime = aApp.installTime; - this.installState = aApp.installState || "installed"; - this.removable = aApp.removable; - this.lastUpdateCheck = aApp.lastUpdateCheck ? aApp.lastUpdateCheck - : Date.now(); - this.updateTime = aApp.updateTime ? aApp.updateTime - : aApp.installTime; - this.progress = NaN; - this.downloadAvailable = aApp.downloadAvailable; - this.downloading = aApp.downloading; - this.readyToApplyDownload = aApp.readyToApplyDownload; - this.downloadSize = aApp.downloadSize || 0; this._onprogress = null; this._ondownloadsuccess = null; @@ -362,40 +307,83 @@ WebappsApplication.prototype = { this._ondownloadavailable = null; this._ondownloadapplied = null; - this._downloadError = null; + this.initDOMRequestHelper(aWindow); + }, - this.initDOMRequestHelper(aWindow, [ - { name: "Webapps:CheckForUpdate:Return:KO", weakRef: true }, - { name: "Webapps:Connect:Return:OK", weakRef: true }, - { name: "Webapps:Connect:Return:KO", weakRef: true }, - { name: "Webapps:FireEvent", weakRef: true }, - { name: "Webapps:GetConnections:Return:OK", weakRef: true }, - { name: "Webapps:UpdateState", weakRef: true } - ]); + get _appStatus() { + return this._proxy.appStatus; + }, - cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { - messages: ["Webapps:FireEvent", - "Webapps:UpdateState"], - app: { - id: this.id, - manifestURL: this.manifestURL, - installState: this.installState, - downloading: this.downloading - } - }); + get downloadAvailable() { + return this._proxy.downloadAvailable; + }, + + get downloading() { + return this._proxy.downloading; + }, + + get downloadSize() { + return this._proxy.downloadSize; + }, + + get installOrigin() { + return this._proxy.installOrigin; + }, + + get installState() { + return this._proxy.installState; + }, + + get installTime() { + return this._proxy.installTime; + }, + + get lastUpdateCheck() { + return this._proxy.lastUpdateCheck; + }, + + get manifestURL() { + return this._proxy.manifestURL; + }, + + get origin() { + return this._proxy.origin; + }, + + get progress() { + return this._proxy.progress; + }, + + get readyToApplyDownload() { + return this._proxy.readyToApplyDownload; + }, + + get receipts() { + return this._proxy.receipts; + }, + + set receipts(aReceipts) { + this._proxy.receipts = aReceipts; + }, + + get removable() { + return this._proxy.removable; + }, + + get updateTime() { + return this._proxy.updateTime; }, get manifest() { - return manifestCache.get(this.manifestURL, - this._manifest, - this._window, - this.innerWindowID); + return WrappedManifestCache.get(this.manifestURL, + this._proxy.manifest, + this._window, + this.innerWindowID); }, get updateManifest() { - return this.updateManifest = - this._updateManifest ? Cu.cloneInto(this._updateManifest, this._window) - : null; + return this._proxy.updateManifest ? + Cu.cloneInto(this._proxy.updateManifest, this._window) : null; }, set onprogress(aCallback) { @@ -440,10 +428,10 @@ WebappsApplication.prototype = { get downloadError() { // Only return DOMError when we have an error. - if (!this._downloadError) { + if (!this._proxy.downloadError) { return null; } - return new this._window.DOMError(this._downloadError); + return new this._window.DOMError(this._proxy.downloadError); }, download: function() { @@ -485,12 +473,11 @@ WebappsApplication.prototype = { BrowserElementPromptService.getBrowserElementChildForWindow(this._window); if (browserChild) { this.addMessageListeners("Webapps:ClearBrowserData:Return"); - browserChild.messageManager.sendAsyncMessage( - "Webapps:ClearBrowserData", - { manifestURL: this.manifestURL, - oid: this._id, - requestID: this.getRequestId(request) } - ); + browserChild.messageManager.sendAsyncMessage("Webapps:ClearBrowserData", { + manifestURL: this.manifestURL, + oid: this._id, + requestID: this.getRequestId(request) + }); } else { Services.DOMRequest.fireErrorAsync(request, "NO_CLEARABLE_BROWSER"); } @@ -498,28 +485,33 @@ WebappsApplication.prototype = { }, connect: function(aKeyword, aRules) { + this.addMessageListeners(["Webapps:Connect:Return:OK", + "Webapps:Connect:Return:KO"]); return this.createPromise(function (aResolve, aReject) { - cpmm.sendAsyncMessage("Webapps:Connect", - { keyword: aKeyword, - rules: aRules, - manifestURL: this.manifestURL, - outerWindowID: this._id, - requestID: this.getPromiseResolverId({ - resolve: aResolve, - reject: aReject - })}); + cpmm.sendAsyncMessage("Webapps:Connect", { + keyword: aKeyword, + rules: aRules, + manifestURL: this.manifestURL, + outerWindowID: this._id, + requestID: this.getPromiseResolverId({ + resolve: aResolve, + reject: aReject + }) + }); }.bind(this)); }, getConnections: function() { + this.addMessageListeners("Webapps:GetConnections:Return:OK"); return this.createPromise(function (aResolve, aReject) { - cpmm.sendAsyncMessage("Webapps:GetConnections", - { manifestURL: this.manifestURL, - outerWindowID: this._id, - requestID: this.getPromiseResolverId({ - resolve: aResolve, - reject: aReject - })}); + cpmm.sendAsyncMessage("Webapps:GetConnections", { + manifestURL: this.manifestURL, + outerWindowID: this._id, + requestID: this.getPromiseResolverId({ + resolve: aResolve, + reject: aReject + }) + }); }.bind(this)); }, @@ -568,12 +560,7 @@ WebappsApplication.prototype = { uninit: function() { this._onprogress = null; - cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", [ - "Webapps:FireEvent", - "Webapps:UpdateState" - ]); - - manifestCache.evict(this.manifestURL, this.innerWindowID); + WrappedManifestCache.evict(this.manifestURL, this.innerWindowID); }, _fireEvent: function(aName) { @@ -590,22 +577,16 @@ WebappsApplication.prototype = { } }, - _updateState: function(aMsg) { - if (aMsg.app) { - for (let prop in aMsg.app) { - this[prop] = aMsg.app[prop]; - } + _fireRequestResult: function(aMessage, aIsError) { + let req; + let msg = aMessage.data; + req = this.takeRequest(msg.requestID); + if (!req) { + return; } - // Intentional use of 'in' so we unset the error if this is explicitly null. - if ('error' in aMsg) { - this._downloadError = aMsg.error; - } - - if (aMsg.manifest) { - this._manifest = aMsg.manifest; - manifestCache.evict(this.manifestURL, this.innerWindowID); - } + aIsError ? Services.DOMRequest.fireError(req, msg.error) + : Services.DOMRequest.fireSuccess(req, msg.result); }, receiveMessage: function(aMessage) { @@ -619,10 +600,7 @@ WebappsApplication.prototype = { req = this.takeRequest(msg.requestID); } - // ondownload* callbacks should be triggered on all app instances - if ((msg.oid != this._id || !req) && - aMessage.name !== "Webapps:FireEvent" && - aMessage.name !== "Webapps:UpdateState") { + if (msg.oid !== this._id || !req) { return; } @@ -637,51 +615,13 @@ WebappsApplication.prototype = { "Webapps:Launch:Return:KO"]); Services.DOMRequest.fireSuccess(req, null); break; - case "Webapps:CheckForUpdate:Return:KO": - Services.DOMRequest.fireError(req, msg.error); - break; - case "Webapps:FireEvent": - if (msg.manifestURL != this.manifestURL) { - return; - } - - // The parent might ask childs to trigger more than one event in one - // shot, so in order to avoid needless IPC we allow an array for the - // 'eventType' IPC message field. - if (!Array.isArray(msg.eventType)) { - msg.eventType = [msg.eventType]; - } - - msg.eventType.forEach((aEventType) => { - // If we are in a successful state clear any past errors. - if (aEventType === 'downloadapplied' || - aEventType === 'downloadsuccess') { - this._downloadError = null; - } - - if ("_on" + aEventType in this) { - this._fireEvent(aEventType); - } else { - dump("Unsupported event type " + aEventType + "\n"); - } - }); - - if (req) { - Services.DOMRequest.fireSuccess(req, this.manifestURL); - } - break; - case "Webapps:UpdateState": - if (msg.manifestURL != this.manifestURL) { - return; - } - - this._updateState(msg); - break; case "Webapps:ClearBrowserData:Return": this.removeMessageListeners(aMessage.name); Services.DOMRequest.fireSuccess(req, null); break; case "Webapps:Connect:Return:OK": + this.removeMessageListeners(["Webapps:Connect:Return:OK", + "Webapps:Connect:Return:KO"]); let messagePorts = []; msg.messagePortIDs.forEach((aPortID) => { let port = new this._window.MozInterAppMessagePort(aPortID); @@ -690,9 +630,12 @@ WebappsApplication.prototype = { req.resolve(messagePorts); break; case "Webapps:Connect:Return:KO": + this.removeMessageListeners(["Webapps:Connect:Return:OK", + "Webapps:Connect:Return:KO"]); req.reject("No connections registered"); break; case "Webapps:GetConnections:Return:OK": + this.removeMessageListeners(aMessage.name); let connections = []; msg.connections.forEach((aConnection) => { let connection = @@ -874,12 +817,8 @@ WebappsApplicationMgmt.prototype = { break; case "Webapps:Uninstall:Broadcast:Return:OK": if (this._onuninstall) { - let detail = { - manifestURL: msg.manifestURL, - origin: msg.origin - }; let event = new this._window.MozApplicationEvent("applicationuninstall", - { application : createApplicationObject(this._window, detail) }); + { application : createApplicationObject(this._window, msg) }); this._onuninstall.handleEvent(event); } break; @@ -908,7 +847,5 @@ WebappsApplicationMgmt.prototype = { classDescription: "Webapps Application Mgmt"}) } -manifestCache.init(); - this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry, WebappsApplication]); diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 216fef131e59..37c86bf4cddc 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -1141,8 +1141,8 @@ this.DOMApplicationRegistry = { this.removeMessageListener(["Webapps:Internal:AllMessages"], mm); break; case "Webapps:GetList": - this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm); - return this.webapps; + return this.doGetList(); + break; case "Webapps:Download": this.startDownload(msg.manifestURL); break; @@ -1245,6 +1245,38 @@ this.DOMApplicationRegistry = { return deferred.promise; }, + /** + * Returns the full list of apps and manifests. + */ + doGetList: function() { + let tmp = []; + + for (let id in this.webapps) { + tmp.push({ id: id }); + } + + let res = {}; + let done = false; + + this._readManifests(tmp).then( + function(manifests) { + manifests.forEach((item) => { + res[item.id] = item.manifest; + }); + done = true; + } + ); + + let thread = Services.tm.currentThread; + while (!done) { + //debug("before processNextEvent"); + thread.processNextEvent(/* mayWait */ true); + //after("before processNextEvent"); + } + return { webapps: this.webapps, manifests: res }; + }, + + doLaunch: function (aData, aMm) { this.launch( aData.manifestURL, @@ -1330,7 +1362,7 @@ this.DOMApplicationRegistry = { downloading: false }, error: error, - manifestURL: app.manifestURL, + id: app.id }) this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -1361,7 +1393,7 @@ this.DOMApplicationRegistry = { if (!app.downloadAvailable) { this.broadcastMessage("Webapps:UpdateState", { error: "NO_DOWNLOAD_AVAILABLE", - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -1409,7 +1441,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: jsonManifest, - manifestURL: aManifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1453,7 +1485,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: aManifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadsuccess", @@ -1555,7 +1587,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: app, manifest: newManifest, - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -1594,7 +1626,7 @@ this.DOMApplicationRegistry = { installState: aApp.installState, progress: 0 }, - manifestURL: aApp.manifestURL + id: aApp.id }); let cacheUpdate = updateSvc.scheduleAppUpdate( appcacheURI, docURI, aApp.localId, false, aProfileDir); @@ -1644,6 +1676,7 @@ this.DOMApplicationRegistry = { debug("checkForUpdate for " + aData.manifestURL); function sendError(aError) { + debug("checkForUpdate error " + aError); aData.error = aError; aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); } @@ -1673,8 +1706,7 @@ this.DOMApplicationRegistry = { // then we can't have an update. if (app.origin.startsWith("app://") && app.manifestURL.startsWith("app://")) { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); return; } @@ -1691,8 +1723,7 @@ this.DOMApplicationRegistry = { if (onlyCheckAppCache) { // Bail out for packaged apps. if (app.origin.startsWith("app://")) { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); return; } @@ -1700,8 +1731,7 @@ this.DOMApplicationRegistry = { this._readManifests([{ id: id }]).then((aResult) => { let manifest = aResult[0].manifest; if (!manifest.appcache_path) { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); return; } @@ -1717,7 +1747,7 @@ this.DOMApplicationRegistry = { this._saveApps().then(() => { this.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -1726,8 +1756,7 @@ this.DOMApplicationRegistry = { }); }); } else { - aData.error = "NOT_UPDATABLE"; - aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); + sendError("NOT_UPDATABLE"); } } }; @@ -1787,7 +1816,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -1814,7 +1843,7 @@ this.DOMApplicationRegistry = { : "downloadapplied"; aMm.sendAsyncMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); aMm.sendAsyncMessage("Webapps:FireEvent", { eventType: eventType, @@ -1923,7 +1952,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadavailable", @@ -1989,7 +2018,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloadapplied", @@ -2023,7 +2052,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: aApp.manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: eventType, @@ -2450,7 +2479,8 @@ this.DOMApplicationRegistry = { } this._saveApps().then(() => { - this.broadcastMessage("Webapps:AddApp", { id: app.id, app: app }); + this.broadcastMessage("Webapps:AddApp", + { id: app.id, app: app, manifest: aManifest }); }); }), @@ -2550,6 +2580,8 @@ this.DOMApplicationRegistry = { // saved in the registry. yield this._saveApps(); + aData.isPackage ? appObject.updateManifest = jsonManifest : + appObject.manifest = jsonManifest; this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject }); // The presence of a requestID means that we have a page to update. @@ -2626,7 +2658,8 @@ this.DOMApplicationRegistry = { delete this._manifestCache[aId]; } - this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp }); + this.broadcastMessage("Webapps:AddApp", + { id: aId, app: aNewApp, manifest: aManifest }); Services.obs.notifyObservers(null, "webapps-installed", JSON.stringify({ manifestURL: aNewApp.manifestURL })); @@ -2760,14 +2793,11 @@ this.DOMApplicationRegistry = { oldApp, aNewApp); - AppDownloadManager.add( - aNewApp.manifestURL, - { - channel: requestChannel, - appId: id, - previousState: aIsUpdate ? "installed" : "pending" - } - ); + AppDownloadManager.add(aNewApp.manifestURL, { + channel: requestChannel, + appId: id, + previousState: aIsUpdate ? "installed" : "pending" + }); // We set the 'downloading' flag to true right before starting the fetch. oldApp.downloading = true; @@ -2787,7 +2817,7 @@ this.DOMApplicationRegistry = { // Clear any previous download errors. error: null, app: oldApp, - manifestURL: aNewApp.manifestURL + id: id }); let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp); @@ -2802,7 +2832,7 @@ this.DOMApplicationRegistry = { // We send an "applied" event right away so code awaiting that event // can proceed to access the app. We also throw an error to alert // the caller that the package wasn't downloaded. - this._sendAppliedEvent(aNewApp, oldApp, id); + this._sendAppliedEvent(oldApp); throw new Error("PACKAGE_UNCHANGED"); } @@ -2942,7 +2972,7 @@ this.DOMApplicationRegistry = { app: { progress: aProgress }, - manifestURL: aNewApp.manifestURL + id: aNewApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "progress", @@ -3059,27 +3089,24 @@ this.DOMApplicationRegistry = { * something similar after updating the app, and we could refactor both cases * to use the same code to send the "applied" event. * - * @param aNewApp {Object} the new app data - * @param aOldApp {Object} the currently stored app data - * @param aId {String} the unique id of the app + * @param aApp {Object} app data */ - _sendAppliedEvent: function(aNewApp, aOldApp, aId) { - aOldApp.downloading = false; - aOldApp.downloadAvailable = false; - aOldApp.downloadSize = 0; - aOldApp.installState = "installed"; - aOldApp.readyToApplyDownload = false; - if (aOldApp.staged && aOldApp.staged.manifestHash) { + _sendAppliedEvent: function(aApp) { + aApp.downloading = false; + aApp.downloadAvailable = false; + aApp.downloadSize = 0; + aApp.installState = "installed"; + aApp.readyToApplyDownload = false; + if (aApp.staged && aApp.staged.manifestHash) { // If we're here then the manifest has changed but the package // hasn't. Let's clear this, so we don't keep offering // a bogus update to the user - aOldApp.manifestHash = aOldApp.staged.manifestHash; - aOldApp.etag = aOldApp.staged.etag || aOldApp.etag; - aOldApp.staged = {}; - - // Move the staged update manifest to a non staged one. + aApp.manifestHash = aApp.staged.manifestHash; + aApp.etag = aApp.staged.etag || aApp.etag; + aApp.staged = {}; + // Move the staged update manifest to a non staged one. try { - let staged = this._getAppDir(aId); + let staged = this._getAppDir(aApp.id); staged.append("staged-update.webapp"); staged.moveTo(staged.parent, "update.webapp"); } catch (ex) { @@ -3090,15 +3117,15 @@ this.DOMApplicationRegistry = { // Save the updated registry, and cleanup the tmp directory. this._saveApps().then(() => { this.broadcastMessage("Webapps:UpdateState", { - app: aOldApp, - manifestURL: aNewApp.manifestURL + app: aApp, + id: aApp.id }); this.broadcastMessage("Webapps:FireEvent", { - manifestURL: aNewApp.manifestURL, + manifestURL: aApp.manifestURL, eventType: ["downloadsuccess", "downloadapplied"] }); }); - let file = FileUtils.getFile("TmpD", ["webapps", aId], false); + let file = FileUtils.getFile("TmpD", ["webapps", aApp.id], false); if (file && file.exists()) { file.remove(true); } @@ -3403,9 +3430,10 @@ this.DOMApplicationRegistry = { dir.moveTo(parent, newId); }); // Signals that we need to swap the old id with the new app. - this.broadcastMessage("Webapps:RemoveApp", { id: oldId }); - this.broadcastMessage("Webapps:AddApp", { id: newId, - app: aOldApp }); + this.broadcastMessage("Webapps:UpdateApp", { oldId: oldId, + newId: newId, + app: aOldApp }); + } } }, @@ -3508,7 +3536,7 @@ this.DOMApplicationRegistry = { this.broadcastMessage("Webapps:UpdateState", { app: aOldApp, error: aError, - manifestURL: aNewApp.manifestURL + id: aNewApp.id }); this.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", @@ -4060,7 +4088,7 @@ AppcacheObserver.prototype = { let app = this.app; DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: "progress", @@ -4092,7 +4120,7 @@ AppcacheObserver.prototype = { app.downloadAvailable = false; DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, - manifestURL: app.manifestURL + id: app.id }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: ["downloadsuccess", "downloadapplied"], @@ -4115,7 +4143,7 @@ AppcacheObserver.prototype = { DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { app: app, error: aError, - manifestURL: app.manifestURL + id: app.id }); DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { eventType: "downloaderror", diff --git a/dom/apps/tests/test_packaged_app_common.js b/dom/apps/tests/test_packaged_app_common.js index 9a0bddbff4c3..7c91021f1f64 100644 --- a/dom/apps/tests/test_packaged_app_common.js +++ b/dom/apps/tests/test_packaged_app_common.js @@ -98,6 +98,7 @@ var PackagedTestHelper = (function PackagedTestHelper() { var aApp = evt.application; aApp.ondownloaderror = function(evt) { var error = aApp.downloadError.name; + ok(true, "Got downloaderror " + error); if (error == aExpectedError) { ok(true, "Got expected " + aExpectedError); var expected = { diff --git a/dom/apps/tests/test_packaged_app_update.html b/dom/apps/tests/test_packaged_app_update.html index 63be714db281..c26c2208ae24 100644 --- a/dom/apps/tests/test_packaged_app_update.html +++ b/dom/apps/tests/test_packaged_app_update.html @@ -79,15 +79,15 @@ function updateApp(aExpectedReady, aPreviousVersion, aNextVersion) { checkLastAppState.bind(PackagedTestHelper, miniManifestURL, false, false, aNextVersion, PackagedTestHelper.next); - var ondownloadsuccesshandler = - checkLastAppState.bind(undefined, miniManifestURL, - aExpectedReady, false, aPreviousVersion, - function() { - navigator.mozApps.mgmt.applyDownload(lApp); - }); + var ondownloadsuccesshandler = + checkLastAppState.bind(undefined, miniManifestURL, + aExpectedReady, false, aPreviousVersion, + function() { + navigator.mozApps.mgmt.applyDownload(lApp); + }); - checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null, - true); + checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, + null, true); } @@ -251,7 +251,7 @@ var steps = [ "&appName=arandomname" + "&appToFail1"; PackagedTestHelper.checkAppDownloadError(miniManifestURL, - "MANIFEST_MISMATCH", 2, false, true, + "MANIFEST_MISMATCH", 1, false, true, "arandomname", function () { checkForUpdate(false, null, null, null, false, diff --git a/dom/apps/tests/test_receipt_operations.html b/dom/apps/tests/test_receipt_operations.html index 0907e8d964c4..7ebe84ccfae0 100644 --- a/dom/apps/tests/test_receipt_operations.html +++ b/dom/apps/tests/test_receipt_operations.html @@ -243,4 +243,4 @@ addLoadEvent(go); - \ No newline at end of file + diff --git a/dom/bluetooth2/tests/marionette/head.js b/dom/bluetooth2/tests/marionette/head.js index 4bfde83ef9ab..eeb7071b95cf 100644 --- a/dom/bluetooth2/tests/marionette/head.js +++ b/dom/bluetooth2/tests/marionette/head.js @@ -1,6 +1,4 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim: sw=2 ts=2 sts=2 et filetype=javascript - * This Source Code Form is subject to the terms of the Mozilla Public +/* 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/. */ @@ -36,7 +34,7 @@ const BDADDR_ALL = "ff:ff:ff:ff:ff:ff"; const BDADDR_LOCAL = "ff:ff:ff:00:00:00"; // A user friendly name for remote BT device. -const REMOTE_DEVICE_NAME = "Remote BT Device"; +const REMOTE_DEVICE_NAME = "Remote_BT_Device"; let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise; @@ -45,6 +43,58 @@ let bluetoothManager; let pendingEmulatorCmdCount = 0; +/** + * Push required permissions and test if |navigator.mozBluetooth| exists. + * Resolve if it does, reject otherwise. + * + * Fulfill params: + * bluetoothManager -- an reference to navigator.mozBluetooth. + * Reject params: (none) + * + * @param aPermissions + * Additional permissions to push before any test cases. Could be either + * a string or an array of strings. + * + * @return A deferred promise. + */ +function ensureBluetoothManager(aPermissions) { + let deferred = Promise.defer(); + + let permissions = ["bluetooth"]; + if (aPermissions) { + if (Array.isArray(aPermissions)) { + permissions = permissions.concat(aPermissions); + } else if (typeof aPermissions == "string") { + permissions.push(aPermissions); + } + } + + let obj = []; + for (let perm of permissions) { + obj.push({ + "type": perm, + "allow": 1, + "context": document, + }); + } + + SpecialPowers.pushPermissions(obj, function() { + ok(true, "permissions pushed: " + JSON.stringify(permissions)); + + bluetoothManager = window.navigator.mozBluetooth; + log("navigator.mozBluetooth is " + + (bluetoothManager ? "available" : "unavailable")); + + if (bluetoothManager instanceof BluetoothManager) { + deferred.resolve(bluetoothManager); + } else { + deferred.reject(); + } + }); + + return deferred.promise; +} + /** * Send emulator command with safe guard. * @@ -193,71 +243,6 @@ function getEmulatorDeviceProperty(aAddress, aPropertyName) { }); } -/** - * Start dicovering Bluetooth devices. - * - * Allows the device's adapter to start seeking for remote devices. - * - * Fulfill params: (none) - * Reject params: a DOMError - * - * @param aAdapter - * A BluetoothAdapter which is used to interact with local BT dev - * - * @return A deferred promise. - */ -function startDiscovery(aAdapter) { - let deferred = Promise.defer(); - - let request = aAdapter.startDiscovery(); - request.onsuccess = function () { - log(" Start discovery - Success"); - // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps. - // Currently, discovering state wouldn't change immediately here. - // We would turn on this check when the redesigned API are landed. - // is(aAdapter.discovering, true, "BluetoothAdapter.discovering"); - deferred.resolve(); - } - request.onerror = function (aEvent) { - ok(false, "Start discovery - Fail"); - deferred.reject(aEvent.target.error); - } - - return deferred.promise; -} - -/** - * Stop dicovering Bluetooth devices. - * - * Allows the device's adapter to stop seeking for remote devices. - * - * Fulfill params: (none) - * Reject params: a DOMError - * - * @param aAdapter - * A BluetoothAdapter which is used to interact with local BT device. - * - * @return A deferred promise. - */ -function stopDiscovery(aAdapter) { - let deferred = Promise.defer(); - - let request = aAdapter.stopDiscovery(); - request.onsuccess = function () { - log(" Stop discovery - Success"); - // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps. - // Currently, discovering state wouldn't change immediately here. - // We would turn on this check when the redesigned API are landed. - // is(aAdapter.discovering, false, "BluetoothAdapter.discovering"); - deferred.resolve(); - } - request.onerror = function (aEvent) { - ok(false, "Stop discovery - Fail"); - deferred.reject(aEvent.target.error); - } - return deferred.promise; -} - /** * Get mozSettings value specified by @aKey. * @@ -319,10 +304,9 @@ function setSettings(aSettings) { } /** - * Get mozSettings value of 'bluetooth.enabled'. + * Get the boolean value which indicates defaultAdapter of bluetooth is enabled. * - * Resolve if that mozSettings value is retrieved successfully, reject - * otherwise. + * Resolve if that defaultAdapter is enabled * * Fulfill params: * A boolean value. @@ -331,7 +315,9 @@ function setSettings(aSettings) { * @return A deferred promise. */ function getBluetoothEnabled() { - return getSettings("bluetooth.enabled"); + log("bluetoothManager.defaultAdapter.state: " + bluetoothManager.defaultAdapter.state); + + return (bluetoothManager.defaultAdapter.state == "enabled"); } /** @@ -353,58 +339,6 @@ function setBluetoothEnabled(aEnabled) { return setSettings(obj); } -/** - * Push required permissions and test if |navigator.mozBluetooth| exists. - * Resolve if it does, reject otherwise. - * - * Fulfill params: - * bluetoothManager -- an reference to navigator.mozBluetooth. - * Reject params: (none) - * - * @param aPermissions - * Additional permissions to push before any test cases. Could be either - * a string or an array of strings. - * - * @return A deferred promise. - */ -function ensureBluetoothManager(aPermissions) { - let deferred = Promise.defer(); - - let permissions = ["bluetooth"]; - if (aPermissions) { - if (Array.isArray(aPermissions)) { - permissions = permissions.concat(aPermissions); - } else if (typeof aPermissions == "string") { - permissions.push(aPermissions); - } - } - - let obj = []; - for (let perm of permissions) { - obj.push({ - "type": perm, - "allow": 1, - "context": document, - }); - } - - SpecialPowers.pushPermissions(obj, function() { - ok(true, "permissions pushed: " + JSON.stringify(permissions)); - - bluetoothManager = window.navigator.mozBluetooth; - log("navigator.mozBluetooth is " + - (bluetoothManager ? "available" : "unavailable")); - - if (bluetoothManager instanceof BluetoothManager) { - deferred.resolve(bluetoothManager); - } else { - deferred.reject(); - } - }); - - return deferred.promise; -} - /** * Wait for one named BluetoothManager event. * @@ -457,73 +391,6 @@ function waitForAdapterEvent(aAdapter, aEventName) { return deferred.promise; } -/** - * Convenient function for setBluetoothEnabled and waitForManagerEvent - * combined. - * - * Resolve if that named event occurs. Reject if we can't set settings. - * - * Fulfill params: the DOMEvent passed. - * Reject params: (none) - * - * @return A deferred promise. - */ -function setBluetoothEnabledAndWait(aEnabled) { - let promises = []; - - // Bug 969109 - Intermittent test_dom_BluetoothManager_adapteradded.js - // - // Here we want to wait for two events coming up -- Bluetooth "settings-set" - // event and one of "enabled"/"disabled" events. Special care is taken here - // to ensure that we can always receive that "enabled"/"disabled" event by - // installing the event handler *before* we ever enable/disable Bluetooth. Or - // we might just miss those events and get a timeout error. - promises.push(waitForManagerEvent(aEnabled ? "enabled" : "disabled")); - promises.push(setBluetoothEnabled(aEnabled)); - - return Promise.all(promises); -} - -/** - * Get default adapter. - * - * Resolve if that default adapter is got, reject otherwise. - * - * Fulfill params: a BluetoothAdapter instance. - * Reject params: a DOMError, or null if if there is no adapter ready yet. - * - * @return A deferred promise. - */ -function getDefaultAdapter() { - let deferred = Promise.defer(); - - let request = bluetoothManager.getDefaultAdapter(); - request.onsuccess = function(aEvent) { - let adapter = aEvent.target.result; - if (!(adapter instanceof BluetoothAdapter)) { - ok(false, "no BluetoothAdapter ready yet."); - deferred.reject(null); - return; - } - - ok(true, "BluetoothAdapter got."); - // TODO: We have an adapter instance now, but some of its attributes may - // still remain unassigned/out-dated. Here we waste a few seconds to - // wait for the property changed events. - // - // See https://bugzilla.mozilla.org/show_bug.cgi?id=932914 - window.setTimeout(function() { - deferred.resolve(adapter); - }, 3000); - }; - request.onerror = function(aEvent) { - ok(false, "Failed to get default adapter."); - deferred.reject(aEvent.target.error); - }; - - return deferred.promise; -} - /** * Flush permission settings and call |finish()|. */ @@ -552,36 +419,40 @@ function startBluetoothTestBase(aPermissions, aTestCaseMain) { function startBluetoothTest(aReenable, aTestCaseMain) { startBluetoothTestBase(["settings-read", "settings-write"], function() { let origEnabled, needEnable; + return Promise.resolve() + .then(function() { + origEnabled = getBluetoothEnabled(); - return getBluetoothEnabled() - .then(function(aEnabled) { - origEnabled = aEnabled; - needEnable = !aEnabled; - log("Original 'bluetooth.enabled' is " + origEnabled); + needEnable = !origEnabled; + log("Original state of bluetooth is " + bluetoothManager.defaultAdapter.state); - if (aEnabled && aReenable) { - log(" Disable 'bluetooth.enabled' ..."); + if (origEnabled && aReenable) { + log("Disable Bluetooth ..."); needEnable = true; - return setBluetoothEnabledAndWait(false); + + isnot(bluetoothManager.defaultAdapter, null, + "bluetoothManager.defaultAdapter") + + return bluetoothManager.defaultAdapter.disable(); } }) .then(function() { if (needEnable) { - log(" Enable 'bluetooth.enabled' ..."); + log("Enable Bluetooth ..."); - // See setBluetoothEnabledAndWait(). We must install all event - // handlers *before* enabling Bluetooth. - let promises = []; - promises.push(waitForManagerEvent("adapteradded")); - promises.push(setBluetoothEnabledAndWait(true)); - return Promise.all(promises); + isnot(bluetoothManager.defaultAdapter, null, + "bluetoothManager.defaultAdapter") + + return bluetoothManager.defaultAdapter.enable(); } }) - .then(getDefaultAdapter) + .then(() => bluetoothManager.defaultAdapter) .then(aTestCaseMain) .then(function() { if (!origEnabled) { - return setBluetoothEnabledAndWait(false); + log("Disable Bluetooth ..."); + + return bluetoothManager.defaultAdapter.disable(); } }); }); diff --git a/dom/bluetooth2/tests/marionette/manifest.ini b/dom/bluetooth2/tests/marionette/manifest.ini index 64819b7b9ce0..e469d54782ad 100644 --- a/dom/bluetooth2/tests/marionette/manifest.ini +++ b/dom/bluetooth2/tests/marionette/manifest.ini @@ -1,10 +1,6 @@ [DEFAULT] b2g = true browser = false -qemu = true +qemu = false -[test_dom_BluetoothManager_enabled.js] -[test_dom_BluetoothManager_adapteradded.js] -[test_dom_BluetoothAdapter_setters.js] -[test_dom_BluetoothAdapter_getters.js] -[test_dom_BluetoothAdapter_discovery.js] +[test_dom_BluetoothManager_API2.js] diff --git a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_discovery.js b/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_discovery.js deleted file mode 100644 index d1d6dd26f256..000000000000 --- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_discovery.js +++ /dev/null @@ -1,47 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim: sw=2 ts=2 sts=2 et filetype=javascript - * 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/. */ - -/////////////////////////////////////////////////////////////////////////////// -// Test Purpose: -// To verify that discovery process of BluetoothAdapter is correct. -// Use B2G emulator commands to add/remote remote devices to simulate -// discovering behavior. -// -// Test Coverage: -// - BluetoothAdapter.startDiscovery() -// - BluetoothAdapter.stopDiscovery() -// - BluetoothAdapter.ondevicefound() -// - BluetoothAdapter.discovering [Temporarily turned off until BT API update] -// -/////////////////////////////////////////////////////////////////////////////// - -MARIONETTE_TIMEOUT = 60000; -MARIONETTE_HEAD_JS = 'head.js'; - -startBluetoothTest(true, function testCaseMain(aAdapter) { - log("Testing the discovery process of BluetoothAdapter ..."); - - // The properties of remote device. - let theProperties = { - "name": REMOTE_DEVICE_NAME, - "discoverable": true - }; - - return Promise.resolve() - .then(() => removeEmulatorRemoteDevice(BDADDR_ALL)) - .then(() => addEmulatorRemoteDevice(/*theProperties*/ null)) - .then(function(aRemoteAddress) { - let promises = []; - promises.push(waitForAdapterEvent(aAdapter, "devicefound")); - promises.push(startDiscovery(aAdapter)); - return Promise.all(promises) - .then(function(aResults) { - is(aResults[0].device.address, aRemoteAddress, "BluetoothDevice.address"); - }); - }) - .then(() => stopDiscovery(aAdapter)) - .then(() => removeEmulatorRemoteDevice(BDADDR_ALL)); -}); diff --git a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_getters.js b/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_getters.js deleted file mode 100644 index 541843dfc37a..000000000000 --- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_getters.js +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim: sw=2 ts=2 sts=2 et filetype=javascript - * 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/. */ - -/////////////////////////////////////////////////////////////////////////////// -// Test Purpose: -// To verify that the properties of BluetoothAdapter can be updated and -// retrieved correctly. Use B2G emulator commands to set properties for this -// test case. -// -// Test Coverage: -// - BluetoothAdapter.name -// - BluetoothAdapter.address -// - BluetoothAdapter.class -// - BluetoothAdapter.discoverable -// - BluetoothAdapter.discovering -// ( P.S. Don't include [BluetoothAdapter.uuids], [BluetoothAdapter.devices] ) -// -/////////////////////////////////////////////////////////////////////////////// - -MARIONETTE_TIMEOUT = 60000; -MARIONETTE_HEAD_JS = 'head.js'; - -function testAdapterGetter(aAdapter, aPropertyName, aParamName, aExpected) { - let cmd = "bt property " + BDADDR_LOCAL + " " + aParamName; - return runEmulatorCmdSafe(cmd) - .then(function(aResults) { - is(aResults[1], "OK", "The status report from emulator command."); - log(" Got adapter " + aResults[0]); - is(aResults[0], aParamName + ": " + aExpected, "BluetoothAdapter." + aPropertyName); - }); -} - -startBluetoothTest(true, function testCaseMain(aAdapter) { - log("Checking the correctness of BluetoothAdapter properties ..."); - - return Promise.resolve() - .then(() => testAdapterGetter(aAdapter, "name", "name", aAdapter.name)) - .then(() => testAdapterGetter(aAdapter, "address", "address", aAdapter.address)) - .then(() => testAdapterGetter(aAdapter, "class", "cod", "0x" + aAdapter.class.toString(16))) - .then(() => testAdapterGetter(aAdapter, "discoverable", "discoverable", aAdapter.discoverable.toString())) - .then(() => testAdapterGetter(aAdapter, "discovering", "discovering", aAdapter.discovering.toString())); -}); diff --git a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_setters.js b/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_setters.js deleted file mode 100644 index ec437fe5baa3..000000000000 --- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_setters.js +++ /dev/null @@ -1,85 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim: sw=2 ts=2 sts=2 et filetype=javascript - * 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/. */ - -/////////////////////////////////////////////////////////////////////////////// -// Test Purpose: -// To verify that all setters of BluetoothAdapter (except for pairing related -// APIs) can change properties correctly. -// -// Test Coverage: -// - BluetoothAdapter.setName() -// - BluetoothAdapter.setDiscoverable() -// - BluetoothAdapter.setDiscoverableTimeout() -// -/////////////////////////////////////////////////////////////////////////////// - -MARIONETTE_TIMEOUT = 60000; -MARIONETTE_HEAD_JS = 'head.js'; - -const BT_DEVICE_NAME = "User friendly name of local BT device"; - -function setName(aAdapter, aName) { - let deferred = Promise.defer(); - - let request = aAdapter.setName(aName); - request.onsuccess = function () { - log(" setName succeed: " + aName); - is(aAdapter.name, aName, "aAdapter.name"); - deferred.resolve(); - } - request.onerror = function () { - ok(false, "setName failed") - deferred.reject(); - } - - return deferred.promise; -} - -function setDiscoverable(aAdapter, aIsDiscoverable) { - let deferred = Promise.defer(); - - let request = aAdapter.setDiscoverable(aIsDiscoverable); - request.onsuccess = function () { - log(" setDiscoverable succeed: " + aIsDiscoverable); - is(aAdapter.discoverable, aIsDiscoverable, "aAdapter.discoverable"); - deferred.resolve(); - } - request.onerror = function () { - ok(false, "setDiscoverable failed") - deferred.reject(); - } - - return deferred.promise; -} - -function setDiscoverableTimeout(aAdapter, aTimeout) { - let deferred = Promise.defer(); - - let request = aAdapter.setDiscoverableTimeout(aTimeout); - request.onsuccess = function () { - log(" setDiscoverableTimeout succeed: " + aTimeout); - is(aAdapter.discoverableTimeout, aTimeout, "aAdapter.discoverableTimeout"); - deferred.resolve(); - } - request.onerror = function () { - ok(false, "setDiscoverableTimeout failed") - deferred.reject(); - } - - return deferred.promise; -} - -startBluetoothTest(true, function testCaseMain(aAdapter) { - log("Testing BluetoothAdapter setters ..."); - - return Promise.resolve() - .then( () => setName(aAdapter, BT_DEVICE_NAME) ) - .then( () => setDiscoverableTimeout(aAdapter, 180) ) - .then( () => setDiscoverable(aAdapter, true) ) - .then( () => setName(aAdapter, EMULATOR_NAME) ) - .then( () => setDiscoverable(aAdapter, false) ) - .then( () => setDiscoverableTimeout(aAdapter, 120) ); -}); diff --git a/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_API2.js b/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_API2.js new file mode 100644 index 000000000000..ece42ea778ad --- /dev/null +++ b/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_API2.js @@ -0,0 +1,48 @@ +/* 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/. */ + +/////////////////////////////////////////////////////////////////////////////// +// Test Purpose: +// To verify the basic functionality of BluetoothManager. +// +// Test Coverage: +// - BluetoothManager.defaultAdapter +// - BluetoothManager.getAdapters() +// TODO: +// - BluetoothManager.onattributechanged() +// - BluetoothManager.onadapteradded() +// - BluetoothManager.onadapterremoved() +// +/////////////////////////////////////////////////////////////////////////////// + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +// TODO: Listens to 'onattributechanged' when B2G supports the feature that +// allows user to add/remove Bluetooth adapter. +// Currently, B2G recognizes build-in Bluetooth chip as default adapter and +// don't support adding additional Bluetooth dongles in gonk layer. +// Therefore, the 'onattributechanged' would be triggered *only* when the +// instance of BluetoothManager is created. +function waitForManagerAttributeChanged() { + let deferred = Promise.defer(); + + bluetoothManager.onattributechanged = function(aEvent) { + if(aEvent.attrs.indexOf("defaultAdapter")) { + bluetoothManager.onattributechanged = null; + ok(true, "BluetoothManager event 'onattributechanged' got."); + deferred.resolve(aEvent); + } + }; + + return deferred.promise; +} + +startBluetoothTestBase(["settings-read", "settings-write"], + function testCaseMain() { + let adapters = bluetoothManager.getAdapters(); + ok(Array.isArray(adapters), "Can not got the array of adapters"); + ok(adapters.length, "The number of adapters should not be zero"); + ok(bluetoothManager.defaultAdapter, "defaultAdapter should not be null."); +}); diff --git a/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_adapteradded.js b/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_adapteradded.js deleted file mode 100644 index ab9c434ddf4d..000000000000 --- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_adapteradded.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim: sw=2 ts=2 sts=2 et filetype=javascript - * 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/. */ - -MARIONETTE_TIMEOUT = 60000; -MARIONETTE_HEAD_JS = 'head.js'; - -startBluetoothTest(true, function testCaseMain(aAdapter) { - log("Checking adapter attributes ..."); - - is(aAdapter.name, EMULATOR_NAME, "adapter.name"); - is(aAdapter.class, EMULATOR_CLASS, "adapter.class"); - is(aAdapter.address, EMULATOR_ADDRESS, "adapter.address"); - is(aAdapter.discovering, false, "adapter.discovering"); - is(aAdapter.discoverable, false, "adapter.discoverable"); - is(aAdapter.discoverableTimeout, 120, "adapter.discoverableTimeout"); -}); diff --git a/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_enabled.js b/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_enabled.js deleted file mode 100644 index ba22c4cf1e5d..000000000000 --- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_enabled.js +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim: sw=2 ts=2 sts=2 et filetype=javascript - * 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/. */ - -MARIONETTE_TIMEOUT = 60000; -MARIONETTE_HEAD_JS = 'head.js'; - -function waitEitherEnabledOrDisabled() { - let deferred = Promise.defer(); - - function onEnabledDisabled(aEvent) { - bluetoothManager.removeEventListener("adapteradded", onEnabledDisabled); - bluetoothManager.removeEventListener("disabled", onEnabledDisabled); - - ok(true, "Got event " + aEvent.type); - deferred.resolve(aEvent.type === "adapteradded"); - } - - // Listen 'adapteradded' rather than 'enabled' since the current API can't - // disable BT before the BT adapter is initialized. - // We should listen to 'enabled' when gecko can handle the case I mentioned - // above, please refer to the follow-up bug 973482. - bluetoothManager.addEventListener("adapteradded", onEnabledDisabled); - bluetoothManager.addEventListener("disabled", onEnabledDisabled); - - return deferred.promise; -} - -function test(aEnabled) { - log("Testing 'bluetooth.enabled' => " + aEnabled); - - let deferred = Promise.defer(); - - Promise.all([setBluetoothEnabled(aEnabled), - waitEitherEnabledOrDisabled()]) - .then(function(aResults) { - /* aResults is an array of two elements: - * [ , - * ] - */ - log(" Examine results " + JSON.stringify(aResults)); - - is(bluetoothManager.enabled, aEnabled, "bluetoothManager.enabled"); - is(aResults[1], aEnabled, "'adapteradded' event received"); - - if (bluetoothManager.enabled === aEnabled && aResults[1] === aEnabled) { - deferred.resolve(); - } else { - deferred.reject(); - } - }); - - return deferred.promise; -} - -startBluetoothTestBase(["settings-read", "settings-write"], - function testCaseMain() { - return getBluetoothEnabled() - .then(function(aEnabled) { - log("Original 'bluetooth.enabled' is " + aEnabled); - // Set to !aEnabled and reset back to aEnabled. - return test(!aEnabled).then(test.bind(null, aEnabled)); - }); -}); diff --git a/dom/cellbroadcast/src/CellBroadcast.cpp b/dom/cellbroadcast/src/CellBroadcast.cpp index 4afa19656a1a..849fbb07942b 100644 --- a/dom/cellbroadcast/src/CellBroadcast.cpp +++ b/dom/cellbroadcast/src/CellBroadcast.cpp @@ -17,7 +17,7 @@ using namespace mozilla::dom; * CellBroadcast::Listener Implementation. */ -class CellBroadcast::Listener : public nsICellBroadcastListener +class CellBroadcast::Listener MOZ_FINAL : public nsICellBroadcastListener { private: CellBroadcast* mCellBroadcast; diff --git a/dom/icc/src/IccListener.h b/dom/icc/src/IccListener.h index f3b6bf41c51e..e050393877ef 100644 --- a/dom/icc/src/IccListener.h +++ b/dom/icc/src/IccListener.h @@ -14,7 +14,7 @@ namespace dom { class IccManager; class Icc; -class IccListener : public nsIIccListener +class IccListener MOZ_FINAL : public nsIIccListener { public: NS_DECL_ISUPPORTS diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index b22fba24f478..00aebe63f49b 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -764,11 +764,6 @@ TabChild::Observe(nsISupports *aSubject, mContentDocumentIsDisplayed = true; - // Reset CSS viewport and zoom to default on new page, then - // calculate them properly using the actual metadata from the - // page. - SetCSSViewport(kDefaultViewportSize); - // In some cases before-first-paint gets called before // RecvUpdateDimensions is called and therefore before we have an // mInnerSize value set. In such cases defer initializing the viewport diff --git a/dom/network/interfaces/moz.build b/dom/network/interfaces/moz.build index a078c7b1be28..fb243fba1be2 100644 --- a/dom/network/interfaces/moz.build +++ b/dom/network/interfaces/moz.build @@ -18,6 +18,7 @@ XPIDL_SOURCES += [ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': XPIDL_SOURCES += [ 'nsIDOMNetworkStatsManager.idl', + 'nsIEthernetManager.idl', 'nsINetworkStatsServiceProxy.idl', ] diff --git a/dom/network/interfaces/nsIEthernetManager.idl b/dom/network/interfaces/nsIEthernetManager.idl new file mode 100644 index 000000000000..0fcb30fe309d --- /dev/null +++ b/dom/network/interfaces/nsIEthernetManager.idl @@ -0,0 +1,137 @@ +/* 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/. */ + +#include "nsISupports.idl" + +[scriptable, function, uuid(2a3ad56c-edc0-439f-8aae-900b331ddf49)] +interface nsIEthernetManagerCallback : nsISupports +{ + /** + * Callback function used to report the success of different operations. + * + * @param success + * Boolean value indicates the success of an operation. + * @prarm message + * Message reported in the end of operation. + */ + void notify(in boolean success, in DOMString message); +}; + +[scriptable, function, uuid(1746e7dd-92d4-43fa-8ef4-bc13d0b60353)] +interface nsIEthernetManagerScanCallback : nsISupports +{ + /** + * Callback function used to report the result of scan function. + * + * @param list + * List of available ethernet interfaces. + */ + void notify(in jsval list); +}; + +/** + * An internal idl provides control to ethernet interfaces. + */ +[scriptable, uuid(a96441dd-36b3-4f7f-963b-2c032e28a039)] +interface nsIEthernetManager : nsISupports +{ + /** + * List of exisiting interface name. + */ + readonly attribute jsval interfaceList; + + /** + * Scan available ethernet interfaces on device. + * + * @param callback + * Callback function. + */ + void scan(in nsIEthernetManagerScanCallback callback); + + /** + * Add a new interface to the interface list. + * + * @param ifname + * Interface name. Should be the form of "eth*". + * @param callback + * Callback function. + */ + void addInterface(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Remove an existing interface from the interface list. + * + * @param ifname + * Interface name. + * @param Callback + * Callback function. + */ + void removeInterface(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Update a conifg of an existing interface in the interface list. + * + * @param ifname + * Interface name. + * @param config + * .ip: ip address. + * .prefixLength: mask length. + * .gateway: gateway. + * .dnses: dnses. + * .httpProxyHost: http proxy host. + * .httpProxyPort: http porxy port. + * .ipMode: ip mode, can be 'dhcp' or 'static'. + * @param callback + * Callback function. + */ + void updateInterfaceConfig(in DOMString ifname, + in jsval config, + in nsIEthernetManagerCallback callback); + + /** + * Enable networking of an existing interface in the interface list. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void enable(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Disable networking of an existing interface in the interface list. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void disable(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Make an existing interface connect to network. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void connect(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Disconnect a connected interface in the interface list. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void disconnect(in DOMString ifname, + in nsIEthernetManagerCallback callback); +}; diff --git a/dom/network/src/EthernetManager.js b/dom/network/src/EthernetManager.js new file mode 100644 index 000000000000..5b7a50ed9a10 --- /dev/null +++ b/dom/network/src/EthernetManager.js @@ -0,0 +1,619 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* 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"; + +const DEBUG = false; +function debug(s) { + if (DEBUG) { + dump("-*- EthernetManager: " + s + "\n"); + } +} + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +const TOPIC_INTERFACE_STATE_CHANGED = "network-interface-state-changed"; + +const ETHERNET_NETWORK_IFACE_PREFIX = "eth"; +const DEFAULT_ETHERNET_NETWORK_IFACE = "eth0"; + +const INTERFACE_IPADDR_NULL = "0.0.0.0"; +const INTERFACE_GATEWAY_NULL = "0.0.0.0"; +const INTERFACE_PREFIX_NULL = 0; +const INTERFACE_MACADDR_NULL = "00:00:00:00:00:00"; + +const NETWORK_INTERFACE_UP = "up"; +const NETWORK_INTERFACE_DOWN = "down"; + +const IP_MODE_DHCP = "dhcp"; +const IP_MODE_STATIC = "static"; + +XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager", + "@mozilla.org/network/manager;1", + "nsINetworkManager"); + +XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService", + "@mozilla.org/network/service;1", + "nsINetworkService"); + + +// nsINetworkInterface + +function EthernetInterface(attr) { + this.state = attr.state; + this.type = attr.type; + this.name = attr.name; + this.ipMode = attr.ipMode; + this.ips = [attr.ip]; + this.prefixLengths = [attr.prefixLength]; + this.gateways = [attr.gateway]; + this.dnses = attr.dnses; + this.httpProxyHost = ""; + this.httpProxyPort = 0; +} +EthernetInterface.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]), + + updateConfig: function(config) { + debug("Interface " + this.name + " updateConfig " + JSON.stringify(config)); + this.state = (config.state != undefined) ? + config.state : this.state; + this.ips = (config.ip != undefined) ? [config.ip] : this.ips; + this.prefixLengths = (config.prefixLength != undefined) ? + [config.prefixLength] : this.prefixLengths; + this.gateways = (config.gateway != undefined) ? + [config.gateway] : this.gateways; + this.dnses = (config.dnses != undefined) ? config.dnses : this.dnses; + this.httpProxyHost = (config.httpProxyHost != undefined) ? + config.httpProxyHost : this.httpProxyHost; + this.httpProxyPort = (config.httpProxyPort != undefined) ? + config.httpProxyPort : this.httpProxyPort; + this.ipMode = (config.ipMode != undefined) ? + config.ipMode : this.ipMode; + }, + + getAddresses: function(ips, prefixLengths) { + ips.value = this.ips.slice(); + prefixLengths.value = this.prefixLengths.slice(); + + return this.ips.length; + }, + + getGateways: function(count) { + if (count) { + count.value = this.gateways.length; + } + return this.gateways.slice(); + }, + + getDnses: function(count) { + if (count) { + count.value = this.dnses.length; + } + return this.dnses.slice(); + } +}; + +// nsIEthernetManager + +/* + * Network state transition diagram + * + * ---------- enable --------- connect ----------- disconnect -------------- + * | Disabled | -----> | Enabled | -------> | Connected | <----------> | Disconnected | + * ---------- --------- ----------- connect -------------- + * ^ | | | + * | disable | | | + * ----------------------------------------------------------------------- + */ + +function EthernetManager() { + debug("EthernetManager start"); + + // Interface list. + this.ethernetInterfaces = {}; + + // Used to memorize last connection information. + this.lastStaticConfig = {}; + + Services.obs.addObserver(this, "xpcom-shutdown", false); +} + +EthernetManager.prototype = { + classID: Components.ID("a96441dd-36b3-4f7f-963b-2c032e28a039"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIEthernetManager]), + + ethernetInterfaces: null, + lastStaticConfig: null, + + observer: function(subject, topic, data) { + switch (topic) { + case "xpcom-shutdown": + debug("xpcom-shutdown"); + + this._shutdown(); + + Services.obs.removeObserver(this, "xpcom-shutdown"); + break; + } + }, + + _shutdown: function() { + debug("shuting down."); + (function onRemove(ifnameList) { + if (!ifnameList.length) { + return; + } + + let ifname = ifnameList.shift(); + this.removeInterface(ifname, { notify: onRemove.bind(this, ifnameList) }); + }).call(this, Object.keys(this.ethernetInterfaces)); + }, + + get interfaceList() { + return Object.keys(this.ethernetInterfaces); + }, + + scan: function(callback) { + debug("scan"); + + gNetworkService.getInterfaces(function(success, list) { + let ethList = []; + + if (!success) { + if (callback) { + callback.notify(ethList); + } + return; + } + + for (let i = 0; i < list.length; i++) { + debug("Found interface " + list[i]); + if (!list[i].startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) { + continue; + } + ethList.push(list[i]); + } + + if (callback) { + callback.notify(ethList); + } + }); + }, + + addInterface: function(ifname, callback) { + debug("addInterfaces " + ifname); + + if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) { + if (callback) { + callback.notify(false, "Invalid interface."); + } + return; + } + + if (this.ethernetInterfaces[ifname]) { + if (callback) { + callback.notify(true, "Interface already exists."); + } + return; + } + + gNetworkService.getInterfaceConfig(ifname, function(success, result) { + if (!success) { + if (callback) { + callback.notify(false, "Netd error."); + } + return; + } + + // Since the operation may still succeed with an invalid interface name, + // check the mac address as well. + if (result.macAddr == INTERFACE_MACADDR_NULL) { + if (callback) { + callback.notify(false, "Interface not found."); + } + return; + } + + this.ethernetInterfaces[ifname] = new EthernetInterface({ + state: result.link == NETWORK_INTERFACE_UP ? + Ci.nsINetworkInterface.NETWORK_STATE_DISABLED : + Ci.nsINetworkInterface.NETWORK_STATE_ENABLED, + name: ifname, + type: Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET, + ip: result.ip, + prefixLength: result.prefix, + ipMode: IP_MODE_DHCP + }); + + // Register the interface to NetworkManager. + gNetworkManager.registerNetworkInterface(this.ethernetInterfaces[ifname]); + + debug("Add interface " + ifname + " success with " + + JSON.stringify(this.ethernetInterfaces[ifname])); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }, + + removeInterface: function(ifname, callback) { + debug("removeInterface"); + + if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) { + if (callback) { + callback.notify(false, "Invalid interface."); + } + return; + } + + if (!this.ethernetInterfaces[ifname]) { + if (callback) { + callback.notify(true, "Interface does not exist."); + } + return; + } + + // Make sure interface is disable before removing. + this.disable(ifname, { notify: function(success, message) { + // Unregister the interface from NetworkManager and also remove it from + // the interface list. + gNetworkManager.unregisterNetworkInterface(this.ethernetInterfaces[ifname]); + delete this.ethernetInterfaces[ifname]; + + debug("Remove interface " + ifname + " success."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)}); + }, + + updateInterfaceConfig: function(ifname, config, callback) { + debug("interfaceConfigUpdate with " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + if (!config) { + if (callback) { + callback.notify(false, "No config to update."); + } + return; + } + + // Network state can not be modified externally. + if (config.state) { + delete config.state; + } + + let currentIpMode = iface.ipMode; + + // Update config. + this.ethernetInterfaces[iface.name].updateConfig(config); + + // Do not automatically re-connect if the interface is not in connected + // state. + if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { + if (callback) { + callback.notify(true, "ok"); + } + return; + } + + let newIpMode = this.ethernetInterfaces[iface.name].ipMode; + if (newIpMode == IP_MODE_STATIC) { + this._setStaticIP(iface.name, callback); + return; + } + if ((currentIpMode == IP_MODE_STATIC) && (newIpMode == IP_MODE_DHCP)) { + gNetworkService.stopDhcp(iface.name); + // Clear the current network settings before do dhcp request, otherwise + // dhcp settings could fail. + this.disconnect(iface.name, { notify: function(success, message) { + if (!success) { + if (callback) { + callback.notify("Disconnect failed."); + } + return; + } + this._runDhcp(iface.name, callback); + }.bind(this) }); + return; + } + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }, + + enable: function(ifname, callback) { + debug("enable with " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + // Interface can be only enabled in the state of disabled. + if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_DISABLED) { + if (callback) { + callback.notify(true, "already enabled."); + } + return; + } + + let ips = {}; + let prefixLengths = {}; + iface.getAddresses(ips, prefixLengths); + let config = { ifname: iface.name, + ip: ips.value[0], + prefix: prefixLengths.value[0], + link: NETWORK_INTERFACE_UP }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + if (callback) { + callback.notify(false, "Netd Error."); + } + return; + } + + this.ethernetInterfaces[iface.name].updateConfig({ + state: Ci.nsINetworkInterface.NETWORK_STATE_ENABLED + }); + + debug("Interface " + iface.name + " enable success."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }.bind(this)); + }, + + disable: function(ifname, callback) { + debug("disable with " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_DISABLED) { + if (callback) { + callback.notify(true, "Interface is already disabled."); + } + return; + } + + if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { + gNetworkService.stopDhcp(iface.name); + } + + let ips = {}; + let prefixLengths = {}; + iface.getAddresses(ips, prefixLengths); + let config = { ifname: iface.name, + ip: ips.value[0], + prefix: prefixLengths.value[0], + link: NETWORK_INTERFACE_DOWN }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + if (callback) { + callback.notify(false, "Netd Error."); + } + return; + } + + this.ethernetInterfaces[iface.name].updateConfig({ + state: Ci.nsINetworkInterface.NETWORK_STATE_DISABLED + }); + + debug("Disable interface " + iface.name + " success."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }.bind(this)); + }, + + connect: function(ifname, callback) { + debug("connect wtih " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + // Interface can only be connected in the state of enabled or + // disconnected. + if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_DISABLED || + iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { + if (callback) { + callback.notify(true, "Interface " + ifname + " is not available or " + + " already connected."); + } + return; + } + + if (iface.ipMode == IP_MODE_DHCP) { + this._runDhcp(iface.name, callback); + return; + } + + if (iface.ipMode == IP_MODE_STATIC) { + if (this._checkConfigNull(iface) && this.lastStaticConfig[iface.name]) { + debug("connect with lastStaticConfig " + + JSON.stringify(this.lastStaticConfig[iface.name])); + this.ethernetInterfaces[iface.name].updateConfig( + this.lastStaticConfig[iface.name]); + } + this._setStaticIP(iface.name, callback); + return; + } + + if (callback) { + callback.notify(false, "Ip mode is wrong or not set."); + } + }.bind(this)); + }, + + disconnect: function(ifname, callback) { + debug("disconnect"); + + this._ensureIfname(ifname, callback, function(iface) { + // Interface can be only disconnected in the state of connected. + if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { + if (callback) { + callback.notify(true, "interface is already disconnected"); + } + return; + } + + let config = { ifname: iface.name, + ip: INTERFACE_IPADDR_NULL, + prefix: INTERFACE_PREFIX_NULL, + link: NETWORK_INTERFACE_UP }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + if (callback) { + callback.notify(false, "Netd error."); + } + return; + } + + // Stop dhcp daemon. + gNetworkService.stopDhcp(iface.name); + + this.ethernetInterfaces[iface.name].updateConfig({ + state: Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED, + ip: INTERFACE_IPADDR_NULL, + prefixLength: INTERFACE_PREFIX_NULL, + gateway: INTERFACE_GATEWAY_NULL + }); + + Services.obs.notifyObservers(this.ethernetInterfaces[iface.name], + TOPIC_INTERFACE_STATE_CHANGED, + null); + + debug("Disconnect interface " + iface.name + " success."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }.bind(this)); + }, + + _checkConfigNull: function(iface) { + let ips = {}; + let prefixLengths = {}; + let gateways = iface.getGateways(); + iface.getAddresses(ips, prefixLengths); + + if (ips.value[0] == INTERFACE_IPADDR_NULL && + prefixLengths.value[0] == INTERFACE_PREFIX_NULL && + gateways[0] == INTERFACE_GATEWAY_NULL) { + return true; + } + + return false; + }, + + _ensureIfname: function(ifname, callback, func) { + // If no given ifname, use the default one. + if (!ifname) { + ifname = DEFAULT_ETHERNET_NETWORK_IFACE; + } + + let iface = this.ethernetInterfaces[ifname]; + if (!iface) { + if (callback) { + callback.notify(true, "Interface " + ifname + " is not available."); + } + return; + } + + func.call(this, iface); + }, + + _runDhcp: function(ifname, callback) { + debug("runDhcp with " + ifname); + + if (!this.ethernetInterfaces[ifname]) { + callback.notify(false, "Invalid interface."); + return + } + + gNetworkService.runDhcp(ifname, function(success, result) { + if (!success) { + callback.notify(false, "Dhcp failed."); + return; + } + + debug("Dhcp success with " + JSON.stringify(result)); + + // Clear last static network information when connecting with dhcp mode. + if (this.lastStaticConfig[ifname]) { + this.lastStaticConfig[ifname] = null; + } + + this.ethernetInterfaces[ifname].updateConfig({ + state: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED, + ip: result.ip, + gateway: result.gateway, + prefixLength: result.prefix, + dnses: [result.dns1, result.dns2] + }); + + Services.obs.notifyObservers(this.ethernetInterfaces[ifname], + TOPIC_INTERFACE_STATE_CHANGED, + null); + + debug("Connect interface " + ifname + "with dhcp success."); + + callback.notify(true, "ok"); + }.bind(this)); + }, + + _setStaticIP: function(ifname, callback) { + let iface = this.ethernetInterfaces[ifname]; + if (!iface) { + callback.notify(false, "Invalid interface."); + return; + } + + let ips = {}; + let prefixLengths = {}; + iface.getAddresses(ips, prefixLengths); + + let config = { ifname: iface.name, + ip: ips.value[0], + prefix: prefixLengths.value[0], + link: NETWORK_INTERFACE_UP }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + callback.notify(false, "Netd Error."); + return; + } + + // Keep the lastest static network information. + let ips = {}; + let prefixLengths = {}; + let gateways = iface.getGateways(); + iface.getAddresses(ips, prefixLengths); + + this.lastStaticConfig[iface.name] = { + ip: ips.value[0], + prefixLength: prefixLengths.value[0], + gateway: gateways[0] + }; + + this.ethernetInterfaces[ifname].updateConfig({ + state: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED, + }); + + Services.obs.notifyObservers(this.ethernetInterfaces[ifname], + TOPIC_INTERFACE_STATE_CHANGED, + null); + + debug("Connect interface " + ifname + "with static ip success."); + + callback.notify(true, "ok"); + }.bind(this)); + }, +} + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EthernetManager]); diff --git a/dom/network/src/EthernetManager.manifest b/dom/network/src/EthernetManager.manifest new file mode 100644 index 000000000000..d25a069e1291 --- /dev/null +++ b/dom/network/src/EthernetManager.manifest @@ -0,0 +1,2 @@ +component {a96441dd-36b3-4f7f-963b-2c032e28a039} EthernetManager.js +contract @mozilla.org/ethernetManager;1 {a96441dd-36b3-4f7f-963b-2c032e28a039} diff --git a/dom/network/src/moz.build b/dom/network/src/moz.build index a3b6096f060c..d818c5b35625 100644 --- a/dom/network/src/moz.build +++ b/dom/network/src/moz.build @@ -44,6 +44,8 @@ EXTRA_PP_COMPONENTS += [ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': EXTRA_COMPONENTS += [ + 'EthernetManager.js', + 'EthernetManager.manifest', 'NetworkStatsManager.js', 'NetworkStatsManager.manifest', 'NetworkStatsServiceProxy.js', diff --git a/dom/network/tests/marionette/head.js b/dom/network/tests/marionette/head.js new file mode 100644 index 000000000000..cec6ac7d5792 --- /dev/null +++ b/dom/network/tests/marionette/head.js @@ -0,0 +1,551 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise; + +const ETHERNET_MANAGER_CONTRACT_ID = "@mozilla.org/ethernetManager;1"; + +const INTERFACE_UP = "UP"; +const INTERFACE_DOWN = "DOWN"; + +let gTestSuite = (function() { + let suite = {}; + + // Private member variables of the returned object |suite|. + let ethernetManager = SpecialPowers.Cc[ETHERNET_MANAGER_CONTRACT_ID] + .getService(SpecialPowers.Ci.nsIEthernetManager); + let pendingEmulatorShellCount = 0; + + /** + * Send emulator shell command with safe guard. + * + * We should only call |finish()| after all emulator command transactions + * end, so here comes with the pending counter. Resolve when the emulator + * gives positive response, and reject otherwise. + * + * Fulfill params: an array of emulator response lines. + * Reject params: an array of emulator response lines. + * + * @param command + * A string command to be passed to emulator through its telnet console. + * + * @return A deferred promise. + */ + function runEmulatorShellSafe(command) { + let deferred = Promise.defer(); + + ++pendingEmulatorShellCount; + runEmulatorShell(command, function(aResult) { + --pendingEmulatorShellCount; + + ok(true, "Emulator shell response: " + JSON.stringify(aResult)); + if (Array.isArray(aResult)) { + deferred.resolve(aResult); + } else { + deferred.reject(aResult); + } + }); + + return deferred.promise; + } + + /** + * Get the system network conifg by the given interface name. + * + * Use shell command 'netcfg' to get the list of network cofig. + * + * Fulfill params: An object of { name, flag, ip } + * + * @parm ifname + * Interface name. + * + * @return A deferred promise. + */ + function getNetworkConfig(ifname) { + return runEmulatorShellSafe(['netcfg']) + .then(result => { + // Sample 'netcfg' output: + // + // lo UP 127.0.0.1/8 0x00000049 00:00:00:00:00:00 + // eth0 UP 10.0.2.15/24 0x00001043 52:54:00:12:34:56 + // eth1 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:57 + // rmnet1 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:59 + + let config; + + for (let i = 0; i < result.length; i++) { + let tokens = result[i].split(/\s+/); + let name = tokens[0]; + let flag = tokens[1]; + let ip = tokens[2].split(/\/+/)[0]; + if (name == ifname) { + config = { name: name, flag: flag, ip: ip }; + break; + } + } + + return config; + }); + } + + /** + * Get the ip assigned by dhcp server of a given interface name. + * + * Get the ip from android property 'dhcp.[ifname].ipaddress'. + * + * Fulfill params: A string of ip address. + * + * @parm ifname + * Interface name. + * + * @return A deferred promise. + */ + function getDhcpIpAddr(ifname) { + return runEmulatorShellSafe(['getprop', 'dhcp.' + ifname + '.ipaddress']) + .then(function(ipAddr) { + return ipAddr[0]; + }); + } + + /** + * Get the gateway assigned by dhcp server of a given interface name. + * + * Get the ip from android property 'dhcp.[ifname].gateway'. + * + * Fulfill params: A string of gateway. + * + * @parm ifname + * Interface name. + * + * @return A deferred promise. + */ + function getDhcpGateway(ifname) { + return runEmulatorShellSafe(['getprop', 'dhcp.' + ifname + '.gateway']) + .then(function(gateway) { + return gateway[0]; + }); + } + + /** + * Get the default route. + * + * Use shell command 'ip route' to get the default of device. + * + * Fulfill params: An array of { name, gateway } + * + * @return A deferred promise. + */ + function getDefaultRoute() { + return runEmulatorShellSafe(['ip', 'route']) + .then(result => { + // Sample 'ip route' output: + // + // 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 + // default via 10.0.2.2 dev eth0 metric 2 + + let routeInfo = []; + + for (let i = 0; i < result.length; i++) { + if (!result[i].match('default')) { + continue; + } + + let tokens = result[i].split(/\s+/); + let name = tokens[4]; + let gateway = tokens[2]; + routeInfo.push({ name: name, gateway: gateway }); + } + + return routeInfo; + }); + } + + /** + * Check a specific interface is enabled or not. + * + * @parm ifname + * Interface name. + * @parm enabled + * A boolean value used to check interface is disable or not. + * + * @return A deferred promise. + */ + function checkInterfaceIsEnabled(ifname, enabled) { + return getNetworkConfig(ifname) + .then(function(config) { + if (enabled) { + is(config.flag, INTERFACE_UP, "Interface is enabled as expectation."); + } else { + is(config.flag, INTERFACE_DOWN, "Interface is disabled as expectation."); + } + }); + } + + /** + * Check the ip of a specific interface is equal to given ip or not. + * + * @parm ifname + * Interface name. + * @parm ip + * Given ip address. + * + * @return A deferred promise. + */ + function checkInterfaceIpAddr(ifname, ip) { + return getNetworkConfig(ifname) + .then(function(config) { + is(config.ip, ip, "IP is right as expectation."); + }); + } + + /** + * Check the default gateway of a specific interface is equal to given gateway + * or not. + * + * @parm ifname + * Interface name. + * @parm gateway + * Given gateway. + * + * @return A deferred promise. + */ + function checkDefaultRoute(ifname, gateway) { + return getDefaultRoute() + .then(function(routeInfo) { + for (let i = 0; i < routeInfo.length; i++) { + if (routeInfo[i].name == ifname) { + is(routeInfo[i].gateway, gateway, + "Default gateway is right as expectation."); + return true; + } + } + + if (!gateway) { + ok(true, "Default route is cleared."); + return true; + } + + return false; + }); + } + + /** + * Check the length of interface list in EthernetManager is equal to given + * length or not. + * + * @parm length + * Given length. + */ + function checkInterfaceListLength(length) { + let list = ethernetManager.interfaceList; + is(length, list.length, "List length is equal as expectation."); + } + + /** + * Check the given interface exists on device or not. + * + * @parm ifname + * Interface name. + * + * @return A deferred promise. + */ + function checkInterfaceExist(ifname) { + return scanInterfaces() + .then(list => { + let index = list.indexOf(ifname); + if (index < 0) { + throw "Interface " + ifname + " not found."; + } + + ok(true, ifname + " exists.") + }); + } + + /** + * Scan for available ethernet interfaces. + * + * Fulfill params: A list of available interfaces found in device. + * + * @return A deferred promise. + */ + function scanInterfaces() { + let deferred = Promise.defer(); + + ethernetManager.scan(function onScan(list) { + deferred.resolve(list); + }); + + return deferred.promise; + } + + /** + * Add an interface into interface list. + * + * Fulfill params: A boolean value indicates success or not. + * + * @param ifname + * Interface name. + * + * @return A deferred promise. + */ + function addInterface(ifname) { + let deferred = Promise.defer(); + + ethernetManager.addInterface(ifname, function onAdd(success, message) { + ok(success, "Add interface " + ifname + " success."); + is(message, "ok", "Message is as expectation."); + + deferred.resolve(success); + }); + + return deferred.promise; + } + + /** + * Remove an interface form the interface list. + * + * Fulfill params: A boolean value indicates success or not. + * + * @param ifname + * Interface name. + * + * @return A deferred promise. + */ + function removeInterface(ifname) { + let deferred = Promise.defer(); + + ethernetManager.removeInterface(ifname, function onRemove(success, message) { + ok(success, "Remove interface " + ifname + " success."); + is(message, "ok", "Message is as expectation."); + + deferred.resolve(success); + }); + + return deferred.promise; + } + + /** + * Enable networking of an interface in the interface list. + * + * Fulfill params: A boolean value indicates success or not. + * + * @param ifname + * Interface name. + * + * @return A deferred promise. + */ + function enableInterface(ifname) { + let deferred = Promise.defer(); + + ethernetManager.enable(ifname, function onEnable(success, message) { + ok(success, "Enable interface " + ifname + " success."); + is(message, "ok", "Message is as expectation."); + + deferred.resolve(success); + }); + + return deferred.promise; + } + + /** + * Disable networking of an interface in the interface list. + * + * Fulfill params: A boolean value indicates success or not. + * + * @param ifname + * Interface name. + * + * @return A deferred promise. + */ + function disableInterface(ifname) { + let deferred = Promise.defer(); + + ethernetManager.disable(ifname, function onDisable(success, message) { + ok(success, "Disable interface " + ifname + " success."); + is(message, "ok", "Message is as expectation."); + + deferred.resolve(success); + }); + + return deferred.promise; + } + + /** + * Make an interface connect to network. + * + * Fulfill params: A boolean value indicates success or not. + * + * @param ifname + * Interface name. + * + * @return A deferred promise. + */ + function makeInterfaceConnect(ifname) { + let deferred = Promise.defer(); + + ethernetManager.connect(ifname, function onConnect(success, message) { + ok(success, "Interface " + ifname + " is connected successfully."); + is(message, "ok", "Message is as expectation."); + + deferred.resolve(success); + }); + + return deferred.promise; + } + + /** + * Make an interface disconnect to network. + * + * Fulfill params: A boolean value indicates success or not. + * + * @param ifname + * Interface name. + * + * @return A deferred promise. + */ + function makeInterfaceDisconnect(ifname) { + let deferred = Promise.defer(); + + ethernetManager.disconnect(ifname, function onDisconnect(success, message) { + ok(success, "Interface " + ifname + " is disconnected successfully."); + is(message, "ok", "Message is as expectation."); + + deferred.resolve(success); + }); + + return deferred.promise; + } + + /** + * Update the config the an interface in the interface list. + * + * @param ifname + * Interface name. + * @param config + * .ip: ip address. + * .prefixLength: mask length. + * .gateway: gateway. + * .dnses: dnses. + * .httpProxyHost: http proxy host. + * .httpProxyPort: http porxy port. + * .usingDhcp: an boolean value indicates using dhcp or not. + * + * @return A deferred promise. + */ + function updateInterfaceConfig(ifname, config) { + let deferred = Promise.defer(); + + ethernetManager.updateInterfaceConfig(ifname, config, + function onUpdated(success, message) { + ok(success, "Interface " + ifname + " config is updated successfully " + + " with " + JSON.stringify(config)); + is(message, "ok", "Message is as expectation."); + + deferred.resolve(success); + }); + + return deferred.promise; + } + + /** + * Wait for timeout. + * + * @param timeout + * Time in ms. + * + * @return A deferred promise. + */ + function waitForTimeout(timeout) { + let deferred = Promise.defer(); + + setTimeout(function() { + ok(true, "waitForTimeout " + timeout); + deferred.resolve(); + }, timeout); + + return deferred.promise; + } + + /** + * Wait for default route of a specific interface being set and + * check. + * + * @param ifname + * Interface name. + * @param gateway + * Target gateway. + * + * @return A deferred promise. + */ + function waitForDefaultRouteSet(ifname, gateway) { + return gTestSuite.waitForTimeout(500) + .then(() => gTestSuite.checkDefaultRoute(ifname, gateway)) + .then(success => { + if (success) { + ok(true, "Default route is set as expectation " + gateway); + return; + } + + ok(true, "Default route is not set yet, check again. " + success); + return waitForDefaultRouteSet(ifname, gateway); + }); + } + + //--------------------------------------------------- + // Public test suite functions + //--------------------------------------------------- + suite.scanInterfaces = scanInterfaces; + suite.addInterface = addInterface; + suite.removeInterface = removeInterface; + suite.enableInterface = enableInterface; + suite.disableInterface = disableInterface; + suite.makeInterfaceConnect = makeInterfaceConnect; + suite.makeInterfaceDisconnect = makeInterfaceDisconnect; + suite.updateInterfaceConfig = updateInterfaceConfig; + suite.getDhcpIpAddr = getDhcpIpAddr; + suite.getDhcpGateway = getDhcpGateway; + suite.checkInterfaceExist = checkInterfaceExist; + suite.checkInterfaceIsEnabled = checkInterfaceIsEnabled; + suite.checkInterfaceIpAddr = checkInterfaceIpAddr; + suite.checkDefaultRoute = checkDefaultRoute; + suite.checkInterfaceListLength = checkInterfaceListLength; + suite.waitForTimeout = waitForTimeout; + suite.waitForDefaultRouteSet = waitForDefaultRouteSet; + + /** + * End up the test run. + * + * Wait until all pending emulator shell commands are done and then |finish| + * will be called in the end. + */ + function cleanUp() { + waitFor(finish, function() { + return pendingEmulatorShellCount === 0; + }); + } + + /** + * Common test routine. + * + * Start a test with the given test case chain. The test environment will be + * settled down before the test. After the test, all the affected things will + * be restored. + * + * @param aTestCaseChain + * The test case entry point, which can be a function or a promise. + * + * @return A deferred promise. + */ + suite.doTest = function(aTestCaseChain) { + return Promise.resolve() + .then(aTestCaseChain) + .then(function onresolve() { + cleanUp(); + }, function onreject(aReason) { + ok(false, 'Promise rejects during test' + (aReason ? '(' + aReason + ')' : '')); + cleanUp(); + }); + }; + + return suite; +})(); \ No newline at end of file diff --git a/dom/network/tests/marionette/manifest.ini b/dom/network/tests/marionette/manifest.ini new file mode 100644 index 000000000000..2273a34b300c --- /dev/null +++ b/dom/network/tests/marionette/manifest.ini @@ -0,0 +1,15 @@ +[DEFAULT] +b2g = true +browser = false +qemu = true + +[test_ethernet_add_interface.js] +[test_ethernet_remove_interface.js] +[test_ethernet_enable.js] +[test_ethernet_disable.js] +[test_ethernet_connect_with_dhcp.js] +[test_ethernet_connect_with_static_ip.js] +[test_ethernet_reconnect_with_dhcp.js] +[test_ethernet_reconnect_with_static_ip.js] +[test_ethernet_ip_mode_change.js] +[test_ethernet_disconnect.js] diff --git a/dom/network/tests/marionette/test_ethernet_add_interface.js b/dom/network/tests/marionette/test_ethernet_add_interface.js new file mode 100644 index 000000000000..d628e77054b1 --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_add_interface.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.checkInterfaceListLength(0)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.checkInterfaceListLength(1)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_connect_with_dhcp.js b/dom/network/tests/marionette/test_ethernet_connect_with_dhcp.js new file mode 100644 index 000000000000..57c2df9c2ec3 --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_connect_with_dhcp.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +function checkDhcpResult(ifname) { + return gTestSuite.getDhcpIpAddr(ifname) + .then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip)) + .then(() => gTestSuite.getDhcpGateway(ifname)) + .then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway)); +} + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_connect_with_static_ip.js b/dom/network/tests/marionette/test_ethernet_connect_with_static_ip.js new file mode 100644 index 000000000000..3adc37b23caa --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_connect_with_static_ip.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +let staticConfig = { + ip: "1.2.3.4", + gateway: "1.2.3.5", + prefixLength: 24, + dnses: ["1.2.3.6"], + ipMode: "static" +}; + +function checkStaticResult(ifname) { + return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip) + .then(() => gTestSuite.checkDefaultRoute(ifname, staticConfig.gateway)); +} + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_disable.js b/dom/network/tests/marionette/test_ethernet_disable.js new file mode 100644 index 000000000000..9c3525faa341 --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_disable.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.checkInterfaceIsEnabled(ETHERNET_INTERFACE_NAME, false)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_disconnect.js b/dom/network/tests/marionette/test_ethernet_disconnect.js new file mode 100644 index 000000000000..73f6aa3c5e83 --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_disconnect.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; +const INTERFACE_IP_NONE = "0.0.0.0"; + +function checkIpAddrIsReset(ifname) { + return gTestSuite.checkInterfaceIpAddr(ifname, INTERFACE_IP_NONE) + .then(() => gTestSuite.checkDefaultRoute(ifname)); +} + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkIpAddrIsReset(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_enable.js b/dom/network/tests/marionette/test_ethernet_enable.js new file mode 100644 index 000000000000..f5578a44f80b --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_enable.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.checkInterfaceIsEnabled(ETHERNET_INTERFACE_NAME, true)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_ip_mode_change.js b/dom/network/tests/marionette/test_ethernet_ip_mode_change.js new file mode 100644 index 000000000000..5db2049bef7d --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_ip_mode_change.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +let staticConfig = { + ip: "1.2.3.4", + gateway: "1.2.3.5", + prefixLength: 24, + dnses: ["1.2.3.6"], + ipMode: "static" +}; + +function checkStaticResult(ifname) { + return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip) + .then(() => gTestSuite.waitForDefaultRouteSet(ifname, staticConfig.gateway)); +} + +function checkDhcpResult(ifname) { + return gTestSuite.getDhcpIpAddr(ifname) + .then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip)) + .then(() => gTestSuite.getDhcpGateway(ifname)) + .then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway)); +} + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig)) + .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, { ipMode: "dhcp"})) + .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_reconnect_with_dhcp.js b/dom/network/tests/marionette/test_ethernet_reconnect_with_dhcp.js new file mode 100644 index 000000000000..4ecc8f09ef51 --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_reconnect_with_dhcp.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +function checkDhcpResult(ifname) { + return gTestSuite.getDhcpIpAddr(ifname) + .then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip)) + .then(() => gTestSuite.getDhcpGateway(ifname)) + .then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway)); +} + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_reconnect_with_static_ip.js b/dom/network/tests/marionette/test_ethernet_reconnect_with_static_ip.js new file mode 100644 index 000000000000..91f25a4710d1 --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_reconnect_with_static_ip.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +let staticConfig = { + ip: "1.2.3.4", + gateway: "1.2.3.5", + prefixLength: 24, + dnses: ["1.2.3.6"], + ipMode: "static" +}; + +function checkStaticResult(ifname) { + return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip) + .then(() => gTestSuite.checkDefaultRoute(ifname, staticConfig.gateway)); +} + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME)) + .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)); +}); \ No newline at end of file diff --git a/dom/network/tests/marionette/test_ethernet_remove_interface.js b/dom/network/tests/marionette/test_ethernet_remove_interface.js new file mode 100644 index 000000000000..c7fb0e81b88b --- /dev/null +++ b/dom/network/tests/marionette/test_ethernet_remove_interface.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +const ETHERNET_INTERFACE_NAME = "eth1"; + +gTestSuite.doTest(function() { + return Promise.resolve() + .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.checkInterfaceListLength(1)) + .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME)) + .then(() => gTestSuite.checkInterfaceListLength(0)); +}); \ No newline at end of file diff --git a/dom/nfc/tests/marionette/head.js b/dom/nfc/tests/marionette/head.js index 3120ee1c7e96..d2c503553351 100644 --- a/dom/nfc/tests/marionette/head.js +++ b/dom/nfc/tests/marionette/head.js @@ -45,6 +45,18 @@ let emulator = (function() { return deferred.promise; }; + function deactivate() { + let deferred = Promise.defer(); + let cmd = 'nfc nci rf_intf_deactivate_ntf'; + + this.run(cmd, function(result) { + is(result.pop(), 'OK', 'check deactivate'); + deferred.resolve(); + }); + + return deferred.promise; + }; + function notifyDiscoverRE(re, type) { let deferred = Promise.defer(); let cmd = 'nfc nci rf_discover_ntf ' + re + ' ' + type; @@ -88,6 +100,7 @@ let emulator = (function() { return { run: run, activateRE: activateRE, + deactivate: deactivate, notifyDiscoverRE: notifyDiscoverRE, setTagData: setTagData, snepPutNdef: snepPutNdef diff --git a/dom/nfc/tests/marionette/manifest.ini b/dom/nfc/tests/marionette/manifest.ini index 351bb0b8b66e..aecfef0d7c3a 100644 --- a/dom/nfc/tests/marionette/manifest.ini +++ b/dom/nfc/tests/marionette/manifest.ini @@ -7,6 +7,7 @@ qemu=true [test_nfc_enabled.js] [test_nfc_manager_tech_discovered.js] [test_nfc_manager_tech_discovered_ndef.js] +[test_nfc_manager_tech_lost.js] [test_nfc_peer.js] [test_nfc_peer_sendndef.js] [test_nfc_tag.js] diff --git a/dom/nfc/tests/marionette/test_nfc_manager_tech_lost.js b/dom/nfc/tests/marionette/test_nfc_manager_tech_lost.js new file mode 100644 index 000000000000..7e6e4742a2a7 --- /dev/null +++ b/dom/nfc/tests/marionette/test_nfc_manager_tech_lost.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 30000; +MARIONETTE_HEAD_JS = 'head.js'; + +function handleTechnologyLost(msg) { + log('Received \'nfc-manager-tech-lost\''); + is(msg.type, 'techLost', 'check for correct message type'); + + toggleNFC(false).then(runNextTest) +} + +function handleTechnologyDiscoveredRE0(msg) { + log('Received \'nfc-manager-tech-discovered\''); + is(msg.type, 'techDiscovered', 'check for correct message type'); + is(msg.techList[0], 'P2P', 'check for correct tech type'); + + emulator.deactivate(); +} + +function testTechLost() { + log('Running \'testTechLost\''); + window.navigator.mozSetMessageHandler( + 'nfc-manager-tech-discovered', handleTechnologyDiscoveredRE0); + window.navigator.mozSetMessageHandler( + 'nfc-manager-tech-lost', handleTechnologyLost); + + toggleNFC(true).then(() => emulator.activateRE(0)); +} + +let tests = [ + testTechLost +]; + +SpecialPowers.pushPermissions( + [{'type': 'nfc-manager', 'allow': true, context: document}], runTests); diff --git a/dom/system/gonk/NetworkManager.js b/dom/system/gonk/NetworkManager.js index 8a087e1b551a..e440a2344b97 100644 --- a/dom/system/gonk/NetworkManager.js +++ b/dom/system/gonk/NetworkManager.js @@ -13,9 +13,9 @@ Cu.import("resource://gre/modules/systemlibs.js"); const NETWORKMANAGER_CONTRACTID = "@mozilla.org/network/manager;1"; const NETWORKMANAGER_CID = - Components.ID("{33901e46-33b8-11e1-9869-f46d04d25bcc}"); + Components.ID("{1ba9346b-53b5-4660-9dc6-58f0b258d0a6}"); -const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI; +const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET; XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService", "@mozilla.org/settingsService;1", @@ -276,7 +276,8 @@ NetworkManager.prototype = { if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { gNetworkService.resetRoutingTable(network); #ifdef MOZ_B2G_RIL - } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { + } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE || + network.type == Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) { gNetworkService.removeDefaultRoute(network); #endif } @@ -367,7 +368,9 @@ NetworkManager.prototype = { getNetworkId: function(network) { let id = "device"; #ifdef MOZ_B2G_RIL - if (this.isNetworkTypeMobile(network.type)) { + if (this.isNetworkTypeEthernet(network.type)) { + id = network.name.substring(3); + } else if (this.isNetworkTypeMobile(network.type)) { if (!(network instanceof Ci.nsIRilNetworkInterface)) { throw Components.Exception("Mobile network not an nsIRilNetworkInterface", Cr.NS_ERROR_INVALID_ARG); @@ -419,17 +422,51 @@ NetworkManager.prototype = { _dataDefaultServiceId: null, + _networkTypePriorityList: [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET, + Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE], + get networkTypePriorityList() { + return this._networkTypePriorityList; + }, + set networkTypePriorityList(val) { + if (val.length != this._networkTypePriorityList.length) { + throw "Priority list length should equal to " + + this._networkTypePriorityList.length; + } + + // Check if types in new priority list are valid and also make sure there + // are no duplicate types. + let list = [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET, + Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE]; + while (list.length) { + let type = list.shift(); + if (val.indexOf(type) == -1) { + throw "There is missing network type"; + } + } + + this._networkTypePriorityList = val; + }, + + getPriority: function(type) { + if (this._networkTypePriorityList.indexOf(type) == -1) { + // 0 indicates the lowest priority. + return 0; + } + + return this._networkTypePriorityList.length - + this._networkTypePriorityList.indexOf(type); + }, + _preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE, get preferredNetworkType() { return this._preferredNetworkType; }, set preferredNetworkType(val) { -#ifdef MOZ_B2G_RIL if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, - Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE].indexOf(val) == -1) { -#else - if (val != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { -#endif + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, + Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET].indexOf(val) == -1) { throw "Invalid network type"; } this._preferredNetworkType = val; @@ -461,6 +498,10 @@ NetworkManager.prototype = { this.isNetworkTypeSecondaryMobile(type)); }, + isNetworkTypeEthernet: function(type) { + return (type == Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET); + }, + setExtraHostRoute: function(network) { if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) { if (!(network instanceof Ci.nsIRilNetworkInterface)) { @@ -601,11 +642,23 @@ NetworkManager.prototype = { defaultDataNetwork = network; } #endif - this.active = network; if (network.type == this.preferredNetworkType) { + this.active = network; debug("Found our preferred type of network: " + network.name); break; } + + // Initialize the active network with the first connected network. + if (!this.active) { + this.active = network; + continue; + } + + // Compare the prioriy between two network types. If found incoming + // network with higher priority, replace the active network. + if (this.getPriority(this.active.type) < this.getPriority(network.type)) { + this.active = network; + } } if (this.active) { #ifdef MOZ_B2G_RIL @@ -680,7 +733,8 @@ NetworkManager.prototype = { // the function will return null so that it won't trigger type change event // in NetworkInformation API. if (network.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && - network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { + network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE && + network.type != Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) { return null; } @@ -693,6 +747,8 @@ NetworkManager.prototype = { return CONNECTION_TYPE_WIFI; case Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE: return CONNECTION_TYPE_CULLULAR; + case Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET: + return CONNECTION_TYPE_ETHERNET; } }, diff --git a/dom/system/gonk/NetworkManager.manifest b/dom/system/gonk/NetworkManager.manifest index 172af047fa6b..995fa65598ea 100644 --- a/dom/system/gonk/NetworkManager.manifest +++ b/dom/system/gonk/NetworkManager.manifest @@ -1,3 +1,3 @@ # NetworkManager.js -component {33901e46-33b8-11e1-9869-f46d04d25bcc} NetworkManager.js -contract @mozilla.org/network/manager;1 {33901e46-33b8-11e1-9869-f46d04d25bcc} +component {1ba9346b-53b5-4660-9dc6-58f0b258d0a6} NetworkManager.js +contract @mozilla.org/network/manager;1 {1ba9346b-53b5-4660-9dc6-58f0b258d0a6} diff --git a/dom/system/gonk/NetworkService.js b/dom/system/gonk/NetworkService.js index b81b7feb857e..f5a4b39a0a83 100644 --- a/dom/system/gonk/NetworkService.js +++ b/dom/system/gonk/NetworkService.js @@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); const NETWORKSERVICE_CONTRACTID = "@mozilla.org/network/service;1"; -const NETWORKSERVICE_CID = Components.ID("{baec696c-c78d-42db-8b44-603f8fbfafb4}"); +const NETWORKSERVICE_CID = Components.ID("{48c13741-aec9-4a86-8962-432011708261}"); XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker", "@mozilla.org/network/worker;1", @@ -542,6 +542,81 @@ NetworkService.prototype = { }); }, + getInterfaces: function(callback) { + let params = { + cmd: "getInterfaces", + isAsync: true + }; + + this.controlMessage(params, function(data) { + if(DEBUG) debug("getInterfaces result: " + JSON.stringify(data)); + let success = !isError(data.resultCode); + callback.getInterfacesResult(success, data.interfaceList); + }); + }, + + setInterfaceConfig: function(config, callback) { + config.cmd = "setInterfaceConfig"; + config.isAsync = true; + + this.controlMessage(config, function(data) { + if(DEBUG) debug("setInterfaceConfig result: " + JSON.stringify(data)); + let success = !isError(data.resultCode); + callback.setInterfaceConfigResult(success); + }); + }, + + getInterfaceConfig: function(ifname, callback) { + let params = { + cmd: "getInterfaceConfig", + ifname: ifname, + isAsync: true + }; + + this.controlMessage(params, function(data) { + if(DEBUG) debug("getInterfaceConfig result: " + JSON.stringify(data)); + let success = !isError(data.resultCode); + let result = { ip: data.ipAddr, + prefix: data.maskLength, + link: data.flag, + mac: data.macAddr }; + callback.getInterfaceConfigResult(success, result); + }); + }, + + runDhcp: function(ifname, callback) { + let params = { + cmd: "runDhcp", + ifname: ifname, + isBlocking: true + }; + + this.controlMessage(params, function(data) { + if(DEBUG) debug("runDhcp result: " + JSON.stringify(data)); + let success = data.success; + let result = { + ip: data.ipAddr, + gateway: data.gateway, + dns1: data.dns1, + dns2: data.dns2, + prefix: data.maskLength, + server: data.server + }; + + callback.runDhcpResult(success, result); + }); + }, + + stopDhcp: function(ifname) { + let params = { + cmd: "stopDhcp", + ifname: ifname, + isAsync: true + }; + + this.controlMessage(params); + }, + shutdown: false, observe: function observe(aSubject, aTopic, aData) { diff --git a/dom/system/gonk/NetworkService.manifest b/dom/system/gonk/NetworkService.manifest index 1e7164d537ca..caf8f2554ffc 100644 --- a/dom/system/gonk/NetworkService.manifest +++ b/dom/system/gonk/NetworkService.manifest @@ -1,3 +1,3 @@ # NetworkService.js -component {baec696c-c78d-42db-8b44-603f8fbfafb4} NetworkService.js -contract @mozilla.org/network/service;1 {baec696c-c78d-42db-8b44-603f8fbfafb4} +component {48c13741-aec9-4a86-8962-432011708261} NetworkService.js +contract @mozilla.org/network/service;1 {48c13741-aec9-4a86-8962-432011708261} diff --git a/dom/system/gonk/NetworkUtils.cpp b/dom/system/gonk/NetworkUtils.cpp index 59534d31735b..fbaadbb05eb6 100644 --- a/dom/system/gonk/NetworkUtils.cpp +++ b/dom/system/gonk/NetworkUtils.cpp @@ -111,7 +111,7 @@ CommandFunc NetworkUtils::sWifiEnableChain[] = { NetworkUtils::startAccessPointDriver, NetworkUtils::setAccessPoint, NetworkUtils::startSoftAP, - NetworkUtils::setInterfaceUp, + NetworkUtils::setConfig, NetworkUtils::tetherInterface, NetworkUtils::setIpForwardingEnabled, NetworkUtils::tetheringStatus, @@ -152,7 +152,7 @@ CommandFunc NetworkUtils::sWifiRetryChain[] = { NetworkUtils::startAccessPointDriver, NetworkUtils::setAccessPoint, NetworkUtils::startSoftAP, - NetworkUtils::setInterfaceUp, + NetworkUtils::setConfig, NetworkUtils::tetherInterface, NetworkUtils::setIpForwardingEnabled, NetworkUtils::tetheringStatus, @@ -168,7 +168,7 @@ CommandFunc NetworkUtils::sWifiOperationModeChain[] = { }; CommandFunc NetworkUtils::sUSBEnableChain[] = { - NetworkUtils::setInterfaceUp, + NetworkUtils::setConfig, NetworkUtils::enableNat, NetworkUtils::setIpForwardingEnabled, NetworkUtils::tetherInterface, @@ -201,7 +201,7 @@ CommandFunc NetworkUtils::sUpdateUpStreamChain[] = { }; CommandFunc NetworkUtils::sStartDhcpServerChain[] = { - NetworkUtils::setInterfaceUp, + NetworkUtils::setConfig, NetworkUtils::startTethering, NetworkUtils::setDhcpServerSuccess }; @@ -240,6 +240,21 @@ CommandFunc NetworkUtils::sSetDnsChain[] = { NetworkUtils::setInterfaceDns }; +CommandFunc NetworkUtils::sGetInterfacesChain[] = { + NetworkUtils::getInterfaceList, + NetworkUtils::getInterfacesSuccess +}; + +CommandFunc NetworkUtils::sSetInterfaceConfigChain[] = { + NetworkUtils::setConfig, + NetworkUtils::setInterfaceConfigSuccess +}; + +CommandFunc NetworkUtils::sGetInterfaceConfigChain[] = { + NetworkUtils::getConfig, + NetworkUtils::getInterfaceConfigSuccess +}; + /** * Helper function to get the mask from given prefix length. */ @@ -322,6 +337,15 @@ static void join(nsTArray& array, #undef CHECK_LEN } +static void convertUTF8toUTF16(nsTArray& narrow, + nsTArray& wide, + uint32_t length) +{ + for (uint32_t i = 0; i < length; i++) { + wide.AppendElement(NS_ConvertUTF8toUTF16(narrow[i].get())); + } +} + /** * Helper function to get network interface properties from the system property table. */ @@ -686,9 +710,9 @@ void NetworkUtils::setAlarm(CommandChain* aChain, doCommand(command, aChain, aCallback); } -void NetworkUtils::setInterfaceUp(CommandChain* aChain, - CommandCallback aCallback, - NetworkResultOptions& aResult) +void NetworkUtils::setConfig(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) { char command[MAX_COMMAND_SIZE]; if (SDK_VERSION >= 16) { @@ -923,6 +947,26 @@ void NetworkUtils::setInterfaceDns(CommandChain* aChain, doCommand(command, aChain, aCallback); } +void NetworkUtils::getInterfaceList(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char command[MAX_COMMAND_SIZE]; + snprintf(command, MAX_COMMAND_SIZE - 1, "interface list"); + + doCommand(command, aChain, aCallback); +} + +void NetworkUtils::getConfig(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char command[MAX_COMMAND_SIZE]; + snprintf(command, MAX_COMMAND_SIZE - 1, "interface getcfg %s", GET_CHAR(mIfname)); + + doCommand(command, aChain, aCallback); +} + #undef GET_CHAR #undef GET_FIELD @@ -1063,6 +1107,75 @@ void NetworkUtils::setDnsFail(NetworkParams& aOptions, NetworkResultOptions& aRe postMessage(aOptions, aResult); } +void NetworkUtils::getInterfacesFail(NetworkParams& aOptions, NetworkResultOptions& aResult) +{ + postMessage(aOptions, aResult); +} + +void NetworkUtils::getInterfacesSuccess(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char buf[BUF_SIZE]; + NS_ConvertUTF16toUTF8 reason(aResult.mResultReason); + memcpy(buf, reason.get(), strlen(reason.get())); + + nsTArray result; + split(buf, INTERFACE_DELIMIT, result); + + nsTArray interfaceList; + uint32_t length = result.Length(); + convertUTF8toUTF16(result, interfaceList, length); + + aResult.mInterfaceList.Construct(); + for (uint32_t i = 0; i < length; i++) { + aResult.mInterfaceList.Value().AppendElement(interfaceList[i]); + } + + postMessage(aChain->getParams(), aResult); +} + +void NetworkUtils::setInterfaceConfigFail(NetworkParams& aOptions, NetworkResultOptions& aResult) +{ + postMessage(aOptions, aResult); +} + +void NetworkUtils::setInterfaceConfigSuccess(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + postMessage(aChain->getParams(), aResult); +} + +void NetworkUtils::getInterfaceConfigFail(NetworkParams& aOptions, NetworkResultOptions& aResult) +{ + postMessage(aOptions, aResult); +} + +void NetworkUtils::getInterfaceConfigSuccess(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char buf[BUF_SIZE]; + NS_ConvertUTF16toUTF8 reason(aResult.mResultReason); + memcpy(buf, reason.get(), strlen(reason.get())); + + nsTArray result; + split(buf, NETD_MESSAGE_DELIMIT, result); + + ASSIGN_FIELD_VALUE(mMacAddr, NS_ConvertUTF8toUTF16(result[0])) + ASSIGN_FIELD_VALUE(mIpAddr, NS_ConvertUTF8toUTF16(result[1])) + ASSIGN_FIELD_VALUE(mMaskLength, atof(result[2].get())) + + if (result[3].Find("up")) { + ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("up")) + } else { + ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("down")) + } + + postMessage(aChain->getParams(), aResult); +} + #undef ASSIGN_FIELD #undef ASSIGN_FIELD_VALUE @@ -1127,6 +1240,14 @@ void NetworkUtils::ExecuteCommand(NetworkParams aOptions) enableUsbRndis(aOptions); } else if (aOptions.mCmd.EqualsLiteral("updateUpStream")) { updateUpStream(aOptions); + } else if (aOptions.mCmd.EqualsLiteral("getInterfaces")) { + getInterfaces(aOptions); + } else if (aOptions.mCmd.EqualsLiteral("stopDhcp")) { + stopDhcp(aOptions); + } else if (aOptions.mCmd.EqualsLiteral("setInterfaceConfig")) { + setInterfaceConfig(aOptions); + } else if (aOptions.mCmd.EqualsLiteral("getInterfaceConfig")) { + getInterfaceConfig(aOptions); } else { WARN("unknon message"); return; @@ -1716,7 +1837,7 @@ bool NetworkUtils::enableUsbRndis(NetworkParams& aOptions) } /** - * handling upstream interface change event. + * Handling upstream interface change event. */ bool NetworkUtils::updateUpStream(NetworkParams& aOptions) { @@ -1724,6 +1845,42 @@ bool NetworkUtils::updateUpStream(NetworkParams& aOptions) return true; } +/** + * Stop dhcp client deamon. + */ +bool NetworkUtils::stopDhcp(NetworkParams& aOptions) +{ + mNetUtils->do_dhcp_stop(GET_CHAR(mIfname)); + return true; +} + +/** + * Get existing network interfaces. + */ +bool NetworkUtils::getInterfaces(NetworkParams& aOptions) +{ + RUN_CHAIN(aOptions, sGetInterfacesChain, getInterfacesFail) + return true; +} + +/** + * Set network config for a specified interface. + */ +bool NetworkUtils::setInterfaceConfig(NetworkParams& aOptions) +{ + RUN_CHAIN(aOptions, sSetInterfaceConfigChain, setInterfaceConfigFail) + return true; +} + +/** + * Get network config of a specified interface. + */ +bool NetworkUtils::getInterfaceConfig(NetworkParams& aOptions) +{ + RUN_CHAIN(aOptions, sGetInterfaceConfigChain, getInterfaceConfigFail) + return true; +} + void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason) { NetworkResultOptions result; diff --git a/dom/system/gonk/NetworkUtils.h b/dom/system/gonk/NetworkUtils.h index bd722fad261b..7046e400b582 100644 --- a/dom/system/gonk/NetworkUtils.h +++ b/dom/system/gonk/NetworkUtils.h @@ -72,6 +72,7 @@ public: mCurInternalIfname = aOther.mCurInternalIfname; mCurExternalIfname = aOther.mCurExternalIfname; mThreshold = aOther.mThreshold; + mIsBlocking = aOther.mIsBlocking; } NetworkParams(const mozilla::dom::NetworkCommandOptions& aOther) { @@ -148,6 +149,7 @@ public: COPY_OPT_STRING_FIELD(mCurInternalIfname, EmptyString()) COPY_OPT_STRING_FIELD(mCurExternalIfname, EmptyString()) COPY_OPT_FIELD(mThreshold, -1) + COPY_OPT_FIELD(mIsBlocking, false) #undef COPY_SEQUENCE_FIELD #undef COPY_OPT_STRING_FIELD @@ -198,6 +200,7 @@ public: nsString mCurInternalIfname; nsString mCurExternalIfname; long mThreshold; + bool mIsBlocking; }; // CommandChain store the necessary information to execute command one by one. @@ -281,6 +284,10 @@ private: bool setUSBTethering(NetworkParams& aOptions); bool enableUsbRndis(NetworkParams& aOptions); bool updateUpStream(NetworkParams& aOptions); + bool getInterfaces(NetworkParams& aOptions); + bool stopDhcp(NetworkParams& aOptions); + bool setInterfaceConfig(NetworkParams& aOptions); + bool getInterfaceConfig(NetworkParams& aOptions); /** * function pointer array holds all netd commands should be executed @@ -302,6 +309,9 @@ private: static CommandFunc sNetworkInterfaceDisableAlarmChain[]; static CommandFunc sNetworkInterfaceSetAlarmChain[]; static CommandFunc sSetDnsChain[]; + static CommandFunc sGetInterfacesChain[]; + static CommandFunc sSetInterfaceConfigChain[]; + static CommandFunc sGetInterfaceConfigChain[]; /** * Individual netd command stored in command chain. @@ -338,6 +348,9 @@ private: static void disableNat(PARAMS); static void setDefaultInterface(PARAMS); static void setInterfaceDns(PARAMS); + static void getInterfaceList(PARAMS); + static void setConfig(PARAMS); + static void getConfig(PARAMS); static void wifiTetheringSuccess(PARAMS); static void usbTetheringSuccess(PARAMS); static void networkInterfaceStatsSuccess(PARAMS); @@ -345,6 +358,9 @@ private: static void updateUpStreamSuccess(PARAMS); static void setDhcpServerSuccess(PARAMS); static void wifiOperationModeSuccess(PARAMS); + static void getInterfacesSuccess(PARAMS); + static void setInterfaceConfigSuccess(PARAMS); + static void getInterfaceConfigSuccess(PARAMS); #undef PARAMS /** @@ -360,6 +376,9 @@ private: static void networkInterfaceStatsFail(PARAMS); static void networkInterfaceAlarmFail(PARAMS); static void setDnsFail(PARAMS); + static void getInterfacesFail(PARAMS); + static void setInterfaceConfigFail(PARAMS); + static void getInterfaceConfigFail(PARAMS); #undef PARAMS /** diff --git a/dom/system/gonk/NetworkWorker.cpp b/dom/system/gonk/NetworkWorker.cpp index 8d7dfe8be2a9..f2e1c77f928b 100644 --- a/dom/system/gonk/NetworkWorker.cpp +++ b/dom/system/gonk/NetworkWorker.cpp @@ -18,6 +18,8 @@ using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::ipc; +#define PROPERTY_VALUE_MAX 80 + namespace mozilla { nsCOMPtr gWorkerThread; @@ -37,6 +39,15 @@ public: MOZ_ASSERT(!NS_IsMainThread()); #define COPY_FIELD(prop) mResult.prop = aResult.prop; +#define COPY_SEQUENCE_FIELD(prop, type) \ + if (aResult.prop.WasPassed()) { \ + mozilla::dom::Sequence const & currentValue = aResult.prop.InternalValue(); \ + uint32_t length = currentValue.Length(); \ + mResult.prop.Construct(); \ + for (uint32_t idx = 0; idx < length; idx++) { \ + mResult.prop.Value().AppendElement(currentValue[idx]); \ + } \ + } COPY_FIELD(mId) COPY_FIELD(mRet) COPY_FIELD(mBroadcast) @@ -53,6 +64,16 @@ public: COPY_FIELD(mSuccess) COPY_FIELD(mCurExternalIfname) COPY_FIELD(mCurInternalIfname) + COPY_FIELD(mIpAddr) + COPY_FIELD(mGateway) + COPY_FIELD(mDns1) + COPY_FIELD(mDns2) + COPY_FIELD(mServer) + COPY_FIELD(mLease) + COPY_FIELD(mVendorInfo) + COPY_FIELD(mMaskLength) + COPY_FIELD(mFlag) + COPY_SEQUENCE_FIELD(mInterfaceList, nsString) #undef COPY_FIELD } @@ -92,6 +113,66 @@ private: NetworkParams mParams; }; +// Runnable used for blocking command. +class RunDhcpEvent : public nsRunnable +{ +public: + RunDhcpEvent(const NetworkParams& aParams) + : mParams(aParams) + {} + + NS_IMETHOD Run() + { + MOZ_ASSERT(!NS_IsMainThread()); + + nsAutoPtr netUtils; + netUtils = new NetUtils(); + + NetworkResultOptions result; + result.mId = mParams.mId; + + int32_t status; + char ipaddr[PROPERTY_VALUE_MAX]; + char gateway[PROPERTY_VALUE_MAX]; + uint32_t prefixLength; + char dns1[PROPERTY_VALUE_MAX]; + char dns2[PROPERTY_VALUE_MAX]; + char server[PROPERTY_VALUE_MAX]; + uint32_t lease; + char vendorinfo[PROPERTY_VALUE_MAX]; + status = netUtils->do_dhcp_do_request(NS_ConvertUTF16toUTF8(mParams.mIfname).get(), + ipaddr, + gateway, + &prefixLength, + dns1, + dns2, + server, + &lease, + vendorinfo); + + + if (status == 0) { + // run dhcp success. + result.mSuccess = true; + result.mIpAddr = NS_ConvertUTF8toUTF16(ipaddr); + result.mGateway = NS_ConvertUTF8toUTF16(gateway); + result.mDns1 = NS_ConvertUTF8toUTF16(dns1); + result.mDns2 = NS_ConvertUTF8toUTF16(dns2); + result.mServer = NS_ConvertUTF8toUTF16(server); + result.mLease = lease; + result.mVendorInfo = NS_ConvertUTF8toUTF16(vendorinfo); + result.mMaskLength = prefixLength; + } + + nsCOMPtr runnable = new NetworkResultDispatcher(result); + NS_DispatchToMainThread(runnable); + + return NS_OK; + } +private: + NetworkParams mParams; +}; + // Runnable used dispatch netd result on the worker thread. class NetdEventRunnable : public nsRunnable { @@ -224,8 +305,14 @@ NetworkWorker::PostMessage(JS::Handle aOptions, JSContext* aCx) return NS_ERROR_FAILURE; } - // Dispatch the command to the control thread. NetworkParams NetworkParams(options); + + if (NetworkParams.mIsBlocking) { + NetworkWorker::HandleBlockingCommand(NetworkParams); + return NS_OK; + } + + // Dispatch the command to the control thread. nsCOMPtr runnable = new NetworkCommandDispatcher(NetworkParams); if (gWorkerThread) { gWorkerThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL); @@ -233,6 +320,24 @@ NetworkWorker::PostMessage(JS::Handle aOptions, JSContext* aCx) return NS_OK; } +void +NetworkWorker::HandleBlockingCommand(NetworkParams& aOptions) +{ + if (aOptions.mCmd.EqualsLiteral("runDhcp")) { + NetworkWorker::RunDhcp(aOptions); + } +} + +void +NetworkWorker::RunDhcp(NetworkParams& aOptions) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr runnable = new RunDhcpEvent(aOptions); + nsCOMPtr thread; + NS_NewThread(getter_AddRefs(thread), runnable); +} + void NetworkWorker::DispatchNetworkResult(const NetworkResultOptions& aOptions) { diff --git a/dom/system/gonk/NetworkWorker.h b/dom/system/gonk/NetworkWorker.h index 32b3ae34b087..aa670405236f 100644 --- a/dom/system/gonk/NetworkWorker.h +++ b/dom/system/gonk/NetworkWorker.h @@ -11,6 +11,8 @@ #include "nsCOMPtr.h" #include "nsThread.h" +class NetworkParams; + namespace mozilla { class NetworkWorker MOZ_FINAL : public nsINetworkWorker @@ -29,6 +31,9 @@ private: static void NotifyResult(mozilla::dom::NetworkResultOptions& aResult); + void HandleBlockingCommand(NetworkParams& aParams); + void RunDhcp(NetworkParams& aParams); + nsCOMPtr mListener; }; diff --git a/dom/system/gonk/nsINetworkManager.idl b/dom/system/gonk/nsINetworkManager.idl index 3bf98b60faa3..0c33984a2c14 100644 --- a/dom/system/gonk/nsINetworkManager.idl +++ b/dom/system/gonk/nsINetworkManager.idl @@ -9,7 +9,7 @@ interface nsIWifiTetheringCallback; /** * Information about networks that is exposed to network manager API consumers. */ -[scriptable, uuid(cb62ae03-6bda-43ff-9560-916d60203d33)] +[scriptable, uuid(8f9ab9e0-72c1-4874-80c7-8143353b979f)] interface nsINetworkInterface : nsISupports { const long NETWORK_STATE_UNKNOWN = -1; @@ -17,6 +17,8 @@ interface nsINetworkInterface : nsISupports const long NETWORK_STATE_CONNECTED = 1; const long NETWORK_STATE_DISCONNECTING = 2; const long NETWORK_STATE_DISCONNECTED = 3; + const long NETWORK_STATE_ENABLED = 4; + const long NETWORK_STATE_DISABLED = 5; /** * Current network state, one of the NETWORK_STATE_* constants. @@ -34,6 +36,7 @@ interface nsINetworkInterface : nsISupports const long NETWORK_TYPE_WIFI_P2P = 4; const long NETWORK_TYPE_MOBILE_IMS = 5; const long NETWORK_TYPE_MOBILE_DUN = 6; + const long NETWORK_TYPE_ETHERNET = 7; /** * Network type. One of the NETWORK_TYPE_* constants. @@ -97,7 +100,7 @@ interface nsINetworkInterface : nsISupports /** * Manage network interfaces. */ -[scriptable, uuid(3ea50550-4b3c-11e3-8f96-0800200c9a66)] +[scriptable, uuid(1ba9346b-53b5-4660-9dc6-58f0b258d0a6)] interface nsINetworkManager : nsISupports { /** @@ -135,6 +138,15 @@ interface nsINetworkManager : nsISupports */ readonly attribute jsval networkInterfaces; + /** + * Priority list of network types. An array of + * nsINetworkInterface::NETWORK_TYPE_* constants. + * + * The piror position of the type indicates the higher priority. The priority + * is used to determine route when there are multiple connected networks. + */ + attribute jsval networkTypePriorityList; + /** * The preferred network type. One of the * nsINetworkInterface::NETWORK_TYPE_* constants. diff --git a/dom/system/gonk/nsINetworkService.idl b/dom/system/gonk/nsINetworkService.idl index eb6cdbf07aa8..b1680851dc70 100644 --- a/dom/system/gonk/nsINetworkService.idl +++ b/dom/system/gonk/nsINetworkService.idl @@ -101,10 +101,65 @@ interface nsIUpdateUpStreamCallback : nsISupports void updateUpStreamResult(in boolean success, in DOMString externalIfname); }; +[scriptable, function, uuid(4a9166f3-7e4f-4d10-bb5c-b49ee21d6184)] +interface nsIRunDhcpCallback : nsISupports +{ + /** + * Callback function used to report the result of dhcp request. + * + * @param success + * Boolean to indicate the operation is successful or not. + */ + void runDhcpResult(in boolean success, in jsval result); +}; + +[scriptable, function, uuid(88e3ee22-f1b3-4fa0-8a5d-793fb827c42c)] +interface nsIGetInterfacesCallback : nsISupports +{ + /** + * Callback function used to return the list of existing network interfaces. + * + * @param success + * Boolean to indicate the operation is successful or not. + * @param interfaceList + * An array of interface name. + */ + void getInterfacesResult(in boolean success, in jsval interfaceList); +}; + +[scriptable, function, uuid(064e02a3-d2c0-42c5-a293-1efa84056100)] +interface nsIGetInterfaceConfigCallback : nsISupports +{ + /** + * Callback function used to return the network config of a given interface. + * + * @param success + * Boolean to indicate the operation is successful or not. + * @param result + * .ip: Ip address. + * .prefix: mask length. + * .link: network link properties. + * .mac: mac address. + */ + void getInterfaceConfigResult(in boolean success, in jsval result); +}; + +[scriptable, function, uuid(b370f360-6ba8-4517-a4f9-31e8f004ee91)] +interface nsISetInterfaceConfigCallback : nsISupports +{ + /** + * Callback function used to set network cofig for a specified interface. + * + * @param success + * Boolean to indicate the operation is successful or not. + */ + void setInterfaceConfigResult(in boolean success); +}; + /** * Provide network services. */ -[scriptable, uuid(f96461fa-e844-45d2-a6c3-8cd23ab0916b)] +[scriptable, uuid(48c13741-aec9-4a86-8962-432011708261)] interface nsINetworkService : nsISupports { /** @@ -345,4 +400,53 @@ interface nsINetworkService : nsISupports void updateUpStream(in jsval previous, in jsval current, in nsIUpdateUpStreamCallback callback); + + /** + * Run Dhcp request. + * + * @param ifname + * Target interface. + * @param callback + * Callback function to report the result. + */ + void runDhcp(in DOMString ifname, in nsIRunDhcpCallback callback); + + /** + * Stop Dhcp daemon. + * + * @param ifname + * Target interface. + */ + void stopDhcp(in DOMString ifname); + + /* + * Obtain interfaces list. + * + * @param callback + * Callback function to return the result. + */ + void getInterfaces(in nsIGetInterfacesCallback callback); + + /** + * Set config for a network interface. + * + * @param config + * .ifname: Target interface. + * .ip: Ip address. + * .prefix: mask length. + * .link: network link properties. + * @param callback + * Callback function to report the result. + */ + void setInterfaceConfig(in jsval config, in nsISetInterfaceConfigCallback callback); + + /** + * Get config of a network interface. + * + * @param ifname + * Target interface. + * @param callback + * Callback function to report the result. + */ + void getInterfaceConfig(in DOMString ifname, in nsIGetInterfaceConfigCallback callback); }; diff --git a/dom/voicemail/Voicemail.cpp b/dom/voicemail/Voicemail.cpp index 7ac893929b8a..5976cce2b661 100644 --- a/dom/voicemail/Voicemail.cpp +++ b/dom/voicemail/Voicemail.cpp @@ -20,7 +20,7 @@ const char* kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces"; using namespace mozilla::dom; -class Voicemail::Listener : public nsIVoicemailListener +class Voicemail::Listener MOZ_FINAL : public nsIVoicemailListener { Voicemail* mVoicemail; diff --git a/dom/webidl/NetworkOptions.webidl b/dom/webidl/NetworkOptions.webidl index 88b149eaf99e..47796cdd8307 100644 --- a/dom/webidl/NetworkOptions.webidl +++ b/dom/webidl/NetworkOptions.webidl @@ -3,7 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ /** -* This dictionnary holds the parameters sent to the network worker. +* This dictionary holds the parameters sent to the network worker. */ dictionary NetworkCommandOptions { @@ -55,6 +55,7 @@ dictionary NetworkCommandOptions DOMString preExternalIfname; // for "updateUpStream". DOMString curInternalIfname; // for "updateUpStream". DOMString curExternalIfname; // for "updateUpStream". + boolean isBlocking; // for "runDhcp". }; /** @@ -81,4 +82,18 @@ dictionary NetworkResultOptions boolean success = false; // for "setDhcpServer". DOMString curExternalIfname = ""; // for "updateUpStream". DOMString curInternalIfname = ""; // for "updateUpStream". + + DOMString ipAddr = ""; // for "runDhcp", "getInterfaceConfig". + DOMString gateway = ""; // for "runDhcp". + DOMString dns1 = ""; // for "runDhcp". + DOMString dns2 = ""; // for "runDhcp". + DOMString server = ""; // for "runDhcp". + short lease = 0; // for "runDhcp". + DOMString vendorInfo = ""; // for "runDhcp". + short maskLength = 0; // for "runDhcp". + + DOMString flag = "down"; // for "getInterfaceConfig". + DOMString macAddr = ""; // for "getInterfaceConfig". + + sequence interfaceList; // for "getInterfaceList". }; diff --git a/testing/marionette/client/marionette/tests/unit-tests.ini b/testing/marionette/client/marionette/tests/unit-tests.ini index b8f222b818ca..b5acd0902b2e 100644 --- a/testing/marionette/client/marionette/tests/unit-tests.ini +++ b/testing/marionette/client/marionette/tests/unit-tests.ini @@ -28,6 +28,7 @@ skip = false [include:../../../../../dom/events/test/marionette/manifest.ini] [include:../../../../../dom/wifi/test/marionette/manifest.ini] [include:../../../../../dom/cellbroadcast/tests/marionette/manifest.ini] +[include:../../../../../dom/network/tests/marionette/manifest.ini] ; layout tests [include:../../../../../layout/base/tests/marionette/manifest.ini] diff --git a/toolkit/devtools/server/actors/webapps.js b/toolkit/devtools/server/actors/webapps.js index 8c96990ba2e2..c4221cda95ac 100644 --- a/toolkit/devtools/server/actors/webapps.js +++ b/toolkit/devtools/server/actors/webapps.js @@ -262,7 +262,7 @@ WebappsActor.prototype = { reg.broadcastMessage("Webapps:UpdateState", { app: aApp, manifest: manifest, - manifestURL: aApp.manifestURL + id: aApp.id }); reg.broadcastMessage("Webapps:FireEvent", { eventType: ["downloadsuccess", "downloadapplied"],