Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

Commit

Permalink
CB-9287 Not enough Icons and Splashscreens for Windows 8.1 and Window…
Browse files Browse the repository at this point in the history
…s Phone 8.1

Added JPG support
Introduced AppxManifest access in cordova.js
cordova.js - refine splashscreen extension based on config.xml
Added a warning when non-MRT splash screens have mixed extensions
Added a warning for size limit exceeding (applies to win10 project only)
Added a warning for images with unsupported format
Updated the tests
  • Loading branch information
daserge committed Dec 1, 2016
1 parent 09d582c commit 973fe6d
Show file tree
Hide file tree
Showing 12 changed files with 578 additions and 99 deletions.
81 changes: 61 additions & 20 deletions cordova-js-src/confighelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,47 @@
*
*/

// config.xml wrapper (non-node ConfigParser analogue)
var config;
function Config(xhr) {
function loadPreferences(xhr) {
var parser = new DOMParser();
var doc = parser.parseFromString(xhr.responseText, "application/xml");

var preferences = doc.getElementsByTagName("preference");
return Array.prototype.slice.call(preferences);
// config.xml and AppxManifest.xml wrapper (non-node ConfigParser analogue)
var configCache = {};
var utils = require("cordova/utils");

var isPhone = (cordova.platformId == 'windows') && WinJS.Utilities.isPhone;
var isWin10UWP = navigator.appVersion.indexOf('MSAppHost/3.0') !== -1;
var splashScreenTagName = isWin10UWP ? "SplashScreen" : (isPhone ? "m3:SplashScreen" : "m2:SplashScreen");

function XmlFile(text) {
this.text = text;
}

XmlFile.prototype.loadTags = function (tagName) {
var parser;
if (!this.doc) {
parser = new DOMParser();
this.doc = parser.parseFromString(this.text, "application/xml");
}

this.xhr = xhr;
this.preferences = loadPreferences(this.xhr);
var tags = this.doc.getElementsByTagName(tagName);
return Array.prototype.slice.call(tags);
}

function readConfig(success, error) {
function Config(text) {
XmlFile.apply(this, arguments);
this.preferences = this.loadTags("preference");
}

function Manifest(text) {
XmlFile.apply(this, arguments);
this.splashScreen = this.loadTags(splashScreenTagName)[0];
}

utils.extend(Config, XmlFile);
utils.extend(Manifest, XmlFile);

function requestFile(filePath, success, error) {
var xhr;

if (typeof config != 'undefined') {
success(config);
if (typeof configCache[filePath] != 'undefined') {
success(configCache[filePath]);
}

function fail(msg) {
Expand All @@ -52,11 +73,11 @@ function readConfig(success, error) {
var xhrStatusChangeHandler = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 304 || xhr.status == 0 /* file:// */) {
config = new Config(xhr);
success(config);
configCache[filePath] = xhr.responseText;
success(xhr.responseText);
}
else {
fail('[Windows][cordova.js][xhrStatusChangeHandler] Could not XHR config.xml: ' + xhr.statusText);
fail('[Windows][cordova.js][xhrStatusChangeHandler] Could not XHR ' + filePath + ': ' + xhr.statusText);
}
}
};
Expand All @@ -65,18 +86,30 @@ function readConfig(success, error) {
xhr.addEventListener("load", xhrStatusChangeHandler);

try {
xhr.open("get", "/config.xml", true);
xhr.open("get", filePath, true);
xhr.send();
} catch (e) {
fail('[Windows][cordova.js][readConfig] Could not XHR config.xml: ' + JSON.stringify(e));
fail('[Windows][cordova.js][xhrFile] Could not XHR ' + filePath + ': ' + JSON.stringify(e));
}
}

function readConfig(success, error) {
requestFile("/config.xml", function (contents) {
success(new Config(contents));
}, error);
}

function readManifest(success, error) {
requestFile("/AppxManifest.xml", function (contents) {
success(new Manifest(contents));
}, error);
}

/**
* Reads a preference value from config.xml.
* Returns preference value or undefined if it does not exist.
* @param {String} preferenceName Preference name to read */
Config.prototype.getPreferenceValue = function getPreferenceValue(preferenceName) {
Config.prototype.getPreferenceValue = function (preferenceName) {
var preferenceItem = this.preferences && this.preferences.filter(function (item) {
return item.attributes['name'].value === preferenceName;
});
Expand All @@ -86,4 +119,12 @@ Config.prototype.getPreferenceValue = function getPreferenceValue(preferenceName
}
}

/**
* Reads SplashScreen image path
*/
Manifest.prototype.getSplashScreenImagePath = function () {
return this.splashScreen.attributes['Image'].value;
}

exports.readConfig = readConfig;
exports.readManifest = readManifest;
10 changes: 8 additions & 2 deletions cordova-js-src/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,14 @@ module.exports = {
return;
}

e.setPromise(makePromise(configHelper.readConfig).then(function (config) {
splashscreen.firstShow(config, e);
var manifest;

e.setPromise(makePromise(configHelper.readManifest).then(function (manifestTmp) {
manifest = manifestTmp;
return makePromise(configHelper.readConfig);
})
.then(function (config) {
splashscreen.firstShow(config, manifest, e);
}).then(function () {
// Avoids splashimage flicker on Windows Phone 8.1/10
return WinJS.Promise.timeout();
Expand Down
13 changes: 8 additions & 5 deletions cordova-js-src/splashscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,11 @@ function readBoolFromCfg(preferenceName, defaultValue, cfg) {
}
}

function readPreferencesFromCfg(cfg) {
function readPreferencesFromCfg(cfg, manifest) {
try {
// Update splashscreen image path to match application manifest
splashImageSrc = schema + ':///' + manifest.getSplashScreenImagePath().replace(/\\/g, '/');

bgColor = cfg.getPreferenceValue('SplashScreenBackgroundColor') || bgColor;
bgColor = bgColor.replace('0x', '#').replace('0X', '#');
if (bgColor.length > 7) {
Expand Down Expand Up @@ -102,8 +105,8 @@ function centerY() {
}
}

function init(config) {
readPreferencesFromCfg(config);
function init(config, manifest) {
readPreferencesFromCfg(config, manifest);

var splashscreenStyles = document.createElement("link");
splashscreenStyles.rel = 'stylesheet';
Expand Down Expand Up @@ -321,8 +324,8 @@ function onResize() {
//// </Events>

module.exports = {
firstShow: function (config, activatedEventArgs) {
init(config);
firstShow: function (config, manifest, activatedEventArgs) {
init(config, manifest);
activated(activatedEventArgs);

if (!isVisible() && (splashScreenDelay > 0 || !autoHideSplashScreen)) {
Expand Down
20 changes: 20 additions & 0 deletions spec/unit/AppxManifest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,26 @@ describe('AppxManifest', function () {
visualElementsWindows10.setForegroundText(foregroundTextLight);
expect(visualElementsWindows10.getForegroundText()).toEqual(undefined);
});

it('getSplashScreenExtension/setSplashScreenExtension', function () {
var visualElementsWindows = AppxManifest.get(WINDOWS_MANIFEST).getVisualElements();
var visualElementsWindows10 = AppxManifest.get(WINDOWS_10_MANIFEST).getVisualElements();
var visualElementsWindowsPhone = AppxManifest.get(WINDOWS_PHONE_MANIFEST).getVisualElements();
var jpgExtension = '.jpg';

// PNG is default extension
expect(visualElementsWindows.getSplashScreenExtension()).toEqual('.png');
expect(visualElementsWindows10.getSplashScreenExtension()).toEqual('.png');
expect(visualElementsWindowsPhone.getSplashScreenExtension()).toEqual('.png');

// Set to jpg
visualElementsWindows.setSplashScreenExtension(jpgExtension);
expect(visualElementsWindows.getSplashScreenExtension()).toEqual(jpgExtension);
visualElementsWindows10.setSplashScreenExtension(jpgExtension);
expect(visualElementsWindows10.getSplashScreenExtension()).toEqual(jpgExtension);
visualElementsWindowsPhone.setSplashScreenExtension(jpgExtension);
expect(visualElementsWindowsPhone.getSplashScreenExtension()).toEqual(jpgExtension);
});
});
});

175 changes: 172 additions & 3 deletions spec/unit/Prepare.Win10.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ var rewire = require('rewire'),
applyStartPage = prepare.__get__('applyStartPage');

var Win10ManifestPath = 'template/package.windows10.appxmanifest',
Win81ManifestPath = 'template/package.windows.appxmanifest';
Win81ManifestPath = 'template/package.windows.appxmanifest',
WP81ManifestPath = 'template/package.phone.appxmanifest';

var Win10ManifestName = path.basename(Win10ManifestPath),
Win81ManifestName = path.basename(Win81ManifestPath),
WP81ManifestName = path.basename(WP81ManifestPath);

/***
* Unit tests for validating default ms-appx-web:// URI scheme in Win10
Expand Down Expand Up @@ -461,10 +466,10 @@ describe('copyIcons method', function () {

var PROJECT = '/some/path';

function createMockConfig(images) {
function createMockConfig(images, splashScreens) {
var result = jasmine.createSpyObj('config', ['getIcons', 'getSplashScreens']);
result.getIcons.andReturn(images);
result.getSplashScreens.andReturn([]);
result.getSplashScreens.andReturn(splashScreens || []);

return result;
}
Expand Down Expand Up @@ -541,4 +546,168 @@ describe('copyIcons method', function () {
expect(FileUpdater.updatePaths).toHaveBeenCalledWith(expectedPathMap, { rootDir: PROJECT }, logFileOp);
});
});

it('should ignore splashScreens for Windows 10 project with size >200K and emit a warning', function () {
var size300K = 300 * 1024;
var warnSpy = jasmine.createSpy('warn');
events.on('warn', warnSpy);

var splashScreens = [
{src: 'res/Windows/splashscreen.png', target: 'SplashScreen' }, // targetProject: 10
{src: 'res/Windows/splashscreen.scale-180.png', width: '1116', height: '540' }, // targetProject: 8.1
{src: 'res/Windows/splashscreen.scale-200.png', width: '1240', height: '600' }, // targetProject: 10
{src: 'res/Windows/splashscreen.scale-400.png', width: '2480', height: '1200' }, // targetProject: 10
{src: 'res/Windows/splashscreenphone.scale-240.png', width: '1152', height: '1920' }, // targetProject: WP 8.1
{src: 'res/Windows/splashscreenphone.png', target: 'SplashScreenPhone' }, // targetProject: WP 8.1
];

var splashScreensFiles = splashScreens.map(function(splash) {
return path.basename(splash.src);
});
spyOn(fs, 'readdirSync').andReturn(splashScreensFiles);

spyOn(fs, 'statSync').andReturn({
size: size300K
});

var project = { projectConfig: createMockConfig([], splashScreens), root: PROJECT };
var locations = { root: PROJECT };

copyImages(project, locations);

var expectedPathMap = {};
expectedPathMap['images' + path.sep + 'SplashScreen.scale-180.png'] = 'res/Windows/splashscreen.scale-180.png';
expectedPathMap['images' + path.sep + 'SplashScreenPhone.scale-240.png'] = path.join('res', 'Windows', 'splashscreenphone.scale-240.png');
expectedPathMap['images' + path.sep + 'SplashScreenPhone.scale-100.png'] = path.join('res', 'Windows', 'splashscreenphone.png');
expect(FileUpdater.updatePaths).toHaveBeenCalledWith(expectedPathMap, { rootDir: PROJECT }, logFileOp);
expect(warnSpy.calls[0].args[0]).toMatch('file size exceeds the limit');
});

it('should ignore splashScreens with unsupported extensions and emit a warning', function () {
var warnSpy = jasmine.createSpy('warn');
events.on('warn', warnSpy);

var splashScreens = [
{src: 'res/Windows/splashscreen.gif', target: 'SplashScreen' }, // targetProject: 10
{src: 'res/Windows/splashscreen.scale-180.bmp', width: '1116', height: '540' }, // targetProject: 8.1
{src: 'res/Windows/splashscreenphone.tga', target: 'SplashScreenPhone' }, // targetProject: WP 8.1
];

var splashScreensFiles = splashScreens.map(function(splash) {
return path.basename(splash.src);
});
spyOn(fs, 'readdirSync').andReturn(splashScreensFiles);

spyOn(fs, 'statSync').andReturn({
size: 0
});

var project = { projectConfig: createMockConfig([], splashScreens), root: PROJECT };
var locations = { root: PROJECT };

copyImages(project, locations);

var extensionNotSupportedMsg = 'extension is not supported';
var expectedPathMap = {};
expect(FileUpdater.updatePaths).toHaveBeenCalledWith(expectedPathMap, { rootDir: PROJECT }, logFileOp);
expect(warnSpy.calls[0].args[0]).toMatch(extensionNotSupportedMsg);
expect(warnSpy.calls[1].args[0]).toMatch(extensionNotSupportedMsg);
expect(warnSpy.calls[2].args[0]).toMatch(extensionNotSupportedMsg);
});

it('should warn about mixed splashscreen extensions used for non-MRT syntax', function () {
var updateSplashScreenImageExtensions = prepare.__get__('updateSplashScreenImageExtensions');
spyOn(fs, 'writeFileSync');
spyOn(AppxManifest, 'get').andReturn({
getVisualElements: function() {
return {
getSplashScreenExtension: function() {
return '.png';
},
setSplashScreenExtension: function() {}
};
},
write: function() {}
});
var warnSpy = jasmine.createSpy('warn');
events.on('warn', warnSpy);

var splashScreens = [
{src: 'res/Windows/splashscreen.png', width: '620', height: '300' }, // targetProject: 10
{src: 'res/Windows/splashscreen.scale-180.jpg', width: '1116', height: '540' }, // targetProject: 8.1
{src: 'res/Windows/splashscreen.scale-200.png', width: '1240', height: '600' }, // targetProject: 10
{src: 'res/Windows/splashscreen.scale-400.jpg', width: '2480', height: '1200' }, // targetProject: 10
{src: 'res/Windows/splashscreenphone.scale-240.png', width: '1152', height: '1920' }, // targetProject: WP 8.1
{src: 'res/Windows/splashscreenphone.jpg', width: '480', height: '800' }, // targetProject: WP 8.1
];

var splashScreensFiles = splashScreens.map(function(splash) {
return path.basename(splash.src);
});
spyOn(fs, 'readdirSync').andReturn(splashScreensFiles);

spyOn(fs, 'statSync').andReturn({
size: 0
});

var project = { projectConfig: createMockConfig([], splashScreens), root: PROJECT };
var locations = { root: PROJECT };

updateSplashScreenImageExtensions(project, locations);

var mixedExtensionsMsg = 'splash screens have mixed file extensions';
expect(warnSpy.calls[0].args[0]).toMatch(mixedExtensionsMsg);
expect(warnSpy.calls[1].args[0]).toMatch(mixedExtensionsMsg);
});

it('should update manifests with proper splashscreen image extension', function () {
// 1. Set manifest with SplashScreen.Image = "image.png" (this is default)
// 2. Set config.xml with splash src="image.jpg"
// 3. updateSplashScreenImageExtensions should call getSplashScreenExtension, setSplashScreenExtension('.jpg')

var updateSplashScreenImageExtensions = prepare.__get__('updateSplashScreenImageExtensions');
spyOn(fs, 'writeFileSync');

var win10Manifest = AppxManifest.get(Win10ManifestPath),
win81Manifest = AppxManifest.get(Win81ManifestPath),
wp81Manifest = AppxManifest.get(WP81ManifestPath);

spyOn(AppxManifest, 'get').andCallFake(function(manifestPath) {
if (manifestPath.indexOf(Win10ManifestName) !== -1) {
return win10Manifest;
}

if (manifestPath.indexOf(Win81ManifestName) !== -1) {
return win81Manifest;
}

if (manifestPath.indexOf(WP81ManifestName) !== -1) {
return wp81Manifest;
}
});

var splashScreens = [
{src: 'res/Windows/splashscreen.jpg', width: '620', height: '300' }, // targetProject: 10
{src: 'res/Windows/splashscreen.scale-180.jpg', width: '1116', height: '540' }, // targetProject: 8.1
{src: 'res/Windows/splashscreenphone.jpg', width: '480', height: '800' }, // targetProject: WP 8.1
];

var splashScreensFiles = splashScreens.map(function(splash) {
return path.basename(splash.src);
});
spyOn(fs, 'readdirSync').andReturn(splashScreensFiles);

spyOn(fs, 'statSync').andReturn({
size: 0
});

var project = { projectConfig: createMockConfig([], splashScreens), root: PROJECT };
var locations = { root: PROJECT };

updateSplashScreenImageExtensions(project, locations);

expect(win10Manifest.getVisualElements().getSplashScreenExtension()).toBe('.jpg');
expect(win81Manifest.getVisualElements().getSplashScreenExtension()).toBe('.jpg');
expect(wp81Manifest.getVisualElements().getSplashScreenExtension()).toBe('.jpg');
});
});
Loading

0 comments on commit 973fe6d

Please sign in to comment.