如何将 Google 地图信息 windows 的默认样式与内容安全策略一起使用?
How to use default style of Google Maps info windows with content security policy?
我在一个网站上使用 Google 地图 Javascript API,其内容安全策略不允许内联 CSS。
在实施 CSP 之前,信息 windows 的样式设计得很好。实施 CSP 后,信息 windows 根本没有样式化,因为 Google 地图 Javascript API 使用内联 CSS.
我愿意接受任何可以获取信息的解决方案 windows 看起来像他们过去那样。
我尝试的策略是获取 Google 地图 API 添加到每个元素的内联样式,然后通过 jQuery 应用该样式。从理论上讲,这应该有效,因为 jQuery's css() method does not use inline CSS:
It’s worth mentioning that if style properties are updated via
JavaScript directly, then you won’t have a problem. For example,
jQuery’s css() method is fine because it updates style properties
directly under the covers.
以下是一个最小的可重现示例。
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.jquery.com/jquery-3.4.0.min.js" integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg=" crossorigin="anonymous"></script>
<script type="text/javascript" src="/js/test.js"></script>
<link type="text/css" rel="stylesheet" href="/css/test.css">
</head>
<body>
<div id="map-wrapper">
<div id="map-canvas"></div>
</div>
</body>
</html>
CSS:
#map-canvas {
height: 100%;
width: 100%;
}
#map-wrapper {
height: 400px;
margin: auto;
max-width: 95%;
width: 600px;
}
JavaScript:
//on page load, call the function to initialize the map
$(function($) {
var script = document.createElement('script');
script.src = "//maps.googleapis.com/maps/api/js?callback=initialize&key=yourKeyHere";
document.body.appendChild(script);
});
//function to initialize the map
function initialize() {
//construct the map
var map = new google.maps.Map(document.getElementById("map-canvas"), {
fullscreenControl: false,
mapTypeId: 'roadmap',
scaleControl: false,
streetViewControl: false,
zoomControl: false,
});
//center the map
var latLng = new google.maps.LatLng('34.4270881', '-117.57501819999999');
map.setCenter(latLng);
map.fitBounds(new google.maps.LatLngBounds(latLng));
//info window
var infoWindow = new google.maps.InfoWindow(), marker, i;
marker = new google.maps.Marker({position: latLng, map: map, title: 'marker title'});
google.maps.event.addListener(marker, 'click', (function(marker, i) { return function() {
infoWindow.setContent("foobar");
infoWindow.open(map, marker);
google.maps.event.addListener(infoWindow, 'domready', applyStyles(document.getElementById("map-wrapper")));
}})(marker, i));
}
//function to get inline CSS and apply it via jQuery
function applyStyles(div) {
//log the div
console.log(div);
//loop through the inline CSS properties applied by Google Maps API
for (var propertyNum = 0; propertyNum < div.style.length; propertyNum++) {
//get property and convert to camelCase
var property = div.style[propertyNum].replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })
//get value
var value = div.style[property];
//log the property and value
console.log(property + " = " + value);
//apply style via jquery
$(div).css(property, value);
}
//log a line break
console.log("");
//do the same for children of this div
var children = $(div).children();
for (var child = 0; child < children.length; child++) {
applyStyles(children[child]);
}
}
这是行不通的。 None 个样式正在生效。
None of the styles are taking effect.
这不是真的。我通过元素的 style
属性 "getting" 的样式实际上是通过 jQuery .css()
方法应用的。
我的错误是假设 API 应用的样式仅限于 inline CSS(例如 <div style="background=color: #ffffff">
) API 也在注入 internal CSS(例如 <style>div {background=color: #ffffff">}</style>
)。
我注意到相关的 div
都有 class 名称以 gm-style-iw
开头(其中,大概 "gm" 表示 Google 地图和 "iw" 表示信息 window)。我搜索了注入的样式标签,发现其中只有一个有这些 class 个名称。
.gm-style .gm-style-iw{font-weight:300;font-size:13px;overflow:hidden}.gm-style .gm-style-iw-a{position:absolute;width:9999px;height:0}.gm-style .gm-style-iw-t{position:absolute;width:100%}.gm-style .gm-style-iw-t::after{background:linear-gradient(45deg,rgba(255,255,255,1) 50%,rgba(255,255,255,0) 51%,rgba(255,255,255,0) 100%);box-shadow:-2px 2px 2px 0 rgba(178,178,178,.4);content:"";height:15px;left:0;position:absolute;top:0;transform:translate(-50%,-50%) rotate(-45deg);width:15px}.gm-style .gm-style-iw-c{position:absolute;box-sizing:border-box;overflow:hidden;top:0;left:0;transform:translate(-50%,-100%);background-color:white;border-radius:8px;padding:12px;box-shadow:0 2px 7px 1px rgba(0,0,0,0.3)}.gm-style .gm-style-iw-d{box-sizing:border-box;overflow:auto}.gm-style .gm-style-iw-d::-webkit-scrollbar{width:18px;height:12px;-webkit-appearance:none}.gm-style .gm-style-iw-d::-webkit-scrollbar-track,.gm-style .gm-style-iw-d::-webkit-scrollbar-track-piece{background:#fff}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,0.12);border:6px solid transparent;border-radius:9px;background-clip:content-box}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-thumb:horizontal{border:3px solid transparent}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-thumb:hover{background-color:rgba(0,0,0,0.3)}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-corner{background:transparent}.gm-style .gm-iw{color:#2c2c2c}.gm-style .gm-iw b{font-weight:400}.gm-style .gm-iw a:link,.gm-style .gm-iw a:visited{color:#4272db;text-decoration:none}.gm-style .gm-iw a:hover{color:#4272db;text-decoration:underline}.gm-style .gm-iw .gm-title{font-weight:400;margin-bottom:1px}.gm-style .gm-iw .gm-basicinfo{line-height:18px;padding-bottom:12px}.gm-style .gm-iw .gm-website{padding-top:6px}.gm-style .gm-iw .gm-photos{padding-bottom:8px;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none}.gm-style .gm-iw .gm-sv,.gm-style .gm-iw .gm-ph{cursor:pointer;height:50px;width:100px;position:relative;overflow:hidden}.gm-style .gm-iw .gm-sv{padding-right:4px}.gm-style .gm-iw .gm-wsv{cursor:pointer;position:relative;overflow:hidden}.gm-style .gm-iw .gm-sv-label,.gm-style .gm-iw .gm-ph-label{cursor:pointer;position:absolute;bottom:6px;color:#fff;font-weight:400;text-shadow:rgba(0,0,0,0.7) 0 1px 4px;font-size:12px}.gm-style .gm-iw .gm-stars-b,.gm-style .gm-iw .gm-stars-f{height:13px;font-size:0}.gm-style .gm-iw .gm-stars-b{position:relative;background-position:0 0;width:65px;top:3px;margin:0 5px}.gm-style .gm-iw .gm-rev{line-height:20px;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none}.gm-style.gm-china .gm-iw .gm-rev{display:none}.gm-style .gm-iw .gm-numeric-rev{font-size:16px;color:#dd4b39;font-weight:400}.gm-style .gm-iw.gm-transit{margin-left:15px}.gm-style .gm-iw.gm-transit td{vertical-align:top}.gm-style .gm-iw.gm-transit .gm-time{white-space:nowrap;color:#676767;font-weight:bold}.gm-style .gm-iw.gm-transit img{width:15px;height:15px;margin:1px 5px 0 -20px;float:left}
我只是复制并粘贴了 CSS 并将其添加到外部样式表。一个更理想的解决方案是以编程方式获取 CSS 并通过 jQuery 应用它,但这对我现在的目的有用。
但是即使在我这样做之后,信息 window 右上角的 "x" 按钮也没有出现,因为图像源是数据 URI,这是不允许的强大的 CSP。
<img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224px%22%20height%3D%2224px%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%23000000%22%3E%0A%20%20%20%20%3Cpath%20d%3D%22M19%206.41L17.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%0A%3C%2Fsvg%3E%0A" style="pointer-events: none; display: block; width: 13px; height: 13px; margin: 12px;">
为了解决这个问题,我暂时禁用了 CSP,以便我可以截取 x 按钮的屏幕截图。我实际上截了两张屏幕截图,一张是普通符号,一张是 mouseover
上出现的深色版本。我将这些保存为 GIF 并存储在服务器上。
我发现 domready
事件没有按信息 window 的预期运行。我的解决方法如下:
marker.addListener('click', function() {
infoWindow.setContent("<div class='info-window'>foobar</div>");
infoWindow.open(map, marker);
//check to see if the info window has loaded yet
checkForInfoWindow();
});
function checkForInfoWindow() {
var checkForInfoWindow = setInterval(function() {
if ($(".info-window").length) {
styleInfoWindow();
clearInterval(checkForInfoWindow);
}
}, 1);
}
function styleInfoWindow() {
//style the "x" button
var button = $(".info-window").parent().parent().parent().children("button");
$(button).html("<img src='/images/x.gif'>");
$(button).mouseover(function() {
$(button).html("<img src='/images/x-hover.gif'>");
});
$(button).mouseout(function() {
$(button).html("<img src='/images/x.gif'>");
});
//get divs with a class name beginning with "gm-style-iw"
var divs = $("div[class^='gm-style-iw']");
//for each div
for (var div = 0; div < divs.length; div++) {
//for each inline css property
for (var propertyNum = 0; propertyNum < divs[div].style.length; propertyNum++) {
//get property and convert to camelCase
var property = divs[div].style[propertyNum].replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })
//get value
var value = divs[div].style[property];
//apply style
divs[div].style[property] = value;
}
}
}
但是"x"按钮的位置不太对,因为在有垂直滚动条的时候,"x"按钮几乎是在上面。我通过将以下内容添加到外部 CSS:
来解决这个问题
.gm-style-iw-d{margin-top: 1rem;}
瞧!信息 windows 看起来和我实施 CSP 之前一样。
我在一个网站上使用 Google 地图 Javascript API,其内容安全策略不允许内联 CSS。
在实施 CSP 之前,信息 windows 的样式设计得很好。实施 CSP 后,信息 windows 根本没有样式化,因为 Google 地图 Javascript API 使用内联 CSS.
我愿意接受任何可以获取信息的解决方案 windows 看起来像他们过去那样。
我尝试的策略是获取 Google 地图 API 添加到每个元素的内联样式,然后通过 jQuery 应用该样式。从理论上讲,这应该有效,因为 jQuery's css() method does not use inline CSS:
It’s worth mentioning that if style properties are updated via JavaScript directly, then you won’t have a problem. For example, jQuery’s css() method is fine because it updates style properties directly under the covers.
以下是一个最小的可重现示例。
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.jquery.com/jquery-3.4.0.min.js" integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg=" crossorigin="anonymous"></script>
<script type="text/javascript" src="/js/test.js"></script>
<link type="text/css" rel="stylesheet" href="/css/test.css">
</head>
<body>
<div id="map-wrapper">
<div id="map-canvas"></div>
</div>
</body>
</html>
CSS:
#map-canvas {
height: 100%;
width: 100%;
}
#map-wrapper {
height: 400px;
margin: auto;
max-width: 95%;
width: 600px;
}
JavaScript:
//on page load, call the function to initialize the map
$(function($) {
var script = document.createElement('script');
script.src = "//maps.googleapis.com/maps/api/js?callback=initialize&key=yourKeyHere";
document.body.appendChild(script);
});
//function to initialize the map
function initialize() {
//construct the map
var map = new google.maps.Map(document.getElementById("map-canvas"), {
fullscreenControl: false,
mapTypeId: 'roadmap',
scaleControl: false,
streetViewControl: false,
zoomControl: false,
});
//center the map
var latLng = new google.maps.LatLng('34.4270881', '-117.57501819999999');
map.setCenter(latLng);
map.fitBounds(new google.maps.LatLngBounds(latLng));
//info window
var infoWindow = new google.maps.InfoWindow(), marker, i;
marker = new google.maps.Marker({position: latLng, map: map, title: 'marker title'});
google.maps.event.addListener(marker, 'click', (function(marker, i) { return function() {
infoWindow.setContent("foobar");
infoWindow.open(map, marker);
google.maps.event.addListener(infoWindow, 'domready', applyStyles(document.getElementById("map-wrapper")));
}})(marker, i));
}
//function to get inline CSS and apply it via jQuery
function applyStyles(div) {
//log the div
console.log(div);
//loop through the inline CSS properties applied by Google Maps API
for (var propertyNum = 0; propertyNum < div.style.length; propertyNum++) {
//get property and convert to camelCase
var property = div.style[propertyNum].replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })
//get value
var value = div.style[property];
//log the property and value
console.log(property + " = " + value);
//apply style via jquery
$(div).css(property, value);
}
//log a line break
console.log("");
//do the same for children of this div
var children = $(div).children();
for (var child = 0; child < children.length; child++) {
applyStyles(children[child]);
}
}
这是行不通的。 None 个样式正在生效。
None of the styles are taking effect.
这不是真的。我通过元素的 style
属性 "getting" 的样式实际上是通过 jQuery .css()
方法应用的。
我的错误是假设 API 应用的样式仅限于 inline CSS(例如 <div style="background=color: #ffffff">
) API 也在注入 internal CSS(例如 <style>div {background=color: #ffffff">}</style>
)。
我注意到相关的 div
都有 class 名称以 gm-style-iw
开头(其中,大概 "gm" 表示 Google 地图和 "iw" 表示信息 window)。我搜索了注入的样式标签,发现其中只有一个有这些 class 个名称。
.gm-style .gm-style-iw{font-weight:300;font-size:13px;overflow:hidden}.gm-style .gm-style-iw-a{position:absolute;width:9999px;height:0}.gm-style .gm-style-iw-t{position:absolute;width:100%}.gm-style .gm-style-iw-t::after{background:linear-gradient(45deg,rgba(255,255,255,1) 50%,rgba(255,255,255,0) 51%,rgba(255,255,255,0) 100%);box-shadow:-2px 2px 2px 0 rgba(178,178,178,.4);content:"";height:15px;left:0;position:absolute;top:0;transform:translate(-50%,-50%) rotate(-45deg);width:15px}.gm-style .gm-style-iw-c{position:absolute;box-sizing:border-box;overflow:hidden;top:0;left:0;transform:translate(-50%,-100%);background-color:white;border-radius:8px;padding:12px;box-shadow:0 2px 7px 1px rgba(0,0,0,0.3)}.gm-style .gm-style-iw-d{box-sizing:border-box;overflow:auto}.gm-style .gm-style-iw-d::-webkit-scrollbar{width:18px;height:12px;-webkit-appearance:none}.gm-style .gm-style-iw-d::-webkit-scrollbar-track,.gm-style .gm-style-iw-d::-webkit-scrollbar-track-piece{background:#fff}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,0.12);border:6px solid transparent;border-radius:9px;background-clip:content-box}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-thumb:horizontal{border:3px solid transparent}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-thumb:hover{background-color:rgba(0,0,0,0.3)}.gm-style .gm-style-iw-c .gm-style-iw-d::-webkit-scrollbar-corner{background:transparent}.gm-style .gm-iw{color:#2c2c2c}.gm-style .gm-iw b{font-weight:400}.gm-style .gm-iw a:link,.gm-style .gm-iw a:visited{color:#4272db;text-decoration:none}.gm-style .gm-iw a:hover{color:#4272db;text-decoration:underline}.gm-style .gm-iw .gm-title{font-weight:400;margin-bottom:1px}.gm-style .gm-iw .gm-basicinfo{line-height:18px;padding-bottom:12px}.gm-style .gm-iw .gm-website{padding-top:6px}.gm-style .gm-iw .gm-photos{padding-bottom:8px;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none}.gm-style .gm-iw .gm-sv,.gm-style .gm-iw .gm-ph{cursor:pointer;height:50px;width:100px;position:relative;overflow:hidden}.gm-style .gm-iw .gm-sv{padding-right:4px}.gm-style .gm-iw .gm-wsv{cursor:pointer;position:relative;overflow:hidden}.gm-style .gm-iw .gm-sv-label,.gm-style .gm-iw .gm-ph-label{cursor:pointer;position:absolute;bottom:6px;color:#fff;font-weight:400;text-shadow:rgba(0,0,0,0.7) 0 1px 4px;font-size:12px}.gm-style .gm-iw .gm-stars-b,.gm-style .gm-iw .gm-stars-f{height:13px;font-size:0}.gm-style .gm-iw .gm-stars-b{position:relative;background-position:0 0;width:65px;top:3px;margin:0 5px}.gm-style .gm-iw .gm-rev{line-height:20px;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none}.gm-style.gm-china .gm-iw .gm-rev{display:none}.gm-style .gm-iw .gm-numeric-rev{font-size:16px;color:#dd4b39;font-weight:400}.gm-style .gm-iw.gm-transit{margin-left:15px}.gm-style .gm-iw.gm-transit td{vertical-align:top}.gm-style .gm-iw.gm-transit .gm-time{white-space:nowrap;color:#676767;font-weight:bold}.gm-style .gm-iw.gm-transit img{width:15px;height:15px;margin:1px 5px 0 -20px;float:left}
我只是复制并粘贴了 CSS 并将其添加到外部样式表。一个更理想的解决方案是以编程方式获取 CSS 并通过 jQuery 应用它,但这对我现在的目的有用。
但是即使在我这样做之后,信息 window 右上角的 "x" 按钮也没有出现,因为图像源是数据 URI,这是不允许的强大的 CSP。
<img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224px%22%20height%3D%2224px%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%23000000%22%3E%0A%20%20%20%20%3Cpath%20d%3D%22M19%206.41L17.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%0A%3C%2Fsvg%3E%0A" style="pointer-events: none; display: block; width: 13px; height: 13px; margin: 12px;">
为了解决这个问题,我暂时禁用了 CSP,以便我可以截取 x 按钮的屏幕截图。我实际上截了两张屏幕截图,一张是普通符号,一张是 mouseover
上出现的深色版本。我将这些保存为 GIF 并存储在服务器上。
我发现 domready
事件没有按信息 window 的预期运行。我的解决方法如下:
marker.addListener('click', function() {
infoWindow.setContent("<div class='info-window'>foobar</div>");
infoWindow.open(map, marker);
//check to see if the info window has loaded yet
checkForInfoWindow();
});
function checkForInfoWindow() {
var checkForInfoWindow = setInterval(function() {
if ($(".info-window").length) {
styleInfoWindow();
clearInterval(checkForInfoWindow);
}
}, 1);
}
function styleInfoWindow() {
//style the "x" button
var button = $(".info-window").parent().parent().parent().children("button");
$(button).html("<img src='/images/x.gif'>");
$(button).mouseover(function() {
$(button).html("<img src='/images/x-hover.gif'>");
});
$(button).mouseout(function() {
$(button).html("<img src='/images/x.gif'>");
});
//get divs with a class name beginning with "gm-style-iw"
var divs = $("div[class^='gm-style-iw']");
//for each div
for (var div = 0; div < divs.length; div++) {
//for each inline css property
for (var propertyNum = 0; propertyNum < divs[div].style.length; propertyNum++) {
//get property and convert to camelCase
var property = divs[div].style[propertyNum].replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })
//get value
var value = divs[div].style[property];
//apply style
divs[div].style[property] = value;
}
}
}
但是"x"按钮的位置不太对,因为在有垂直滚动条的时候,"x"按钮几乎是在上面。我通过将以下内容添加到外部 CSS:
来解决这个问题.gm-style-iw-d{margin-top: 1rem;}
瞧!信息 windows 看起来和我实施 CSP 之前一样。