在 TWebBrowser 中将字符串传递给 Javascript 可能存在编码问题

Possible encoding issue passing string to Javascript in TWebBrowser

简介

这是我加载到 TWebBrowser 以访问 Google 地图的 HTML 页面 API:

<html>
<head>
</head>
<body>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=%GMAPSAPIKEY%&callback=initMap"></script>

<div id="editMap" style="width:600px; height:500px; margin:0; margin-bottom:0; background-color:#F9F9F9; border-right:1px solid #999; float:left;"></div>

<input type='hidden' id='DelphiVan' value='' />
<input type='hidden' id='DelphiNaar' value='' />
<input type='hidden' id='DelphiDistance' value='' />
<input type='hidden' id='DelphiPolyLine' value='' />

<script type="text/javascript">
  function initMap() {
                  ttMapHelper.InitMap('editMap')
  }

var ttMapHelper = (function () {
    var map;
    var directionsService, directionsDisplay;
    var polyLine;
    var markerA;
    var markerB;

    var routeChangedCallback;
    var routeOrigin;
    var routeDestination;
    var encodedLine;
    var totalDistance;
    var totalDistanceText;

    function getDirections(origin, destination, display) {
        directionsService.route({
            origin: origin,
            destination: destination,
            avoidTolls: true,
            travelMode: google.maps.TravelMode.DRIVING, //getTravelMode($('#travelMode').val()),
            unitSystem: google.maps.UnitSystem.METRIC //google.maps.UnitSystem.IMPERIAL
        }, function (response, status) {
            if (status === google.maps.DirectionsStatus.OK) {
                if (display)
                    directionsDisplay.setDirections(response);
                else
                    processDirections(response);
            }
            else {
                alert('Could not display directions due to: ' + status);
            }
        });
    }
    function getTravelMode(mode) {
        switch (mode) {
            case "DRIVING":
                return google.maps.TravelMode.DRIVING;
            case "BICYCLING":
                return google.maps.TravelMode.BICYCLING;
            case "WALKING":
                return google.maps.TravelMode.WALKING;
        }
    }
    function processDirections(result) {
        var route = result.routes[0];
        var leg = route.legs[0];

        routeOrigin = leg.start_address
        routeDestination = leg.end_address
        totalDistance = leg.distance.value;
        totalDistanceText = leg.distance.text;
        encodedLine = route.overview_polyline;

        if (routeChangedCallback)
            routeChangedCallback();
    }
    function displayPolyLine(encodedLine) {
        if (polyLine) {
            polyLine.setMap(null);
            markerA.setMap(null);
            markerB.setMap(null);
            polyLine = undefined;
            markerA = undefined;
            markerB = undefined;
        }

        var coordinates = google.maps.geometry.encoding.decodePath(encodedLine);
        polyLine = new google.maps.Polyline({
            path: coordinates,
            strokeColor: '#0000FF',
            strokeOpacity: 1.0,
            strokeWeight: 2
        });
        polyLine.setMap(map);

        markerA = new google.maps.Marker({
            position: coordinates[0],
            label: 'A',
            map: map
        });

        markerB = new google.maps.Marker({
            position: coordinates[coordinates.length - 1],
            label: 'B',
            map: map
        });

        var bounds = new google.maps.LatLngBounds();
        for (var i = 0; i < coordinates.length; i++) {
            bounds.extend(coordinates[i]);
        }
        map.fitBounds(bounds);
    }

    var initMap = function (mapId) {
        map = new google.maps.Map(document.getElementById(mapId), {
            zoom: 7,
            center: { lat: 52.2169918, lng: 5.6460789 },
            mapTypeId: google.maps.MapTypeId.ROADMAP
        });
        directionsService = new google.maps.DirectionsService;
        directionsDisplay = new google.maps.DirectionsRenderer({
            draggable: false,
            suppressBicyclingLayer: true,
            map: map
        });
        directionsDisplay.addListener('directions_changed', function () {
            processDirections(directionsDisplay.getDirections());
        });
    }
    var calculateRoute = function (origin, destination, callback) {
        routeChangedCallback = callback;
        routeOrigin = origin;
        routeDestination = destination;
        getDirections(origin, destination);
    }
    var editRoute = function (origin, destination, callback) {
        routeChangedCallback = callback;
        routeOrigin = origin;
        routeDestination = destination;
        directionsDisplay.setOptions({ draggable: true });
        getDirections(origin, destination, true);
    }
    var showRoute = function (encodedLine) {
        displayPolyLine(encodedLine);
    }

    var getOrigin = function () { return routeOrigin; }
    var getDestination = function () { return routeDestination; }
    var getDistance = function () { return totalDistance; }
    var getDistanceText = function () { return totalDistanceText; }
    var getEncodedLine = function () { return encodedLine; }

    return {
        InitMap: initMap,
        CalculateRoute: calculateRoute,
        EditRoute: editRoute,
        ShowRoute: showRoute,
        GetOrigin: getOrigin,
        GetDestination: getDestination,
        GetDistance: getDistance,
        GetDistanceText: getDistanceText,
        GetEncodedLine: getEncodedLine
    }
})();

  function PrepareDelphiVars() {
    document.getElementById('DelphiVan').value = ttMapHelper.GetOrigin();
    document.getElementById('DelphiNaar').value = ttMapHelper.GetDestination();
    document.getElementById('DelphiDistance').value = ttMapHelper.GetDistance();
    document.getElementById('DelphiPolyLine').value = ttMapHelper.GetEncodedLine();
  };

</script>
</body>
</html>

最初,这可以很好地确定从 A 到 B 的路由。在 WebBrowserDocumentComplete 处理程序中,我执行:

procedure TFrmGoogleMaps.ToonRoute;
begin
  FHTMLWindow.execScript('ttMapHelper.EditRoute("' + FVan + '","' + FNaar + '",function (){PrepareDelphiVars();})', 'JavaScript')
end;

PrepareDelphiVars 将生成的数据放在隐藏的输入字段中,我用

检索它们
lElement := WebBrowser.OleObject.Document.getElementById('DelphiVan');
if not VarIsNull(lElement) then
   FVan := lElement.getAttribute('value');

我检索到的内容之一是生成的 折线。它的形式是

i|r~Hi{y\kAqDh@wIBcEGcELcCNoHOoFDwDLqE`@mBl@sBx@g@NE]kCg@{BsFoG[eAC[b@w@`@o@pBwApCsAdWyM~^{QvDgBlKoFnOeIzEgJhEgIxAcCtB_Cf@e@Tk@Hw@e@uIm@wHIiDJ}....

如果我 copy/paste 这个字符串从 TMemo 到 Google 的 Interactive Polyline Encode/Decoder 我得到从鹿特丹到阿姆斯特丹的正确路线:

问题

我想在下次再次启动 TWebBrowser 时显示这条生成的折线(注意:它在每次创建 run-time 的表单上)。
所以我现在从 WebBrowserDocumentComplete:

打电话
procedure TFrmGoogleMaps.ToonPolyLine;
begin
  FHTMLWindow.execScript('ttMapHelper.ShowRoute("' + FPolyLine + '")', 'JavaScript')
end;

与 TMemo 中的字符串完全相同。 我现在在德国(我在 https://maps.googleapis.com/maps-api-v3/api/js/24/10/intl/nl_ALL/onion.js 秒后移动鼠标时出现 Javascript 错误):

这看起来像是一个编码问题,但我还找不到解决方案。这是我试过的:

注意:我的标题说 可能。这是因为 TMemo 内容和 JavaScript alert() 之间的比较似乎显示 字符串中没有差异 除了 \b 的小显示问题子字符串:

此外,Javascript 代码是从云解决方案中复制的,它运行良好。

这里可能出了什么问题,解决方案是什么?

(Delphi 10 升级 1)

FWIW,这就是我将文件的 HTML 加载到 TWebBrowser 中的方式:

procedure TFrmGoogleMaps.FormCreate(Sender: TObject);
var
   lMemStream : TMemoryStream;
   lFileStream: TFileStream;
   lData      : ANSIString;
   lFileName  : String;
   p,l        : Integer;
begin
   lFileName := ExtractFilePath(ParamStr(0)) + cMapsHTMLFile;
   lFileStream := TFileStream.Create(lFileName,fmOpenRead);
   SetLength(lData, lFileStream.Size);
   lFileStream.ReadBuffer(Pointer(lData)^, Length(lData));
   lFileStream.Free;
   // Replace the API key marker with the actual key:
   p := System.AnsiStrings.PosEx(cAPIKeyMarker,lData);
   l := Length(cAPIKeyMarker);
   Delete(lData,p,l);
   Insert(cGoogleMapsAPIKey,lData,p);
   WebBrowser.Navigate('about:blank');  // Nodig voor initialisatie
   if Assigned(WebBrowser.Document) then
   begin
      lMemStream := TMemoryStream.Create;
      try
         lMemStream.WriteBuffer(Pointer(lData)^, Length(lData));
         lMemStream.Seek(0, soFromBeginning);
         (WebBrowser.Document as IPersistStreamInit).Load(TStreamAdapter.Create(lMemStream));
      finally
         lMemStream.Free;
      end;
      FHTMLWindow := (WebBrowser.Document as IHTMLDocument2).parentWindow;
    end;
end;

问题出在这一行:

FHTMLWindow.execScript('ttMapHelper.ShowRoute("' + FPolyLine + '")', 'JavaScript')

并且与 SQL injection 非常相似:如果 FPolyLine 是 JavaScript 格式,则您会默默地假设该值,但事实并非如此。一个简单的解决方法可能是使用

StringReplace(StringReplace(FPolyLine'\','\',[rfReplaceAll]),'"','\"',[rfReplaceAll])

但更好的解决方法是使用额外的 <input type="hidden",通过对象设置它的值,并通过 JavaScript.

中的名称调用它