CB-11933: Add uap prefixes for capabilities at plugin install This closes #203
Project: http://git-wip-us.apache.org/repos/asf/cordova-windows/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-windows/commit/966acc85 Tree: http://git-wip-us.apache.org/repos/asf/cordova-windows/tree/966acc85 Diff: http://git-wip-us.apache.org/repos/asf/cordova-windows/diff/966acc85 Branch: refs/heads/4.4.x Commit: 966acc850bc60710bdcda935c8359b660e4d8aac Parents: db0a391 Author: Nikita Matrosov <[email protected]> Authored: Wed Oct 19 14:23:52 2016 +0300 Committer: daserge <[email protected]> Committed: Thu Oct 20 20:54:56 2016 +0300 ---------------------------------------------------------------------- spec/unit/ConfigChanges.spec.js | 81 +++++++----- .../windows/package.windows10.appxmanifest | 73 +++++++++++ template/cordova/Api.js | 5 +- template/cordova/lib/AppxManifest.js | 7 ++ template/cordova/lib/ConfigChanges.js | 125 +++++++++++++++---- 5 files changed, 237 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/966acc85/spec/unit/ConfigChanges.spec.js ---------------------------------------------------------------------- diff --git a/spec/unit/ConfigChanges.spec.js b/spec/unit/ConfigChanges.spec.js index 44ad4cd..df346dd 100644 --- a/spec/unit/ConfigChanges.spec.js +++ b/spec/unit/ConfigChanges.spec.js @@ -61,72 +61,97 @@ describe('PlatformMunger', function () { }); it('should additionally call parent\'s method with another munge if removing changes from windows 10 appxmanifest', function () { - munger.apply_file_munge('package.windows10.appxmanifest', munge, /*remove=*/true); + munger.apply_file_munge(WINDOWS10_MANIFEST, munge, /*remove=*/true); expect(BaseMunger.prototype.apply_file_munge).toHaveBeenCalledWith(WINDOWS10_MANIFEST, munge, true); - expect(BaseMunger.prototype.apply_file_munge).toHaveBeenCalledWith(WINDOWS10_MANIFEST, jasmine.any(Object), true); }); it('should remove uap: capabilities added by windows prepare step', function () { // Generate a munge that contain non-prefixed capabilities changes - var baseMunge = { parents: { WINDOWS10_MANIFEST: [ + var baseMunge = { parents: { '/Package/Capabilities': [ // Emulate capability that was initially added with uap prefix { before: undefined, count: 1, xml: '<uap:Capability Name=\"privateNetworkClientServer\">'}, { before: undefined, count: 1, xml: '<Capability Name=\"enterpriseAuthentication\">'} ]}}; - var capabilitiesMunge = { parents: { WINDOWS10_MANIFEST: [ + var capabilitiesMunge = { parents: { '/Package/Capabilities': [ { before: undefined, count: 1, xml: '<uap:Capability Name=\"enterpriseAuthentication\">'} ]}}; - - munger.apply_file_munge('package.windows10.appxmanifest', baseMunge, /*remove=*/true); + munger.apply_file_munge(WINDOWS10_MANIFEST, baseMunge, /*remove=*/true); expect(BaseMunger.prototype.apply_file_munge).toHaveBeenCalledWith(WINDOWS10_MANIFEST, capabilitiesMunge, true); }); }); }); describe('Capabilities within package.windows.appxmanifest', function() { - var testDir; + + var testDir, windowsPlatform, windowsManifest, windowsManifest10, dummyPluginInfo, api; beforeEach(function() { testDir = path.join(__dirname, 'testDir'); shell.mkdir('-p', testDir); shell.cp('-rf', windowsProject + '/*', testDir); + windowsPlatform = path.join(testDir, 'platforms/windows'); + windowsManifest = path.join(windowsPlatform, WINDOWS_MANIFEST); + windowsManifest10 = path.join(windowsPlatform, WINDOWS10_MANIFEST); + dummyPluginInfo = new PluginInfo(dummyPlugin); + api = new Api(); + api.root = windowsPlatform; + api.locations.root = windowsPlatform; + api.locations.www = path.join(windowsPlatform, 'www'); }); afterEach(function() { shell.rm('-rf', testDir); }); - it('should be removed using overriden PlatformMunger', function(done) { - var windowsPlatform = path.join(testDir, 'platforms/windows'); - var windowsManifest = path.join(windowsPlatform, WINDOWS_MANIFEST); - var api = new Api(); - api.root = windowsPlatform; - api.locations.root = windowsPlatform; - api.locations.www = path.join(windowsPlatform, 'www'); - var dummyPluginInfo = new PluginInfo(dummyPlugin); + function getPluginCapabilities(pluginInfo) { + return pluginInfo.getConfigFiles()[0].xmls; + } - var fail = jasmine.createSpy('fail') - .andCallFake(function (err) { - console.error(err); - }); + function getManifestCapabilities(manifest) { + var appxmanifest = AppxManifest.get(manifest, true); + return appxmanifest.getCapabilities(); + } - function getPluginCapabilities() { - return dummyPluginInfo.getConfigFiles()[0].xmls; - } + var fail = jasmine.createSpy('fail') + .andCallFake(function (err) { + console.error(err); + }); + + it('should be removed using overriden PlatformMunger', function(done) { + api.addPlugin(dummyPluginInfo) + .then(function() { + // There is the one default capability in manifest with 'internetClient' name + expect(getManifestCapabilities(windowsManifest).length).toBe(getPluginCapabilities(dummyPluginInfo).length + 1); + api.removePlugin(dummyPluginInfo); + }) + .then(function() { + expect(getManifestCapabilities(windowsManifest).length).toBe(1); + }) + .catch(fail) + .finally(function() { + expect(fail).not.toHaveBeenCalled(); + done(); + }); + }); - function getManifestCapabilities() { - var appxmanifest = AppxManifest.get(windowsManifest, true); - return appxmanifest.getCapabilities(); - } + it('should be added with uap prefixes when install plugin', function(done) { api.addPlugin(dummyPluginInfo) .then(function() { // There is the one default capability in manifest with 'internetClient' name - expect(getManifestCapabilities().length).toBe(getPluginCapabilities().length + 1); + var manifestCapabilities = getManifestCapabilities(windowsManifest10); + expect(manifestCapabilities.length).toBe(getPluginCapabilities(dummyPluginInfo).length + 1); + + // Count 'uap' prefixed capabilities + var uapPrefixedCapsCount = manifestCapabilities.filter(function(capability) { + return capability.type === 'uap:Capability'; + }).length; + + expect(uapPrefixedCapsCount).toBe(2); api.removePlugin(dummyPluginInfo); }) .then(function() { - expect(getManifestCapabilities().length).toBe(1); + expect(getManifestCapabilities(windowsManifest10).length).toBe(1); }) .catch(fail) .finally(function() { http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/966acc85/spec/unit/fixtures/testProj/platforms/windows/package.windows10.appxmanifest ---------------------------------------------------------------------- diff --git a/spec/unit/fixtures/testProj/platforms/windows/package.windows10.appxmanifest b/spec/unit/fixtures/testProj/platforms/windows/package.windows10.appxmanifest new file mode 100644 index 0000000..59e435a --- /dev/null +++ b/spec/unit/fixtures/testProj/platforms/windows/package.windows10.appxmanifest @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<Package + xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" + xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" + xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" + IgnorableNamespaces="uap mp"> + + <Identity + Name="$guid1$" + Version="1.0.0.0" + Publisher="CN=$username$" /> + + <mp:PhoneIdentity PhoneProductId="$guid1$" PhonePublisherId="00000000-0000-0000-0000-000000000000"/> + + <Properties> + <DisplayName>$projectname$</DisplayName> + <PublisherDisplayName>$username$</PublisherDisplayName> + <Logo>images\StoreLogo.png</Logo> + </Properties> + + <Dependencies> + <TargetDeviceFamily Name="Windows.Universal" MinVersion="0.0.0.0" MaxVersionTested="10.0.0.0" /> + </Dependencies> + + <Resources> + <Resource Language="x-generate" /> + </Resources> + + <Applications> + <Application + Id="App" + StartPage="www/index.html"> + + <uap:VisualElements + DisplayName="$projectname$" + Description="CordovaApp" + BackgroundColor="#464646" + Square150x150Logo="images\Square150x150Logo.png" + Square44x44Logo="images\Square44x44Logo.png"> + + <uap:SplashScreen Image="images\splashscreen.png" /> + <uap:DefaultTile ShortName="$projectname$" + Square310x310Logo="images\Square310x310Logo.png" + Square71x71Logo="images\Square71x71Logo.png" + Wide310x150Logo="images\Wide310x150Logo.png" /> + + </uap:VisualElements> + </Application> + </Applications> + + <Capabilities> + <Capability Name="internetClient" /> + </Capabilities> + +</Package> http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/966acc85/template/cordova/Api.js ---------------------------------------------------------------------- diff --git a/template/cordova/Api.js b/template/cordova/Api.js index 50a0778..7637582 100644 --- a/template/cordova/Api.js +++ b/template/cordova/Api.js @@ -208,7 +208,10 @@ Api.prototype.addPlugin = function (plugin, installOptions) { installOptions.variables.PACKAGE_NAME = jsProject.getPackageName(); } - return PluginManager.get(this.platform, this.locations, jsProject) + var platformJson = PlatformJson.load(this.root, this.platform); + var pluginManager = PluginManager.get(this.platform, this.locations, jsProject); + pluginManager.munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider()); + return pluginManager .addPlugin(plugin, installOptions) .then(function () { // CB-11657 Add BOM to www files here because files added by plugin http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/966acc85/template/cordova/lib/AppxManifest.js ---------------------------------------------------------------------- diff --git a/template/cordova/lib/AppxManifest.js b/template/cordova/lib/AppxManifest.js index db59cf1..80cf113 100644 --- a/template/cordova/lib/AppxManifest.js +++ b/template/cordova/lib/AppxManifest.js @@ -72,6 +72,13 @@ function AppxManifest(path, prefix) { this.hasPhoneIdentity = this.prefix === 'uap:' || this.prefix === 'm3:'; } +// Static read-only property to get capabilities which need to be prefixed with uap +Object.defineProperty(AppxManifest, 'CapsNeedUapPrefix', { + writable: false, + configurable: false, + value: CAPS_NEEDING_UAPNS +}); + /** * @static * @constructs AppxManifest|Win10AppxManifest http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/966acc85/template/cordova/lib/ConfigChanges.js ---------------------------------------------------------------------- diff --git a/template/cordova/lib/ConfigChanges.js b/template/cordova/lib/ConfigChanges.js index 7e15606..64eba4d 100644 --- a/template/cordova/lib/ConfigChanges.js +++ b/template/cordova/lib/ConfigChanges.js @@ -15,7 +15,12 @@ */ var util = require('util'); +var path = require('path'); var CommonMunger = require('cordova-common').ConfigChanges.PlatformMunger; +var CapsNeedUapPrefix = require(path.join(__dirname, 'AppxManifest')).CapsNeedUapPrefix; + +var CAPS_SELECTOR = '/Package/Capabilities'; +var WINDOWS10_MANIFEST = 'package.windows10.appxmanifest'; function PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider) { CommonMunger.apply(this, arguments); @@ -34,36 +39,111 @@ util.inherits(PlatformMunger, CommonMunger); * need to be removed or added to the file */ PlatformMunger.prototype.apply_file_munge = function (file, munge, remove) { - // Call parent class' method - PlatformMunger.super_.prototype.apply_file_munge.call(this, file, munge, remove); - - // CB-11066 If this is a windows10 manifest and we're removing the changes - // then we also need to check if there are <Capability> elements were previously - // added and schedule removal of corresponding <uap:Capability> elements - if (remove && file === 'package.windows10.appxmanifest') { - var uapCapabilitiesMunge = generateUapCapabilities(munge); - // We do not check whether generated munge is empty or not before calling - // 'apply_file_munge' since applying empty one is just a no-op - PlatformMunger.super_.prototype.apply_file_munge.call(this, file, uapCapabilitiesMunge, remove); + + // Create a copy to avoid modification of original munge + var mungeCopy = cloneObject(munge); + var capabilities = mungeCopy.parents[CAPS_SELECTOR]; + + if (capabilities) { + // Add 'uap' prefixes for windows 10 manifest + if (file === WINDOWS10_MANIFEST) { + capabilities = generateUapCapabilities(capabilities); + } + + // Remove duplicates and sort capabilities when installing plugin + if (!remove) { + capabilities = getUniqueCapabilities(capabilities).sort(compareCapabilities); + } + + // Put back capabilities into munge's copy + mungeCopy.parents[CAPS_SELECTOR] = capabilities; } + + PlatformMunger.super_.prototype.apply_file_munge.call(this, file, mungeCopy, remove); }; +// Recursive function to clone an object +function cloneObject(obj) { + if (obj === null || typeof obj !== 'object') { + return obj; + } + + var copy = obj.constructor(); + Object.keys(obj).forEach(function(key) { + copy[key] = cloneObject(obj[key]); + }); + + return copy; +} + +/** + * Retrieve capabality name from xml field + * @param {Object} capability with xml field like <Capability Name="CapabilityName"> + * @return {String} name of capability + */ +function getCapabilityName(capability) { + var reg = /Name="(\w+)"/i; + return capability.xml.match(reg)[1]; +} + +/** + * Remove capabilities with same names + * @param {Object} an array of capabilities + * @return {Object} an unique array of capabilities + */ +function getUniqueCapabilities(capabilities) { + return capabilities.reduce(function(uniqueCaps, currCap) { + + var isRepeated = uniqueCaps.some(function(cap) { + return getCapabilityName(cap) === getCapabilityName(currCap); + }); + + return isRepeated ? uniqueCaps : uniqueCaps.concat([currCap]); + }, []); +} + +/** + * Comparator function to pass to Array.sort + * @param {Object} firstCap first capability + * @param {Object} secondCap second capability + * @return {Number} either -1, 0 or 1 + */ +function compareCapabilities(firstCap, secondCap) { + var firstCapName = getCapabilityName(firstCap); + var secondCapName = getCapabilityName(secondCap); + + if (firstCapName < secondCapName) { + return -1; + } + + if (firstCapName > secondCapName) { + return 1; + } + + return 0; +} + + /** * Generates a new munge that contains <uap:Capability> elements created based on * corresponding <Capability> elements from base munge. If there are no such elements * found in base munge, the empty munge is returned (selectors might be present under * the 'parents' key, but they will contain no changes). * - * @param {Object} munge A munge that we need to check for <Capability> elements - * @return {Object} A munge with 'uap'-prefixed capabilities or empty one + * @param {Object} capabilities A list of capabilities + * @return {Object} A list with 'uap'-prefixed capabilities */ -function generateUapCapabilities(munge) { +function generateUapCapabilities(capabilities) { function hasCapabilityChange(change) { return /^\s*<Capability\s/.test(change.xml); } function createPrefixedCapabilityChange(change) { + if (CapsNeedUapPrefix.indexOf(getCapabilityName(change)) < 0) { + return change; + } + return { xml: change.xml.replace(/Capability/, 'uap:Capability'), count: change.count, @@ -71,17 +151,12 @@ function generateUapCapabilities(munge) { }; } - // Iterate through all selectors in munge - return Object.keys(munge.parents) - .reduce(function (result, selector) { - result.parents[selector] = munge.parents[selector] - // For every xml change check if it adds a <Capability> element ... - .filter(hasCapabilityChange) - // ... and create a duplicate with 'uap:' prefix - .map(createPrefixedCapabilityChange); - - return result; - }, { parents: {} }); + return capabilities + // For every xml change check if it adds a <Capability> element ... + .filter(hasCapabilityChange) + // ... and create a duplicate with 'uap:' prefix + .map(createPrefixedCapabilityChange); + } exports.PlatformMunger = PlatformMunger; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
