diff --git a/externs.js b/externs.js
new file mode 100644
index 0000000..8cdd475
--- /dev/null
+++ b/externs.js
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * @externs
+ */
+
+var Windows;
+
+Windows.UI;
+
+Windows.UI.ViewManagement;
+
+Windows.UI.ViewManagement.ApplicationView;
+
+/**
+ * @return {{titleBar: ApplicationViewTitleBar}}
+ */
+Windows.UI.ViewManagement.ApplicationView.getForCurrentView = function() {};
+
+/**
+ * @typedef {{
+ * r: number,
+ * g: number,
+ * b: number,
+ * a: number,
+ * }}
+ */
+var WindowsColor;
+
+/**
+ * @typedef {{
+ * foregroundColor: WindowsColor,
+ * backgroundColor: WindowsColor,
+ * buttonForegroundColor: WindowsColor,
+ * buttonBackgroundColor: WindowsColor,
+ * buttonHoverForegroundColor: WindowsColor,
+ * buttonHoverBackgroundColor: WindowsColor,
+ * inactiveForegroundColor: WindowsColor,
+ * inactiveBackgroundColor: WindowsColor,
+ * buttonInactiveForegroundColor: WindowsColor,
+ * buttonInactiveBackgroundColor: WindowsColor,
+ * buttonInactiveHoverForegroundColor: WindowsColor,
+ * buttonInactiveHoverBackgroundColor: WindowsColor,
+ * buttonPressedForegroundColor: WindowsColor,
+ * buttonPressedBackgroundColor: WindowsColor,
+ * }}
+ */
+var ApplicationViewTitleBar;
\ No newline at end of file
diff --git a/pwacompat.js b/pwacompat.js
index 9c89773..450a981 100644
--- a/pwacompat.js
+++ b/pwacompat.js
@@ -30,6 +30,7 @@
const isSafari = (navigator.vendor && navigator.vendor.indexOf('Apple') !== -1);
const isEdge = (navigator.userAgent && navigator.userAgent.indexOf('Edge') !== -1);
+ const isEdgePWA = (typeof Windows !== 'undefined');
function setup() {
const manifestEl = document.head.querySelector('link[rel="manifest"]');
@@ -83,6 +84,7 @@
const isCapable = capableDisplayModes.indexOf(manifest['display']) !== -1;
meta('mobile-web-app-capable', isCapable);
+ updateThemeColor(manifest['theme_color'] || 'black');
if (isEdge) {
meta('msapplication-starturl', manifest['start_url'] || '/');
@@ -94,15 +96,13 @@
const backgroundIsLight =
shouldUseLightForeground(manifest['background_color'] || defaultSplashColor);
- const themeIsLight = shouldUseLightForeground(manifest['theme_color'] || 'black');
const title = manifest['name'] || manifest['short_name'] || document.title;
// Add related iTunes app from manifest.
const itunes = findAppleId(manifest['related_applications']);
itunes && meta('apple-itunes-app', `app-id=${itunes}`);
- // nb. Safari 11.3+ gives a deprecation warning about this meta tag.
- meta('apple-mobile-web-app-status-bar-style', themeIsLight ? 'default' : 'black');
+ // General iOS meta tags.
meta('apple-mobile-web-app-capable', isCapable);
meta('apple-mobile-web-app-title', title);
@@ -211,11 +211,97 @@
return itunes;
}
+ /**
+ * @param {string} color
+ */
+ function updateThemeColor(color) {
+ if (!(isSafari || isEdgePWA)) {
+ return;
+ }
+
+ const themeIsLight = shouldUseLightForeground(color);
+ if (isSafari) {
+ // nb. Safari 11.3+ gives a deprecation warning about this meta tag.
+ meta('apple-mobile-web-app-status-bar-style', themeIsLight ? 'default' : 'black');
+ } else {
+ // Edge PWA
+ const t = getEdgeTitleBar();
+ if (t === null) {
+ console.debug('found Windows, could not fetch titleBar')
+ return;
+ }
+
+ const foreground = colorToWindowsRGBA(themeIsLight ? 'black' : 'white');
+ const background = colorToWindowsRGBA(color);
+
+ t.foregroundColor = foreground;
+ t.backgroundColor = background;
+
+ t.buttonForegroundColor = foreground;
+ t.buttonBackgroundColor = background;
+
+ t.inactiveForegroundColor = foreground;
+ t.inactiveBackgroundColor = background;
+
+ t.buttonInactiveForegroundColor = foreground;
+ t.buttonInactiveBackgroundColor = background;
+
+ // TODO(samthor): Left out for now to determine Windows defaults.
+ // t.buttonHoverForegroundColor = background; // inverted
+ // t.buttonHoverBackgroundColor = foreground;
+ //
+ // t.buttonPressedForegroundColor = background; // inverted
+ // t.buttonPressedBackgroundColor = foreground;
+ //
+ // t.buttonInactiveHoverForegroundColor = background; // inverted
+ // t.buttonInactiveHoverBackgroundColor = foreground;
+ }
+ }
+
+ /**
+ * @return {?ApplicationViewTitleBar}
+ */
+ function getEdgeTitleBar() {
+ try {
+ return Windows.UI.ViewManagement.ApplicationView.getForCurrentView().titleBar;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ /**
+ * The Windows titlebar APIs expect an object of {r, g, b, a}.
+ *
+ * @param {string} color
+ * @return {WindowsColor}
+ */
+ function colorToWindowsRGBA(color) {
+ const data = readColor(color);
+ return /** @type {WindowsColor} */ ({
+ 'r': data[0],
+ 'g': data[1],
+ 'b': data[2],
+ 'a': data[3],
+ });
+ }
+
+ /**
+ * @param {string} color
+ * @return {!Uint8ClampedArray}
+ */
+ function readColor(color) {
+ const c = contextForCanvas();
+ c.fillStyle = color;
+ c.fillRect(0, 0, 1, 1);
+ return c.getImageData(0, 0, 1, 1).data;
+ }
+
+ /**
+ * @param {string} color
+ * @return {boolean}
+ */
function shouldUseLightForeground(color) {
- const lightTestContext = contextForCanvas();
- lightTestContext.fillStyle = color;
- lightTestContext.fillRect(0, 0, 1, 1);
- const pixelData = lightTestContext.getImageData(0, 0, 1, 1).data;
+ const pixelData = readColor(color);
// From https://cs.chromium.org/chromium/src/chrome/android/java/src/org/chromium/chrome/browser/util/ColorUtils.java
const data = pixelData.map((v) => {
diff --git a/pwacompat.min.js b/pwacompat.min.js
index 4527a56..6ebf0b6 100644
--- a/pwacompat.min.js
+++ b/pwacompat.min.js
@@ -1,7 +1,9 @@
-(function(){function l(){var a=document.head.querySelector('link[rel="manifest"]'),b=a?a.href:"";Promise.resolve().then(function(){if(!b)throw'can\'t find \'';var a={};"use-credentials"===b.crossOrigin&&(a.credentials="include");return window.fetch(b,a)}).then(function(a){return a.json()}).then(function(a){return n(a,b)}).catch(function(a){return console.warn("pwacompat.js error",a)})}function p(a,b){a=document.createElement(a);for(var c in b)a.setAttribute(c,b[c]);
-document.head.appendChild(a);return a}function h(a,b){b&&(!0===b&&(b="yes"),p("meta",{name:a,content:b}))}function n(a,b){function c(e,c,b){var d=e.width,g=e.height,f=window.devicePixelRatio;e=q({width:d*f,height:g*f});e.scale(f,f);e.fillStyle=a.background_color||"#f8f9fa";e.fillRect(0,0,d,g);e.translate(d/2,(g-32)/2);e.font="24px HelveticaNeue-CondensedBold";e.fillStyle=l?"white":"black";d=e.measureText(r).width;b&&(g=b.width/f,f=b.height/f,128a?a/12.92:Math.pow((a+.055)/1.055,2.4)});return 3\'';var a={};"use-credentials"===b.crossOrigin&&(a.credentials="include");return window.fetch(b,a)}).then(function(a){return a.json()}).then(function(a){return z(a,b)}).catch(function(a){return console.warn("pwacompat.js error",a)})}function r(a,b){a=document.createElement(a);for(var c in b)a.setAttribute(c,b[c]);
+document.head.appendChild(a);return a}function g(a,b){b&&(!0===b&&(b="yes"),r("meta",{name:a,content:b}))}function z(a,b){function c(b,c,f){var h=b.width,d=b.height,e=window.devicePixelRatio;b=t({width:h*e,height:d*e});b.scale(e,e);b.fillStyle=a.background_color||"#f8f9fa";b.fillRect(0,0,h,d);b.translate(h/2,(d-32)/2);b.font="24px HelveticaNeue-CondensedBold";b.fillStyle=n?"white":"black";h=b.measureText(u).width;f&&(d=f.width/e,e=f.height/e,128a?a/12.92:Math.pow((a+.055)/1.055,2.4)});return 3