blob: f62aa588e70afaefe66eb24d12e1a923da2adece [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function() {
// We are going to kill all of the builtins, so hold onto the ones we need.
var defineProperty = Object.defineProperty;
var Error = window.Error;
var forEach = Array.prototype.forEach;
var push = Array.prototype.push;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var getOwnPropertyNames = Object.getOwnPropertyNames;
var stringify = JSON.stringify;
// Kill all of the builtins functions to give us a fairly high confidence that
// the environment our bindings run in can't interfere with our code.
// These are taken from the ECMAScript spec.
var builtinTypes = [
Object, Function, Array, String, Boolean, Number, Math, Date, RegExp, JSON,
];
function clobber(obj, name, qualifiedName) {
// Clobbering constructors would break everything.
// Clobbering toString is annoying.
// Clobbering __proto__ breaks in ways that grep can't find.
// Clobbering function name will break because
// SafeBuiltins does not support getters yet. See crbug.com/463526.
// Clobbering Function.call would make it impossible to implement these tests.
// Clobbering Object.valueOf breaks v8.
// Clobbering %FunctionPrototype%.caller and .arguments will break because
// these properties are poisoned accessors in ES6.
if (name == 'constructor' ||
name == 'toString' ||
name == '__proto__' ||
name == 'name' && typeof obj == 'function' ||
qualifiedName == 'Function.call' ||
(obj !== Function && qualifiedName == 'Function.caller') ||
(obj !== Function && qualifiedName == 'Function.arguments') ||
qualifiedName == 'Object.valueOf') {
return;
}
var desc = getOwnPropertyDescriptor(obj, name);
if (!desc.configurable) return;
var new_desc;
if (desc.get || desc.set || typeof desc.value !== 'function') {
new_desc =
{ get: function() {
throw new Error('Clobbered ' + qualifiedName + ' getter');
},
set: function(x) {
throw new Error('Clobbered ' + qualifiedName + ' setter');
},
};
} else {
new_desc =
{ value: function() {
throw new Error('Clobbered ' + qualifiedName + ' function');
}
};
}
defineProperty(obj, name, new_desc);
}
forEach.call(builtinTypes, function(builtin) {
var prototype = builtin.prototype;
var typename = '<unknown>';
if (prototype) {
typename = prototype.constructor.name;
forEach.call(getOwnPropertyNames(prototype), function(name) {
clobber(prototype, name, typename + '.' + name);
});
}
forEach.call(getOwnPropertyNames(builtin), function(name) {
clobber(builtin, name, typename + '.' + name);
});
if (builtin.name)
clobber(window, builtin.name, 'window.' + builtin.name);
});
// Codes for test results. Must match ExternallyConnectableMessagingTest::Result
// in c/b/extensions/extension_messages_apitest.cc.
var results = {
OK: 0,
NAMESPACE_NOT_DEFINED: 1,
FUNCTION_NOT_DEFINED: 2,
COULD_NOT_ESTABLISH_CONNECTION_ERROR: 3,
OTHER_ERROR: 4,
INCORRECT_RESPONSE_SENDER: 5,
INCORRECT_RESPONSE_MESSAGE: 6,
};
// Make the messages sent vaguely complex, but unambiguously JSON-ifiable.
var kMessage = [{'a': {'b': 10}}, 20, 'c\x10\x11'];
// Our tab's location. Normally this would be our document's location but if
// we're an iframe it will be the location of the parent - in which case,
// expect to be told.
var tabLocationHref = null;
if (parent == window) {
tabLocationHref = document.location.href;
} else {
window.addEventListener('message', function listener(event) {
window.removeEventListener('message', listener);
tabLocationHref = event.data;
});
}
function checkLastError(reply) {
if (!chrome.runtime.lastError)
return true;
var kCouldNotEstablishConnection =
'Could not establish connection. Receiving end does not exist.';
if (chrome.runtime.lastError.message == kCouldNotEstablishConnection)
reply(results.COULD_NOT_ESTABLISH_CONNECTION_ERROR);
else
reply(results.OTHER_ERROR);
return false;
}
function checkResponse(response, reply, expectedMessage, isApp) {
// The response will be an echo of both the original message *and* the
// MessageSender (with the tab field stripped down).
//
// First check the sender was correct.
var incorrectSender = false;
if (!isApp) {
// Only extensions get access to a 'tab' property.
if (!hasOwnProperty.call(response.sender, 'tab')) {
console.warn('Expected a tab, got none');
incorrectSender = true;
}
if (response.sender.tab.url != tabLocationHref) {
console.warn('Expected tab url ' + tabLocationHref + ' got ' +
response.sender.tab.url);
incorrectSender = true;
}
}
if (hasOwnProperty.call(response.sender, 'id')) {
console.warn('Expected no id, got "' + response.sender.id + '"');
incorrectSender = true;
}
if (response.sender.url != document.location.href) {
console.warn('Expected url ' + document.location.href + ' got ' +
response.sender.url);
incorrectSender = true;
}
if (incorrectSender) {
reply(results.INCORRECT_RESPONSE_SENDER);
return false;
}
// Check the correct content was echoed.
var expectedJson = stringify(expectedMessage);
var actualJson = stringify(response.message);
if (actualJson == expectedJson)
return true;
console.warn('Expected message ' + expectedJson + ' got ' + actualJson);
reply(results.INCORRECT_RESPONSE_MESSAGE);
return false;
}
function sendToBrowser(msg) {
domAutomationController.send(msg);
}
function sendToBrowserForTlsChannelId(result) {
// Because the TLS channel ID tests read the TLS either an error code or the
// TLS channel ID string from the same value, they require the result code
// to be sent as a string.
// String() is clobbered, so coerce string creation with +.
sendToBrowser("" + result);
}
function checkRuntime(reply) {
if (!reply)
reply = sendToBrowser;
if (!chrome.runtime) {
reply(results.NAMESPACE_NOT_DEFINED);
return false;
}
if (!chrome.runtime.connect || !chrome.runtime.sendMessage) {
reply(results.FUNCTION_NOT_DEFINED);
return false;
}
return true;
}
function checkRuntimeForTlsChannelId() {
return checkRuntime(sendToBrowserForTlsChannelId);
}
function checkTlsChannelIdResponse(response) {
if (chrome.runtime.lastError) {
if (chrome.runtime.lastError.message == kCouldNotEstablishConnection)
sendToBrowserForTlsChannelId(
results.COULD_NOT_ESTABLISH_CONNECTION_ERROR);
else
sendToBrowserForTlsChannelId(results.OTHER_ERROR);
return;
}
if (response.sender.tlsChannelId !== undefined)
sendToBrowserForTlsChannelId(response.sender.tlsChannelId);
else
sendToBrowserForTlsChannelId('');
}
window.actions = {
appendIframe: function(src) {
var iframe = document.createElement('iframe');
// When iframe has loaded, notify it of our tab location (probably
// document.location) to use in its assertions, then continue.
iframe.addEventListener('load', function listener() {
iframe.removeEventListener('load', listener);
iframe.contentWindow.postMessage(tabLocationHref, '*');
sendToBrowser(true);
});
iframe.src = src;
document.body.appendChild(iframe);
}
};
window.assertions = {
canConnectAndSendMessages: function(extensionId, isApp, message) {
if (!checkRuntime())
return;
if (!message)
message = kMessage;
function canSendMessage(reply) {
chrome.runtime.sendMessage(extensionId, message, function(response) {
if (checkLastError(reply) &&
checkResponse(response, reply, message, isApp)) {
reply(results.OK);
}
});
}
function canConnectAndSendMessages(reply) {
var port = chrome.runtime.connect(extensionId);
port.postMessage(message, function() {
checkLastError(reply);
});
port.postMessage(message, function() {
checkLastError(reply);
});
var pendingResponses = 2;
var ok = true;
port.onMessage.addListener(function(response) {
pendingResponses--;
ok = ok && checkLastError(reply) &&
checkResponse(response, reply, message, isApp);
if (pendingResponses == 0 && ok)
reply(results.OK);
});
}
canSendMessage(function(result) {
if (result != results.OK)
sendToBrowser(result);
else
canConnectAndSendMessages(sendToBrowser);
});
},
trySendMessage: function(extensionId) {
chrome.runtime.sendMessage(extensionId, kMessage, function(response) {
// The result is unimportant. All that matters is the attempt.
});
},
tryIllegalArguments: function() {
// Tests that illegal arguments to messaging functions throw exceptions.
// Regression test for crbug.com/472700, where they crashed the renderer.
function runIllegalFunction(fun) {
try {
fun();
} catch(e) {
return true;
}
console.error('Function did not throw exception: ' + fun);
sendToBrowser(false);
return false;
}
var result =
runIllegalFunction(chrome.runtime.connect) &&
runIllegalFunction(function() {
chrome.runtime.connect('');
}) &&
runIllegalFunction(function() {
chrome.runtime.connect(42);
}) &&
runIllegalFunction(function() {
chrome.runtime.connect('', 42);
}) &&
runIllegalFunction(function() {
chrome.runtime.connect({name: 'noname'});
}) &&
runIllegalFunction(chrome.runtime.sendMessage) &&
runIllegalFunction(function() {
chrome.runtime.sendMessage('');
}) &&
runIllegalFunction(function() {
chrome.runtime.sendMessage(42);
}) &&
runIllegalFunction(function() {
chrome.runtime.sendMessage('', 42);
}) &&
sendToBrowser(true);
},
areAnyRuntimePropertiesDefined: function(names) {
var result = false;
if (chrome.runtime) {
forEach.call(names, function(name) {
if (chrome.runtime[name]) {
console.log('runtime.' + name + ' is defined');
result = true;
}
});
}
sendToBrowser(result);
},
getTlsChannelIdFromPortConnect: function(extensionId, includeTlsChannelId,
message) {
if (!checkRuntimeForTlsChannelId())
return;
if (!message)
message = kMessage;
var port = chrome.runtime.connect(extensionId,
{'includeTlsChannelId': includeTlsChannelId});
port.onMessage.addListener(checkTlsChannelIdResponse);
port.postMessage(message);
},
getTlsChannelIdFromSendMessage: function(extensionId, includeTlsChannelId,
message) {
if (!checkRuntimeForTlsChannelId())
return;
if (!message)
message = kMessage;
chrome.runtime.sendMessage(extensionId, message,
{'includeTlsChannelId': includeTlsChannelId},
checkTlsChannelIdResponse);
}
};
}());