зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1174733 - Browser API: iframe.executeScript. r=kanru, r=bholley
This commit is contained in:
Родитель
3ff8c1f3fa
Коммит
dfe7fcd341
|
@ -143,6 +143,12 @@ this.PermissionsTable = { geolocation: {
|
|||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"browser:universalxss": {
|
||||
app: DENY_ACTION,
|
||||
trusted: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
bluetooth: {
|
||||
app: DENY_ACTION,
|
||||
trusted: DENY_ACTION,
|
||||
|
|
|
@ -238,6 +238,7 @@ BrowserElementChild.prototype = {
|
|||
"find-all": this._recvFindAll.bind(this),
|
||||
"find-next": this._recvFindNext.bind(this),
|
||||
"clear-match": this._recvClearMatch.bind(this),
|
||||
"execute-script": this._recvExecuteScript,
|
||||
"get-audio-channel-volume": this._recvGetAudioChannelVolume,
|
||||
"set-audio-channel-volume": this._recvSetAudioChannelVolume,
|
||||
"get-audio-channel-muted": this._recvGetAudioChannelMuted,
|
||||
|
@ -984,6 +985,90 @@ BrowserElementChild.prototype = {
|
|||
takeScreenshotClosure, maxDelayMS);
|
||||
},
|
||||
|
||||
_recvExecuteScript: function(data) {
|
||||
debug("Received executeScript message: (" + data.json.id + ")");
|
||||
|
||||
let domRequestID = data.json.id;
|
||||
|
||||
let sendError = errorMsg => sendAsyncMsg("execute-script-done", {
|
||||
errorMsg,
|
||||
id: domRequestID
|
||||
});
|
||||
|
||||
let sendSuccess = successRv => sendAsyncMsg("execute-script-done", {
|
||||
successRv,
|
||||
id: domRequestID
|
||||
});
|
||||
|
||||
let isJSON = obj => {
|
||||
try {
|
||||
JSON.stringify(obj);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let expectedOrigin = data.json.args.options.origin;
|
||||
let expectedUrl = data.json.args.options.url;
|
||||
|
||||
if (expectedOrigin) {
|
||||
if (expectedOrigin != content.location.origin) {
|
||||
sendError("Origin mismatches");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedUrl) {
|
||||
let expectedURI
|
||||
try {
|
||||
expectedURI = Services.io.newURI(expectedUrl, null, null);
|
||||
} catch(e) {
|
||||
sendError("Malformed URL");
|
||||
return;
|
||||
}
|
||||
let currentURI = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
|
||||
if (!currentURI.equalsExceptRef(expectedURI)) {
|
||||
sendError("URL mismatches");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let sandbox = new Cu.Sandbox([content], {
|
||||
sandboxPrototype: content,
|
||||
sandboxName: "browser-api-execute-script",
|
||||
allowWaivers: false,
|
||||
sameZoneAs: content
|
||||
});
|
||||
|
||||
try {
|
||||
let sandboxRv = Cu.evalInSandbox(data.json.args.script, sandbox, "1.8");
|
||||
if (sandboxRv instanceof Promise) {
|
||||
sandboxRv.then(rv => {
|
||||
if (isJSON(rv)) {
|
||||
sendSuccess(rv);
|
||||
} else {
|
||||
sendError("Value returned (resolve) by promise is not a valid JSON object");
|
||||
}
|
||||
}, error => {
|
||||
if (isJSON(error)) {
|
||||
sendError(error);
|
||||
} else {
|
||||
sendError("Value returned (reject) by promise is not a valid JSON object");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (isJSON(sandboxRv)) {
|
||||
sendSuccess(sandboxRv);
|
||||
} else {
|
||||
sendError("Script last expression must be a promise or a JSON object");
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
sendError(e.toString());
|
||||
}
|
||||
},
|
||||
|
||||
_recvGetContentDimensions: function(data) {
|
||||
debug("Received getContentDimensions message: (" + data.json.id + ")");
|
||||
sendAsyncMsg('got-contentdimensions', {
|
||||
|
|
|
@ -207,6 +207,7 @@ BrowserElementParent.prototype = {
|
|||
"scrollviewchange": this._handleScrollViewChange,
|
||||
"caretstatechanged": this._handleCaretStateChanged,
|
||||
"findchange": this._handleFindChange,
|
||||
"execute-script-done": this._gotDOMRequestResult,
|
||||
"got-audio-channel-volume": this._gotDOMRequestResult,
|
||||
"got-set-audio-channel-volume": this._gotDOMRequestResult,
|
||||
"got-audio-channel-muted": this._gotDOMRequestResult,
|
||||
|
@ -692,6 +693,19 @@ BrowserElementParent.prototype = {
|
|||
this._sendAsyncMsg('stop');
|
||||
}),
|
||||
|
||||
executeScript: function(script, options) {
|
||||
if (!this._isAlive()) {
|
||||
throw Components.Exception("Dead content process",
|
||||
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
// Enforcing options.url or options.origin
|
||||
if (!options.url && !options.origin) {
|
||||
throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
return this._sendDOMRequest('execute-script', {script, options});
|
||||
},
|
||||
|
||||
/*
|
||||
* The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/* Any copyright is dedicated to the public domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Bug 1174733 - Browser API: iframe.executeScript
|
||||
|
||||
'use strict';
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
browserElementTestHelpers.setEnabledPref(true);
|
||||
|
||||
function runTest() {
|
||||
|
||||
const origin = 'http://example.org';
|
||||
const url = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_ExecuteScript.html';
|
||||
|
||||
// Test if all key=>value pairs in o1 are present in o2.
|
||||
const c = (o1, o2) => Object.keys(o1).every(k => o1[k] == o2[k]);
|
||||
|
||||
let scriptId = 0;
|
||||
|
||||
const bail = () => {
|
||||
ok(false, `scriptId: ${scriptId++}`);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'browser', allow: 1, context: document},
|
||||
{type: 'browser:universalxss', allow: 1, context: document}
|
||||
], function() {
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('mozbrowser', 'true');
|
||||
iframe.addEventListener('mozbrowserloadend', function onload() {
|
||||
iframe.removeEventListener('mozbrowserloadend', onload);
|
||||
onReady(iframe);
|
||||
});
|
||||
iframe.src = url;
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
|
||||
|
||||
function onReady(iframe) {
|
||||
iframe.executeScript('4 + 4', {url}).then(rv => {
|
||||
is(rv, 8, `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('(() => {return {a:42}})()', {url})
|
||||
}, bail).then(rv => {
|
||||
ok(c(rv, {a:42}), `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('(() => {return {a:42}})()', {origin})
|
||||
}, bail).then(rv => {
|
||||
ok(c(rv, {a:42}), `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('(() => {return {a:42}})()', {origin, url})
|
||||
}, bail).then(rv => {
|
||||
ok(c(rv, {a:42}), `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
resolve(document.body.textContent.trim());
|
||||
});
|
||||
`, {url})
|
||||
}, bail).then(rv => {
|
||||
is(rv, 'foo', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
resolve({a:43,b:34});
|
||||
});
|
||||
`, {url})
|
||||
}, bail).then(rv => {
|
||||
ok(c(rv, {a:43,b:34}), `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript(`
|
||||
… syntax error
|
||||
`, {url});
|
||||
}, bail).then(bail, (error) => {
|
||||
is(error.name, 'SyntaxError: illegal character', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript(`
|
||||
window
|
||||
`, {url});
|
||||
}).then(bail, (error) => {
|
||||
is(error.name, 'Script last expression must be a promise or a JSON object', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
reject('BOOM');
|
||||
});
|
||||
`, {url});
|
||||
}).then(bail, (error) => {
|
||||
is(error.name, 'BOOM', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
resolve(window);
|
||||
});
|
||||
`, {url});
|
||||
}).then(bail, (error) => {
|
||||
is(error.name, 'Value returned (resolve) by promise is not a valid JSON object', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('window.btoa("a")', {url})
|
||||
}, bail).then(rv => {
|
||||
ok(c(rv, 'YQ=='), `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('window.wrappedJSObject.btoa("a")', {url})
|
||||
}, bail).then(bail, (error) => {
|
||||
is(error.name, 'TypeError: window.wrappedJSObject is undefined', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('42', {})
|
||||
}).then(bail, error => {
|
||||
is(error.name, 'InvalidAccessError', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('42');
|
||||
}).then(bail, error => {
|
||||
is(error.name, 'InvalidAccessError', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('43', { url: 'http://foo.com' });
|
||||
}).then(bail, (error) => {
|
||||
is(error.name, 'URL mismatches', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('43', { url: '_' });
|
||||
}, bail).then(bail, (error) => {
|
||||
is(error.name, 'Malformed URL', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('43', { origin: 'http://foo.com' });
|
||||
}, bail).then(bail, (error) => {
|
||||
is(error.name, 'Origin mismatches', `scriptId: ${scriptId++}`);
|
||||
return iframe.executeScript('43', { origin: 'https://example.org' });
|
||||
}, bail).then(bail, (error) => {
|
||||
is(error.name, 'Origin mismatches', `scriptId: ${scriptId++}`);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('testready', runTest);
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<script>
|
||||
window.btoa = () => "fake btoa";
|
||||
</script>
|
||||
</head>
|
||||
<body>foo</body>
|
||||
</html>
|
|
@ -6,7 +6,9 @@
|
|||
skip-if = os == "android" || (toolkit == "cocoa" && debug) || buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || e10s
|
||||
support-files =
|
||||
browserElement_OpenMixedProcess.js
|
||||
file_browserElement_ExecuteScript.html
|
||||
file_browserElement_OpenMixedProcess.html
|
||||
browserElement_ExecuteScript.js
|
||||
browserElement_Find.js
|
||||
browserElement_OpenTab.js
|
||||
|
||||
|
@ -42,6 +44,7 @@ skip-if = (toolkit == 'gonk' && !debug)
|
|||
disabled = bug 1022281
|
||||
[test_browserElement_oop_ErrorSecurity.html]
|
||||
skip-if = (toolkit == 'gonk' && !debug)
|
||||
[test_browserElement_oop_ExecuteScript.html]
|
||||
[test_browserElement_oop_Find.html]
|
||||
[test_browserElement_oop_FirstPaint.html]
|
||||
[test_browserElement_oop_ForwardName.html]
|
||||
|
|
|
@ -28,6 +28,7 @@ support-files =
|
|||
browserElement_DocumentFirstPaint.js
|
||||
browserElement_Download.js
|
||||
browserElement_ErrorSecurity.js
|
||||
browserElement_ExecuteScript.js
|
||||
browserElement_ExposableURI.js
|
||||
browserElement_Find.js
|
||||
browserElement_FirstPaint.js
|
||||
|
@ -88,6 +89,7 @@ support-files =
|
|||
file_browserElement_CloseFromOpener.html
|
||||
file_browserElement_CookiesNotThirdParty.html
|
||||
file_browserElement_DisallowEmbedAppsInOOP.html
|
||||
file_browserElement_ExecuteScript.html
|
||||
file_browserElement_ForwardName.html
|
||||
file_browserElement_FrameWrongURI.html
|
||||
file_browserElement_LoadEvents.html
|
||||
|
@ -163,6 +165,7 @@ skip-if = os == "android" || toolkit == 'gonk' # embed-apps doesn't work in the
|
|||
[test_browserElement_inproc_DocumentFirstPaint.html]
|
||||
[test_browserElement_inproc_Download.html]
|
||||
disabled = bug 1022281
|
||||
[test_browserElement_inproc_ExecuteScript.html]
|
||||
[test_browserElement_inproc_ExposableURI.html]
|
||||
[test_browserElement_inproc_Find.html]
|
||||
[test_browserElement_inproc_FirstPaint.html]
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1174733
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1163961</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="browserElementTestHelpers.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1174733">Mozilla Bug 1174733</a>
|
||||
|
||||
<script type="application/javascript;version=1.7" src="browserElement_ExecuteScript.js">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1174733
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1163961</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="browserElementTestHelpers.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1174733">Mozilla Bug 1174733</a>
|
||||
|
||||
<script type="application/javascript;version=1.7" src="browserElement_ExecuteScript.js">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -26,7 +26,7 @@ interface nsIBrowserElementNextPaintListener : nsISupports
|
|||
* Interface to the BrowserElementParent implementation. All methods
|
||||
* but setFrameLoader throw when the remote process is dead.
|
||||
*/
|
||||
[scriptable, uuid(daa264b2-54df-4fc7-89b7-c9d02167c5d4)]
|
||||
[scriptable, uuid(26a832d1-9d71-43ef-9d46-9d7c8ec33f00)]
|
||||
interface nsIBrowserElementAPI : nsISupports
|
||||
{
|
||||
const long FIND_CASE_SENSITIVE = 0;
|
||||
|
@ -91,4 +91,6 @@ interface nsIBrowserElementAPI : nsISupports
|
|||
nsIDOMDOMRequest isAudioChannelActive(in uint32_t audioChannel);
|
||||
|
||||
void setNFCFocus(in boolean isFocus);
|
||||
|
||||
nsIDOMDOMRequest executeScript(in DOMString script, in jsval options);
|
||||
};
|
||||
|
|
|
@ -678,4 +678,59 @@ nsBrowserElement::SetNFCFocus(bool aIsFocus,
|
|||
}
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::ExecuteScript(const nsAString& aScript,
|
||||
const BrowserElementExecuteScriptOptions& aOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
|
||||
|
||||
nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
|
||||
if (!frameLoader) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMElement> ownerElement;
|
||||
nsresult rv = frameLoader->GetOwnerElement(getter_AddRefs(ownerElement));
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsINode> node = do_QueryInterface(ownerElement);
|
||||
nsCOMPtr<nsIPrincipal> principal = node->NodePrincipal();
|
||||
|
||||
if (!nsContentUtils::IsExactSitePermAllow(principal, "browser:universalxss")) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(mBrowserElementAPI);
|
||||
MOZ_ASSERT(wrappedObj, "Failed to get wrapped JS from XPCOM component.");
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init(wrappedObj->GetJSObject());
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::Rooted<JS::Value> options(cx);
|
||||
if (!ToJSValue(cx, aOptions, &options)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rv = mBrowserElementAPI->ExecuteScript(aScript, options, getter_AddRefs(req));
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
if (rv == NS_ERROR_INVALID_ARG) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace mozilla {
|
|||
|
||||
namespace dom {
|
||||
struct BrowserElementDownloadOptions;
|
||||
struct BrowserElementExecuteScriptOptions;
|
||||
class BrowserElementNextPaintEventCallback;
|
||||
class DOMRequest;
|
||||
enum class BrowserFindCaseSensitivity: uint32_t;
|
||||
|
@ -100,6 +101,10 @@ public:
|
|||
already_AddRefed<dom::DOMRequest> SetInputMethodActive(bool isActive,
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest> ExecuteScript(const nsAString& aScript,
|
||||
const dom::BrowserElementExecuteScriptOptions& aOptions,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void SetNFCFocus(bool isFocus,
|
||||
ErrorResult& aRv);
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ dictionary BrowserElementDownloadOptions {
|
|||
DOMString? referrer;
|
||||
};
|
||||
|
||||
dictionary BrowserElementExecuteScriptOptions {
|
||||
DOMString? url;
|
||||
DOMString? origin;
|
||||
};
|
||||
|
||||
[NoInterfaceObject]
|
||||
interface BrowserElement {
|
||||
};
|
||||
|
@ -165,4 +170,11 @@ interface BrowserElementPrivileged {
|
|||
CheckPermissions="browser"]
|
||||
void clearMatch();
|
||||
|
||||
// Additional |browser:universalxss| permission is required for executeScript API
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest executeScript(DOMString script,
|
||||
optional BrowserElementExecuteScriptOptions options);
|
||||
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче