author | Dave Townsend <dtownsend@oxymoronical.com> |
Fri, 19 Feb 2016 16:49:30 -0800 | |
changeset 330926 | a01d14eb9caf57b73ede38fcfe3620e42f9b32db |
parent 330925 | 6f51002d4589638e0120681f8de5512c48bb5155 |
child 330927 | 64cba8d398a38470ece1fe50be8210fff82fc272 |
push id | 6048 |
push user | kmoir@mozilla.com |
push date | Mon, 06 Jun 2016 19:02:08 +0000 |
treeherder | mozilla-beta@46d72a56c57d [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bz |
bugs | 1245571 |
milestone | 48.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100644 --- /dev/null +++ b/dom/webidl/AddonManager.webidl @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* We need a JSImplementation but cannot get one without a contract ID. Since + This object is only ever created from JS we don't need a real contract ID. */ +[ChromeOnly, JSImplementation="dummy"] +interface Addon { + // The add-on's ID. + readonly attribute DOMString id; + // The add-on's version. + readonly attribute DOMString version; + // The add-on's type (extension, theme, etc.). + readonly attribute DOMString type; + // The add-on's name in the current locale. + readonly attribute DOMString name; + // The add-on's description in the current locale. + readonly attribute DOMString description; + // If the user has enabled this add-on, note that it still may not be running + // depending on whether enabling requires a restart or if the add-on is + // incompatible in some way. + readonly attribute boolean isEnabled; + // If the add-on is currently active in the browser. + readonly attribute boolean isActive; +}; + +[HeaderFile="mozilla/AddonManagerWebAPI.h", + Func="mozilla::AddonManagerWebAPI::IsAPIEnabled", + NavigatorProperty="mozAddonManager", + JSImplementation="@mozilla.org/addon-web-api/manager;1"] +interface AddonManager { + /** + * Gets information about an add-on + * + * @param id + * The ID of the add-on to test for. + * @return A promise. It will resolve to an Addon if the add-on is installed. + */ + Promise<Addon> getAddonByID(DOMString id); +};
--- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -15,16 +15,17 @@ PREPROCESSED_WEBIDL_FILES = [ 'PromiseDebugging.webidl', 'ServiceWorkerRegistration.webidl', 'Window.webidl', ] WEBIDL_FILES = [ 'AbstractWorker.webidl', 'ActivityRequestHandler.webidl', + 'AddonManager.webidl', 'AnalyserNode.webidl', 'Animatable.webidl', 'Animation.webidl', 'AnimationEffectReadOnly.webidl', 'AnimationEffectTiming.webidl', 'AnimationEffectTimingReadOnly.webidl', 'AnimationEvent.webidl', 'AnimationTimeline.webidl',
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonManagerWebAPI.h" + +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/NavigatorBinding.h" + +#include "mozilla/Preferences.h" +#include "nsGlobalWindow.h" + +#include "nsIDocShell.h" +#include "nsIScriptObjectPrincipal.h" + +namespace mozilla { +using namespace mozilla::dom; + +// Checks if the given uri is secure and matches one of the hosts allowed to +// access the API. +bool +AddonManagerWebAPI::IsValidSite(nsIURI* uri) +{ + if (!uri) { + return false; + } + + bool isSecure; + nsresult rv = uri->SchemeIs("https", &isSecure); + if (NS_FAILED(rv) || !isSecure) { + return false; + } + + nsCString host; + rv = uri->GetHost(host); + if (NS_FAILED(rv)) { + return false; + } + + if (host.Equals("addons.mozilla.org") || + host.Equals("services.addons.mozilla.org")) { + return true; + } + + // When testing allow access to the developer sites. + if (Preferences::GetBool("extensions.webapi.testing", false)) { + if (host.Equals("addons.allizom.org") || + host.Equals("services.addons.allizom.org") || + host.Equals("addons-dev.allizom.org") || + host.Equals("services.addons-dev.allizom.org") || + host.Equals("example.com")) { + return true; + } + } + + return false; +} + +bool +AddonManagerWebAPI::IsAPIEnabled(JSContext* cx, JSObject* obj) +{ + nsGlobalWindow* global = xpc::WindowGlobalOrNull(obj); + if (!global) { + return false; + } + + nsCOMPtr<nsPIDOMWindowInner> win = global->AsInner(); + if (!win) { + return false; + } + + // Check that the current window and all parent frames are allowed access to + // the API. + while (win) { + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(win); + if (!sop) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); + if (!principal) { + return false; + } + + // Reaching a window with a system principal means we have reached + // privileged UI of some kind so stop at this point and allow access. + if (principal->GetIsSystemPrincipal()) { + return true; + } + + nsCOMPtr<nsIDocShell> docShell = win->GetDocShell(); + if (!docShell) { + // This window has been torn down so don't allow access to the API. + return false; + } + + if (!IsValidSite(win->GetDocumentURI())) { + return false; + } + + // Checks whether there is a parent frame of the same type. This won't cross + // mozbrowser or chrome boundaries. + nsCOMPtr<nsIDocShellTreeItem> parent; + nsresult rv = docShell->GetSameTypeParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) { + return false; + } + + if (!parent) { + // No parent means we've hit a mozbrowser or chrome boundary so allow + // access to the API. + return true; + } + + nsIDocument* doc = win->GetDoc(); + if (!doc) { + return false; + } + + doc = doc->GetParentDocument(); + if (!doc) { + // Getting here means something has been torn down so fail safe. + return false; + } + + + win = doc->GetInnerWindow(); + } + + // Found a document with no inner window, don't grant access to the API. + return false; +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/AddonManagerWebAPI.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPIDOMWindow.h" + +namespace mozilla { + +class AddonManagerWebAPI { +public: + static bool IsAPIEnabled(JSContext* cx, JSObject* obj); + +private: + static bool IsValidSite(nsIURI* uri); +}; + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/amWebAPI.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +function WebAPI() { +} + +WebAPI.prototype = { + init(window) { + this.window = window; + }, + + getAddonByID(id) { + return this.window.Promise.reject("Not yet implemented"); + }, + + classID: Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"), + contractID: "@mozilla.org/addon-web-api/manager;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer]) +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]);
--- a/toolkit/mozapps/extensions/extensions.manifest +++ b/toolkit/mozapps/extensions/extensions.manifest @@ -18,8 +18,10 @@ contract @mozilla.org/addons/web-install component {9df8ef2b-94da-45c9-ab9f-132eb55fddf1} amInstallTrigger.js contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fddf1} category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1 #ifndef MOZ_WIDGET_ANDROID category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm #endif category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm #endif +component {8866d8e3-4ea5-48b7-a891-13ba0ac15235} amWebAPI.js +contract @mozilla.org/addon-web-api/manager;1 {8866d8e3-4ea5-48b7-a891-13ba0ac15235}
--- a/toolkit/mozapps/extensions/moz.build +++ b/toolkit/mozapps/extensions/moz.build @@ -18,16 +18,17 @@ XPIDL_SOURCES += [ ] XPIDL_MODULE = 'extensions' EXTRA_COMPONENTS += [ 'addonManager.js', 'amContentHandler.js', 'amInstallTrigger.js', + 'amWebAPI.js', 'amWebInstallListener.js', 'nsBlocklistService.js', 'nsBlocklistServiceContent.js', ] EXTRA_PP_COMPONENTS += [ 'extensions.manifest', ] @@ -38,20 +39,26 @@ EXTRA_JS_MODULES += [ 'DeferredSave.jsm', 'LightweightThemeManager.jsm', ] JAR_MANIFESTS += ['jar.mn'] EXPORTS.mozilla += [ 'AddonContentPolicy.h', + 'AddonManagerWebAPI.h', 'AddonPathService.h', ] UNIFIED_SOURCES += [ 'AddonContentPolicy.cpp', + 'AddonManagerWebAPI.cpp', 'AddonPathService.cpp', ] +LOCAL_INCLUDES += [ + '/dom/base', +] + FINAL_LIBRARY = 'xul' with Files('**'): BUG_COMPONENT = ('Toolkit', 'Add-ons Manager')
--- a/toolkit/mozapps/extensions/test/browser/browser.ini +++ b/toolkit/mozapps/extensions/test/browser/browser.ini @@ -32,16 +32,20 @@ support-files = browser_updatessl.rdf browser_updatessl.rdf^headers^ browser_install.rdf browser_install.rdf^headers^ browser_install.xml browser_install1_3.xpi browser_eula.xml browser_purchase.xml + webapi_checkavailable.html + webapi_checkchromeframe.xul + webapi_checkframed.html + webapi_checknavigatedwindow.html !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi !/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi !/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi @@ -55,10 +59,11 @@ support-files = # Verifies the old style of signing hotfixes skip-if = require_signing [browser_installssl.js] [browser_newaddon.js] [browser_updatessl.js] [browser_task_next_test.js] [browser_discovery_install.js] [browser_update.js] +[browser_webapi_access.js] [include:browser-common.ini]
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_access.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("extensions.webapi.testing"); +}); + +function check_frame_availability(browser) { + return ContentTask.spawn(browser, null, function*() { + let frame = content.document.getElementById("frame"); + return frame.contentWindow.document.getElementById("result").textContent == "true"; + }); +} + +function check_availability(browser) { + return ContentTask.spawn(browser, null, function*() { + return content.document.getElementById("result").textContent == "true"; + }); +} + +// Test that initially the API isn't available in the test domain +add_task(function* test_not_available() { + yield BrowserTestUtils.withNewTab(`${SECURE_TESTROOT}webapi_checkavailable.html`, + function* test_not_available(browser) { + let available = yield check_availability(browser); + ok(!available, "API should not be available."); + }) +}); + +// Test that with testing on the API is available in the test domain +add_task(function* test_available() { + Services.prefs.setBoolPref("extensions.webapi.testing", true); + + yield BrowserTestUtils.withNewTab(`${SECURE_TESTROOT}webapi_checkavailable.html`, + function* test_not_available(browser) { + let available = yield check_availability(browser); + ok(available, "API should be available."); + }) +}); + +// Test that the API is not available in a bad domain +add_task(function* test_bad_domain() { + yield BrowserTestUtils.withNewTab(`${SECURE_TESTROOT2}webapi_checkavailable.html`, + function* test_not_available(browser) { + let available = yield check_availability(browser); + ok(!available, "API should not be available."); + }) +}); + +// Test that the API is only available in https sites +add_task(function* test_not_available_http() { + yield BrowserTestUtils.withNewTab(`${TESTROOT}webapi_checkavailable.html`, + function* test_not_available(browser) { + let available = yield check_availability(browser); + ok(!available, "API should not be available."); + }) +}); + +// Test that the API is available when in a frame of the test domain +add_task(function* test_available_framed() { + yield BrowserTestUtils.withNewTab(`${SECURE_TESTROOT}webapi_checkframed.html`, + function* test_available(browser) { + let available = yield check_frame_availability(browser); + ok(available, "API should be available."); + }) +}); + +// Test that if the external frame is http then the inner frame doesn't have +// the API +add_task(function* test_not_available_http_framed() { + yield BrowserTestUtils.withNewTab(`${TESTROOT}webapi_checkframed.html`, + function* test_not_available(browser) { + let available = yield check_frame_availability(browser); + ok(!available, "API should not be available."); + }) +}); + +// Test that if the external frame is a bad domain then the inner frame doesn't +// have the API +add_task(function* test_not_available_framed() { + yield BrowserTestUtils.withNewTab(`${SECURE_TESTROOT2}webapi_checkframed.html`, + function* test_not_available(browser) { + let available = yield check_frame_availability(browser); + ok(!available, "API should not be available."); + }) +}); + +// Test that a window navigated to a bad domain doesn't allow access to the API +add_task(function* test_navigated_window() { + yield BrowserTestUtils.withNewTab(`${SECURE_TESTROOT2}webapi_checknavigatedwindow.html`, + function* test_available(browser) { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + + yield ContentTask.spawn(browser, null, function*() { + yield content.wrappedJSObject.openWindow(); + }); + + // Should be a new tab open + let tab = yield tabPromise; + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab)); + + ContentTask.spawn(browser, null, function*() { + content.wrappedJSObject.navigate(); + }); + + yield loadPromise; + + let available = yield ContentTask.spawn(browser, null, function*() { + return content.wrappedJSObject.check(); + }); + + ok(!available, "API should not be available."); + + gBrowser.removeTab(tab); + }) +}); + +// Check that if a page is embedded in a chrome content UI that it can still +// access the API. +add_task(function* test_chrome_frame() { + yield BrowserTestUtils.withNewTab(`${CHROMEROOT}webapi_checkchromeframe.xul`, + function* test_available(browser) { + let available = yield check_frame_availability(browser); + ok(available, "API should be available."); + }) +});
--- a/toolkit/mozapps/extensions/test/browser/head.js +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -21,17 +21,19 @@ var gTestInWindow = /-window$/.test(path // Drop the UI type if (gTestInWindow) { pathParts.splice(pathParts.length - 1, pathParts.length); } const RELATIVE_DIR = pathParts.slice(4).join("/") + "/"; const TESTROOT = "http://example.com/" + RELATIVE_DIR; +const SECURE_TESTROOT = "https://example.com/" + RELATIVE_DIR; const TESTROOT2 = "http://example.org/" + RELATIVE_DIR; +const SECURE_TESTROOT2 = "https://example.org/" + RELATIVE_DIR; const CHROMEROOT = pathParts.join("/") + "/"; const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; const PREF_XPI_ENABLED = "xpinstall.enabled"; const PREF_UPDATEURL = "extensions.update.url"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; const PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI = "xpinstall.customConfirmationUI"; const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> + +<html> +<body> +<p id="result"></p> +<script type="text/javascript"> +document.getElementById("result").textContent = ("mozAddonManager" in window.navigator); +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/webapi_checkchromeframe.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <browser id="frame" disablehistory="true" flex="1" type="content" + src="https://example.com/browser/toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html"/> +</window>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/webapi_checkframed.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> + +<html> +<body> +<iframe id="frame" height="200" width="200" src="https://example.com/browser/toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html"> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/webapi_checknavigatedwindow.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> + +<html> +<body> +<script type="text/javascript"> +var nav, win; + +function openWindow() { + return new Promise(resolve => { + win = window.open(window.location); + + win.addEventListener("load", function listener() { + nav = win.navigator; + resolve(); + }, false); + }); +} + +function navigate() { + win.location = "http://example.com/"; +} + +function check() { + return "mozAddonManager" in nav; +} +</script> +</body> +</html>