From d6b969e27562e8deec28d7171470729825851666 Mon Sep 17 00:00:00 2001 From: Clint Talbert Date: Wed, 22 Dec 2010 08:39:29 -0800 Subject: [PATCH] Bug 582472 - Add a special powers object to remove enable privilege r=ted,smaug a=test-only --- testing/mochitest/Makefile.in | 16 +- testing/mochitest/runtests.py | 17 ++ testing/mochitest/specialpowers/Makefile.in | 65 +++++++ .../mochitest/specialpowers/chrome.manifest | 4 + .../components/SpecialPowersObserver.js | 160 ++++++++++++++++++ .../specialpowers/content/specialpowers.js | 144 ++++++++++++++++ testing/mochitest/specialpowers/install.rdf | 26 +++ testing/mochitest/specialpowers/jar.mn | 3 + testing/mochitest/tests/Makefile.in | 7 +- .../tests/file_SpecialPowersFrame1.html | 15 ++ .../tests/test_SpecialPowersExtension.html | 46 +++++ .../tests/test_SpecialPowersExtension2.html | 21 +++ 12 files changed, 515 insertions(+), 9 deletions(-) create mode 100644 testing/mochitest/specialpowers/Makefile.in create mode 100644 testing/mochitest/specialpowers/chrome.manifest create mode 100644 testing/mochitest/specialpowers/components/SpecialPowersObserver.js create mode 100644 testing/mochitest/specialpowers/content/specialpowers.js create mode 100644 testing/mochitest/specialpowers/install.rdf create mode 100644 testing/mochitest/specialpowers/jar.mn create mode 100644 testing/mochitest/tests/file_SpecialPowersFrame1.html create mode 100644 testing/mochitest/tests/test_SpecialPowersExtension.html create mode 100644 testing/mochitest/tests/test_SpecialPowersExtension2.html diff --git a/testing/mochitest/Makefile.in b/testing/mochitest/Makefile.in index 05b34a54c479..19418630b981 100644 --- a/testing/mochitest/Makefile.in +++ b/testing/mochitest/Makefile.in @@ -43,13 +43,15 @@ relativesrcdir = testing/mochitest include $(DEPTH)/config/autoconf.mk -DIRS = MochiKit \ - static \ - dynamic \ - tests \ - chrome \ - ssltunnel \ - $(NULL) +DIRS = \ + MochiKit \ + static \ + dynamic \ + tests \ + chrome \ + ssltunnel \ + specialpowers \ + $(NULL) NO_JS_MANIFEST = 1 diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py index c9cea7cdb4ca..e97aea55e414 100644 --- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -463,11 +463,27 @@ class Mochitest(object): """ return self.getFullPath(logFile) + def installSpecialPowersExtension(self, options): + """ install the Special Powers extension for special testing capabilities """ + extensionSource = os.path.normpath(os.path.join(self.SCRIPT_DIRECTORY, "specialpowers")) + self.automation.log.info("INFO | runtests.py | Installing extension at %s to %s." % + (extensionSource, options.profilePath)) + + self.automation.installExtension(extensionSource, options.profilePath, "special-powers@mozilla.org") + self.automation.log.info("INFO | runtests.py | Done installing extension.") + def buildProfile(self, options): """ create the profile and add optional chrome bits and files if requested """ self.automation.initializeProfile(options.profilePath, options.extraPrefs, useServerLocations = True) manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) + + # We only need special powers in non-chrome harnesses + if (not options.browserChrome and + not options.chrome and + not options.a11y): + self.installSpecialPowersExtension(options) + return manifest def buildBrowserEnv(self, options): @@ -579,6 +595,7 @@ class Mochitest(object): manifest = self.buildProfile(options) if manifest is None: return 1 + self.startWebServer(options) self.startWebSocketServer(options) diff --git a/testing/mochitest/specialpowers/Makefile.in b/testing/mochitest/specialpowers/Makefile.in new file mode 100644 index 000000000000..a9c6ef824439 --- /dev/null +++ b/testing/mochitest/specialpowers/Makefile.in @@ -0,0 +1,65 @@ +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Mozilla Foundation +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = testing/mochitest/specialpowers + +include $(DEPTH)/config/autoconf.mk + +NO_JS_MANIFEST = 1 +MOZ_CHROME_FILE_FORMAT = flat +DIST_FILES = \ + install.rdf \ + chrome.manifest \ + $(NULL) + +EXTRA_COMPONENTS = components/SpecialPowersObserver.js + +XPI_NAME=specialpowers + +DEST_DIR=testing/mochitest + +# Used in install.rdf +USE_EXTENSION_MANIFEST=1 + +include $(topsrcdir)/config/rules.mk + +libs:: + (cd $(DIST)/xpi-stage && tar $(TAR_CREATE_FLAGS) - specialpowers) | (cd $(DEPTH)/_tests/$(DEST_DIR) && tar -xf -) diff --git a/testing/mochitest/specialpowers/chrome.manifest b/testing/mochitest/specialpowers/chrome.manifest new file mode 100644 index 000000000000..614f31c3a8b7 --- /dev/null +++ b/testing/mochitest/specialpowers/chrome.manifest @@ -0,0 +1,4 @@ +content specialpowers chrome/specialpowers/content/ +component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js +contract @mozilla.org/special-powers-observer;1 {59a52458-13e0-4d93-9d85-a637344f29a1} +category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1 diff --git a/testing/mochitest/specialpowers/components/SpecialPowersObserver.js b/testing/mochitest/specialpowers/components/SpecialPowersObserver.js new file mode 100644 index 000000000000..9161f2b95ca4 --- /dev/null +++ b/testing/mochitest/specialpowers/components/SpecialPowersObserver.js @@ -0,0 +1,160 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Special Powers code + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jesse Ruderman + * Robert Sayre + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK *****/ + +// Based on: +// https://bugzilla.mozilla.org/show_bug.cgi?id=549539 +// https://bug549539.bugzilla.mozilla.org/attachment.cgi?id=429661 +// https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_1.9.3 +// http://mxr.mozilla.org/mozilla-central/source/toolkit/components/console/hudservice/HUDService.jsm#3240 +// https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +const Cc = Components.classes; +const Ci = Components.interfaces; + +const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js" + +/** + * Special Powers Exception - used to throw exceptions nicely + **/ +function SpecialPowersException(aMsg) { + this.message = aMsg; + this.name = "SpecialPowersException"; +} + +SpecialPowersException.prototype.toString = function() { + return this.name + ': "' + this.message + '"'; +}; + +/* XPCOM gunk */ +function SpecialPowersObserver() {} + +SpecialPowersObserver.prototype = { + classDescription: "Special powers Observer for use in testing.", + classID: Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}"), + contractID: "@mozilla.org/special-powers-observer;1", + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]), + _xpcom_categories: [{category: "profile-after-change", service: true }], + isFrameScriptLoaded: false, + + observe: function(aSubject, aTopic, aData) + { + if (aTopic == "profile-after-change") { + this.init(); + } else if (!this.isFrameScriptLoaded && + aTopic == "chrome-document-global-created") { + + var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]. + getService(Ci.nsIChromeFrameMessageManager); + // Register for any messages our API needs us to handle + messageManager.addMessageListener("SPPrefService", this); + + messageManager.loadFrameScript(CHILD_SCRIPT, true); + this.isFrameScriptLoaded = true; + } else if (aTopic == "xpcom-shutdown") { + this.uninit(); + } + }, + + init: function() + { + var obs = Services.obs; + obs.addObserver(this, "xpcom-shutdown", false); + obs.addObserver(this, "chrome-document-global-created", false); + }, + + uninit: function() + { + var obs = Services.obs; + obs.removeObserver(this, "chrome-document-global-created", false); + }, + + /** + * messageManager callback function + * This will get requests from our API in the window and process them in chrome for it + **/ + receiveMessage: function(aMessage) { + switch(aMessage.name) { + case "SPPrefService": + var prefs = Services.prefs; + var prefType = aMessage.json.prefType.toUpperCase(); + var prefName = aMessage.json.prefName; + var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null; + + if (aMessage.json.op == "get") { + if (!prefName || !prefType) + throw new SpecialPowersException("Invalid parameters for get in SPPrefService"); + } else if (aMessage.json.op == "set") { + if (!prefName || !prefType || prefValue === null) + throw new SpecialPowersException("Invalid parameters for set in SPPrefService"); + } else { + throw new SpecialPowersException("Invalid operation for SPPrefService"); + } + // Now we make the call + switch(prefType) { + case "BOOL": + if (aMessage.json.op == "get") + return(prefs.getBoolPref(prefName)); + else + return(prefs.setBoolPref(prefName, prefValue)); + case "INT": + if (aMessage.json.op == "get") + return(prefs.getIntPref(prefName)); + else + return(prefs.setIntPref(prefName, prefValue)); + case "CHAR": + if (aMessage.json.op == "get") + return(prefs.getCharPref(prefName)); + else + return(prefs.setCharPref(prefName, prefValue)); + case "COMPLEX": + if (aMessage.json.op == "get") + return(prefs.getComplexValue(prefName, prefValue[0])); + else + return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1])); + } + break; + default: + throw new SpecialPowersException("Unrecognized Special Powers API"); + } + } +}; + +const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]); diff --git a/testing/mochitest/specialpowers/content/specialpowers.js b/testing/mochitest/specialpowers/content/specialpowers.js new file mode 100644 index 000000000000..4e454a5b350e --- /dev/null +++ b/testing/mochitest/specialpowers/content/specialpowers.js @@ -0,0 +1,144 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Special Powers code + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Clint Talbert cmtalbert@gmail.com + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK *****/ +/* This code is loaded in every child process that is started by mochitest in + * order to be used as a replacement for UniversalXPConnect + */ +function SpecialPowers() {} + +var SpecialPowers = { + sanityCheck: function() { return "foo"; }, + + // Mimic the get*Pref API + getBoolPref: function(aPrefName) { + return (this._getPref(aPrefName, 'BOOL')); + }, + getIntPref: function(aPrefName) { + return (this._getPref(aPrefName, 'INT')); + }, + getCharPref: function(aPrefName) { + return (this._getPref(aPrefName, 'CHAR')); + }, + getComplexValue: function(aPrefName, aIid) { + return (this._getPref(aPrefName, 'COMPLEX', aIid)); + }, + + // Mimic the set*Pref API + setBoolPref: function(aPrefName, aValue) { + return (this._setPref(aPrefName, 'BOOL', aValue)); + }, + setIntPref: function(aPrefName, aValue) { + return (this._setPref(aPrefName, 'INT', aValue)); + }, + setCharPref: function(aPrefName, aValue) { + return (this._setPref(aPrefName, 'CHAR', aValue)); + }, + setComplexValue: function(aPrefName, aIid, aValue) { + return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid)); + }, + + // Private pref functions to communicate to chrome + _getPref: function(aPrefName, aPrefType, aIid) { + var msg = {}; + if (aIid) { + // Overloading prefValue to handle complex prefs + msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]}; + } else { + msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType}; + } + return(sendSyncMessage('SPPrefService', msg)[0]); + }, + _setPref: function(aPrefName, aPrefType, aValue, aIid) { + var msg = {}; + if (aIid) { + msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]}; + } else { + msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue}; + } + return(sendSyncMessage('SPPrefService', msg)[0]); + } +} + +// Attach our API to the window +function attachSpecialPwrToWindow(aSubject) { + try { + if ((aSubject !== null) && + (aSubject !== undefined) && + (aSubject.wrappedJSObject) && + !(aSubject.wrappedJSObject.SpecialPowers)) { + aSubject.wrappedJSObject.SpecialPowers = SpecialPowers; + } + } catch(ex) { + dump("TEST-INFO | specialpowers.js | Failed to attach specialpowers to window exception: " + ex + "\n"); + } +} + +// In true IPC, this loads in the child process so we need our own observer here +// to ensure we actually get attached, otherwise we'll miss content-document-global-created +// notifications +// NOTE: The observers are GC'd when the window dies, so while this looks like it should +// leak, it actually doesn't. And if you add in an observer for dom-window-destroyed or +// xpcom-shutdown and upon capturing either of those notifications you unregister the +// content-document-global-created observer, then in the child process you will never +// be able to capture another content-document-global-created on the next page load. Essentially, +// this is registered inside the child process once by our SpecialPowersObserver, and after that, +// we will no longer trip that chrome code again. +function frameScriptObserver() { + // Then we need to register the observers + this.register(); +} + +frameScriptObserver.prototype = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "content-document-global-created") { + attachSpecialPwrToWindow(aSubject); + } + }, + register: function() { + var obsSvc = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + obsSvc.addObserver(this, "content-document-global-created", false); + }, + unregister: function() { + var obsSvc = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + obsSvc.removeObserver(this, "content-document-global-created"); + } +}; + +// VERY IMPORTANT: Only add observers if they are needed +if (content && !content.wrappedJSObject.SpecialPowers) + var frameScriptObsv = new frameScriptObserver(); diff --git a/testing/mochitest/specialpowers/install.rdf b/testing/mochitest/specialpowers/install.rdf new file mode 100644 index 000000000000..bb459cfe6b63 --- /dev/null +++ b/testing/mochitest/specialpowers/install.rdf @@ -0,0 +1,26 @@ + + + + + + special-powers@mozilla.org + 2010.07.23 + 2 + + + + + toolkit@mozilla.org +#expand __MOZILLA_VERSION_U__ +#expand __MOZILLA_VERSION_U__ + + + + + Special Powers + Special powers for use in testing. + Mozilla + + diff --git a/testing/mochitest/specialpowers/jar.mn b/testing/mochitest/specialpowers/jar.mn new file mode 100644 index 000000000000..dbb5bee144dd --- /dev/null +++ b/testing/mochitest/specialpowers/jar.mn @@ -0,0 +1,3 @@ +specialpowers.jar: +% content specialpowers %content/ + content/specialpowers.js (content/specialpowers.js) diff --git a/testing/mochitest/tests/Makefile.in b/testing/mochitest/tests/Makefile.in index fb9b88de5898..10f6736fcc4a 100644 --- a/testing/mochitest/tests/Makefile.in +++ b/testing/mochitest/tests/Makefile.in @@ -53,8 +53,11 @@ PARALLEL_DIRS = \ include $(topsrcdir)/config/rules.mk _TEST_FILES = \ - test_sanity.html \ - $(NULL) + test_sanity.html \ + test_SpecialPowersExtension.html \ + test_SpecialPowersExtension2.html \ + file_SpecialPowersFrame1.html \ + $(NULL) # Copy the sanity tests into a subdirectory, so the top level is all dirs # in the test screen. diff --git a/testing/mochitest/tests/file_SpecialPowersFrame1.html b/testing/mochitest/tests/file_SpecialPowersFrame1.html new file mode 100644 index 000000000000..75ba07347d42 --- /dev/null +++ b/testing/mochitest/tests/file_SpecialPowersFrame1.html @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/testing/mochitest/tests/test_SpecialPowersExtension.html b/testing/mochitest/tests/test_SpecialPowersExtension.html new file mode 100644 index 000000000000..0893aebe678a --- /dev/null +++ b/testing/mochitest/tests/test_SpecialPowersExtension.html @@ -0,0 +1,46 @@ + + + + Test for SpecialPowers extension + + + + + + + + +
+
+
+ + + diff --git a/testing/mochitest/tests/test_SpecialPowersExtension2.html b/testing/mochitest/tests/test_SpecialPowersExtension2.html new file mode 100644 index 000000000000..041a0fb120e2 --- /dev/null +++ b/testing/mochitest/tests/test_SpecialPowersExtension2.html @@ -0,0 +1,21 @@ + + + + Test for SpecialPowers extension + + + + + + + +
+ + +
+ +