blob: 0f21c89ecce62344d01e3bdfa82aa343f8298397 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Navigation related APIs.
*/
/**
* Keep the original pushState() and replaceState() methods. It's needed to
* update the web view's URL and window.history.state property during history
* navigations that don't cause a page load.
* @private
*/
var originalWindowHistoryPushState = window.history.pushState;
var originalWindowHistoryReplaceState = window.history.replaceState;
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.';
}
// Stores queued messages until they can be sent to the "NavigationEventMessage"
// handler.
var queuedMessages = [];
// Attempts to send any queued messages. Messages will be only be removed once
// they have been sent.
function sendQueuedMessages() {
while (queuedMessages.length > 0) {
try {
__gCrWeb.common.sendWebKitMessage(
'NavigationEventMessage', queuedMessages[0]);
queuedMessages.shift();
} catch (e) {
// 'NavigationEventMessage' message handler is not currently registered.
// Send the message later when possible.
break;
}
}
};
// Queues the |message| and triggers the queue to be sent.
function queueNavigationEventMessage(message) {
queuedMessages.push(message);
sendQueuedMessages();
};
/**
* Intercepts window.history methods so native code can differentiate between
* same-document navigation that are state navigations vs. hash navigations.
* This is needed for backward compatibility of DidStartLoading, which is
* triggered for fragment navigation but not state navigation.
* TODO(crbug.com/783382): Remove this once DidStartLoading is no longer
* called for same-document navigation.
*/
window.history.pushState = function(stateObject, pageTitle, pageUrl) {
queueNavigationEventMessage({
'command': 'willChangeState',
'frame_id': __gCrWeb.message.getFrameId()
});
// JSONStringify throws an exception when given a cyclical object. This
// internal implementation detail should not be exposed to callers of
// 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;
originalWindowHistoryPushState.call(history, stateObject, pageTitle, pageUrl);
queueNavigationEventMessage({
'command': 'didPushState',
'stateObject': serializedState,
'baseUrl': document.baseURI,
'pageUrl': pageUrl.toString(),
'frame_id': __gCrWeb.message.getFrameId()
});
};
window.history.replaceState = function(stateObject, pageTitle, pageUrl) {
queueNavigationEventMessage({
'command': 'willChangeState',
'frame_id': __gCrWeb.message.getFrameId()
});
// JSONStringify throws an exception when given a cyclical object. This
// internal implementation detail should not be exposed to callers of
// replaceState. 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;
originalWindowHistoryReplaceState.call(
history, stateObject, pageTitle, pageUrl);
queueNavigationEventMessage({
'command': 'didReplaceState',
'stateObject': serializedState,
'baseUrl': document.baseURI,
'pageUrl': pageUrl.toString(),
'frame_id': __gCrWeb.message.getFrameId()
});
};
window.addEventListener('__gCrWebWindowIdInjected', function() {
sendQueuedMessages();
});