Commit e982f201 authored by Ali Juma's avatar Ali Juma Committed by Commit Bot

[iOS] Don't throw un-spec'd exceptions in pushState and replaceState

The injected implementations of pushState and replaceState stringify
the given state using JSON.stringify. This behavior is non-standard,
and leaks to the web when JSON.stringify fails and throws a TypeError.
This happens when the given state object has a cycle.

To prevent leaking this implementation detail, this CL catches exceptions
thrown by JSON.stringify, and then throws a standard DataCloneError instead.

This fixes subtests in the following two Web Platform Tests that currently
fail in Chrome but pass in Safari:
html/browsers/histoy/the-history-interface/001.html
html/browsers/histoy/the-history-interface/002.html

Longer term, we should remove the stringification all together. This is
blocked on deleting legacy navigation and finding an alternate solution to
crbug.com/949305 that doesn't involve capturing and replaying the
state passed to pushState or replaceState.

Bug: 769945
Change-Id: I945251ffe09ecac79f086ca75030dc366ef99286
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1866870Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Commit-Queue: Ali Juma <ajuma@chromium.org>
Cr-Commit-Position: refs/heads/master@{#707022}
parent 52725075
...@@ -63,6 +63,14 @@ __gCrWeb['replaceWebViewURL'] = function(url, stateObject) { ...@@ -63,6 +63,14 @@ __gCrWeb['replaceWebViewURL'] = function(url, stateObject) {
originalWindowHistoryReplaceState.call(history, stateObject, '', url); originalWindowHistoryReplaceState.call(history, stateObject, '', url);
}; };
function DataCloneError() {
// The name and code for this error are defined by the WebIDL spec. See
// https://heycam.github.io/webidl/#datacloneerror
this.name = 'DataCloneError';
this.code = 25;
this.message = "Cyclic structures are not supported.";
}
/** /**
* Intercepts window.history methods so native code can differentiate between * Intercepts window.history methods so native code can differentiate between
* same-document navigation that are state navigations vs. hash navigations. * same-document navigation that are state navigations vs. hash navigations.
...@@ -73,10 +81,18 @@ __gCrWeb['replaceWebViewURL'] = function(url, stateObject) { ...@@ -73,10 +81,18 @@ __gCrWeb['replaceWebViewURL'] = function(url, stateObject) {
*/ */
window.history.pushState = function(stateObject, pageTitle, pageUrl) { window.history.pushState = function(stateObject, pageTitle, pageUrl) {
__gCrWeb.message.invokeOnHost({'command': 'navigation.willChangeState'}); __gCrWeb.message.invokeOnHost({'command': 'navigation.willChangeState'});
// Calling stringify() on undefined causes a JSON parse error.
var serializedState = typeof (stateObject) == 'undefined' ? // JSONStringify throws an exception when given a cyclical object. This
'' : // internal implementation detail should not be exposed to callers of
__gCrWeb.common.JSONStringify(stateObject); // pushState. Instead, throw a standard exception when stringification fails.
try {
// Calling stringify() on undefined causes a JSON parse error.
var serializedState = typeof (stateObject) == 'undefined' ?
'' :
__gCrWeb.common.JSONStringify(stateObject);
} catch (e) {
throw new DataCloneError();
}
pageUrl = pageUrl || window.location.href; pageUrl = pageUrl || window.location.href;
originalWindowHistoryPushState.call(history, stateObject, pageTitle, pageUrl); originalWindowHistoryPushState.call(history, stateObject, pageTitle, pageUrl);
__gCrWeb.message.invokeOnHost({ __gCrWeb.message.invokeOnHost({
...@@ -90,10 +106,18 @@ window.history.pushState = function(stateObject, pageTitle, pageUrl) { ...@@ -90,10 +106,18 @@ window.history.pushState = function(stateObject, pageTitle, pageUrl) {
window.history.replaceState = function(stateObject, pageTitle, pageUrl) { window.history.replaceState = function(stateObject, pageTitle, pageUrl) {
__gCrWeb.message.invokeOnHost({'command': 'navigation.willChangeState'}); __gCrWeb.message.invokeOnHost({'command': 'navigation.willChangeState'});
// Calling stringify() on undefined causes a JSON parse error. // JSONStringify throws an exception when given a cyclical object. This
var serializedState = typeof (stateObject) == 'undefined' ? // internal implementation detail should not be exposed to callers of
'' : // replaceState. Instead, throw a standard exception when stringification
__gCrWeb.common.JSONStringify(stateObject); // fails.
try {
// Calling stringify() on undefined causes a JSON parse error.
var serializedState = typeof (stateObject) == 'undefined' ?
'' :
__gCrWeb.common.JSONStringify(stateObject);
} catch (e) {
throw new DataCloneError();
}
pageUrl = pageUrl || window.location.href; pageUrl = pageUrl || window.location.href;
originalWindowHistoryReplaceState.call( originalWindowHistoryReplaceState.call(
history, stateObject, pageTitle, pageUrl); history, stateObject, pageTitle, pageUrl);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment