diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore new file mode 100644 index 000000000..ef853ef61 --- /dev/null +++ b/test/pdfs/.gitignore @@ -0,0 +1 @@ +pdf.pdf diff --git a/tests/canvas.pdf b/test/pdfs/canvas.pdf similarity index 100% rename from tests/canvas.pdf rename to test/pdfs/canvas.pdf diff --git a/tests/pdf.pdf.link b/test/pdfs/pdf.pdf.link similarity index 100% rename from tests/pdf.pdf.link rename to test/pdfs/pdf.pdf.link diff --git a/tests/tracemonkey.pdf b/test/pdfs/tracemonkey.pdf similarity index 100% rename from tests/tracemonkey.pdf rename to test/pdfs/tracemonkey.pdf diff --git a/test/resources/browser_manifests/browser_manifest.json.mac b/test/resources/browser_manifests/browser_manifest.json.mac new file mode 100644 index 000000000..7c9dda943 --- /dev/null +++ b/test/resources/browser_manifests/browser_manifest.json.mac @@ -0,0 +1,10 @@ +[ + { + "name":"firefox5", + "path":"/Applications/Firefox.app" + }, + { + "name":"firefox6", + "path":"/Users/sayrer/firefoxen/Aurora.app" + } +] diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest new file mode 100644 index 000000000..614f31c3a --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/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/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js new file mode 100644 index 000000000..538b104eb --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js @@ -0,0 +1,372 @@ +/* ***** 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 + */ + +var Ci = Components.interfaces; +var Cc = Components.classes; + +function SpecialPowers(window) { + this.window = window; + bindDOMWindowUtils(this, window); + this._encounteredCrashDumpFiles = []; + this._unexpectedCrashDumpFiles = { }; + this._crashDumpDir = null; + this._pongHandlers = []; + this._messageListener = this._messageReceived.bind(this); + addMessageListener("SPPingService", this._messageListener); +} + +function bindDOMWindowUtils(sp, window) { + var util = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // This bit of magic brought to you by the letters + // B Z, and E, S and the number 5. + // + // Take all of the properties on the nsIDOMWindowUtils-implementing + // object, and rebind them onto a new object with a stub that uses + // apply to call them from this privileged scope. This way we don't + // have to explicitly stub out new methods that appear on + // nsIDOMWindowUtils. + var proto = Object.getPrototypeOf(util); + var target = {}; + function rebind(desc, prop) { + if (prop in desc && typeof(desc[prop]) == "function") { + var oldval = desc[prop]; + desc[prop] = function() { return oldval.apply(util, arguments); }; + } + } + for (var i in proto) { + var desc = Object.getOwnPropertyDescriptor(proto, i); + rebind(desc, "get"); + rebind(desc, "set"); + rebind(desc, "value"); + Object.defineProperty(target, i, desc); + } + sp.DOMWindowUtils = target; +} + +SpecialPowers.prototype = { + toString: function() { return "[SpecialPowers]"; }, + sanityCheck: function() { return "foo"; }, + + // This gets filled in in the constructor. + DOMWindowUtils: undefined, + + // 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)); + }, + + // Mimic the clearUserPref API + clearUserPref: function(aPrefName) { + var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""}; + sendSyncMessage('SPPrefService', msg); + }, + + // 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]); + }, + + //XXX: these APIs really ought to be removed, they're not e10s-safe. + // (also they're pretty Firefox-specific) + _getTopChromeWindow: function(window) { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow) + .QueryInterface(Ci.nsIDOMChromeWindow); + }, + _getDocShell: function(window) { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + }, + _getMUDV: function(window) { + return this._getDocShell(window).contentViewer + .QueryInterface(Ci.nsIMarkupDocumentViewer); + }, + _getAutoCompletePopup: function(window) { + return this._getTopChromeWindow(window).document + .getElementById("PopupAutoComplete"); + }, + addAutoCompletePopupEventListener: function(window, listener) { + this._getAutoCompletePopup(window).addEventListener("popupshowing", + listener, + false); + }, + removeAutoCompletePopupEventListener: function(window, listener) { + this._getAutoCompletePopup(window).removeEventListener("popupshowing", + listener, + false); + }, + isBackButtonEnabled: function(window) { + return !this._getTopChromeWindow(window).document + .getElementById("Browser:Back") + .hasAttribute("disabled"); + }, + + addChromeEventListener: function(type, listener, capture, allowUntrusted) { + addEventListener(type, listener, capture, allowUntrusted); + }, + removeChromeEventListener: function(type, listener, capture) { + removeEventListener(type, listener, capture); + }, + + getFullZoom: function(window) { + return this._getMUDV(window).fullZoom; + }, + setFullZoom: function(window, zoom) { + this._getMUDV(window).fullZoom = zoom; + }, + getTextZoom: function(window) { + return this._getMUDV(window).textZoom; + }, + setTextZoom: function(window, zoom) { + this._getMUDV(window).textZoom = zoom; + }, + + createSystemXHR: function() { + return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + }, + + gc: function() { + this.DOMWindowUtils.garbageCollect(); + }, + + hasContentProcesses: function() { + try { + var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); + return rt.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + } catch (e) { + return true; + } + }, + + registerProcessCrashObservers: function() { + addMessageListener("SPProcessCrashService", this._messageListener); + sendSyncMessage("SPProcessCrashService", { op: "register-observer" }); + }, + + _messageReceived: function(aMessage) { + switch (aMessage.name) { + case "SPProcessCrashService": + if (aMessage.json.type == "crash-observed") { + var self = this; + aMessage.json.dumpIDs.forEach(function(id) { + self._encounteredCrashDumpFiles.push(id + ".dmp"); + self._encounteredCrashDumpFiles.push(id + ".extra"); + }); + } + break; + + case "SPPingService": + if (aMessage.json.op == "pong") { + var handler = this._pongHandlers.shift(); + if (handler) { + handler(); + } + } + break; + } + return true; + }, + + removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) { + var success = true; + if (aExpectingProcessCrash) { + var message = { + op: "delete-crash-dump-files", + filenames: this._encounteredCrashDumpFiles + }; + if (!sendSyncMessage("SPProcessCrashService", message)[0]) { + success = false; + } + } + this._encounteredCrashDumpFiles.length = 0; + return success; + }, + + findUnexpectedCrashDumpFiles: function() { + var self = this; + var message = { + op: "find-crash-dump-files", + crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles + }; + var crashDumpFiles = sendSyncMessage("SPProcessCrashService", message)[0]; + crashDumpFiles.forEach(function(aFilename) { + self._unexpectedCrashDumpFiles[aFilename] = true; + }); + return crashDumpFiles; + }, + + executeAfterFlushingMessageQueue: function(aCallback) { + this._pongHandlers.push(aCallback); + sendAsyncMessage("SPPingService", { op: "ping" }); + }, + + executeSoon: function(aFunc) { + var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + tm.mainThread.dispatch({ + run: function() { + aFunc(); + } + }, Ci.nsIThread.DISPATCH_NORMAL); + }, + + /* from http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/quit.js + * by Bob Clary, Jeff Walden, and Robert Sayre. + */ + quitApplication: function() { + function canQuitApplication() + { + var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + if (!os) + return true; + + try { + var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + os.notifyObservers(cancelQuit, "quit-application-requested", null); + + // Something aborted the quit process. + if (cancelQuit.data) + return false; + } catch (ex) {} + return true; + } + + if (!canQuitApplication()) + return false; + + var appService = Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup); + appService.quit(Ci.nsIAppStartup.eForceQuit); + return true; + } +}; + +// Expose everything but internal APIs (starting with underscores) to +// web content. +SpecialPowers.prototype.__exposedProps__ = {}; +for each (i in Object.keys(SpecialPowers.prototype).filter(function(v) {return v.charAt(0) != "_";})) { + SpecialPowers.prototype.__exposedProps__[i] = "r"; +} + +// Attach our API to the window. +function attachSpecialPowersToWindow(aWindow) { + try { + if ((aWindow !== null) && + (aWindow !== undefined) && + (aWindow.wrappedJSObject) && + !(aWindow.wrappedJSObject.SpecialPowers)) { + aWindow.wrappedJSObject.SpecialPowers = new SpecialPowers(aWindow); + } + } catch(ex) { + dump("TEST-INFO | specialpowers.js | Failed to attach specialpowers to window exception: " + ex + "\n"); + } +} + +// This is a frame script, so it may be running in a content process. +// In any event, it is targeted at a specific "tab", so we listen for +// the DOMWindowCreated event to be notified about content windows +// being created in this context. + +function SpecialPowersManager() { + addEventListener("DOMWindowCreated", this, false); +} + +SpecialPowersManager.prototype = { + handleEvent: function handleEvent(aEvent) { + var window = aEvent.target.defaultView; + + // Need to make sure we are called on what we care about - + // content windows. DOMWindowCreated is called on *all* HTMLDocuments, + // some of which belong to chrome windows or other special content. + // + var uri = window.document.documentURIObject; + if (uri.scheme === "chrome" || uri.spec.split(":")[0] == "about") { + return; + } + + attachSpecialPowersToWindow(window); + } +}; + +var specialpowersmanager = new SpecialPowersManager(); diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js new file mode 100755 index 000000000..90655e2e7 --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js @@ -0,0 +1,293 @@ +/* ***** 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() { + this._isFrameScriptLoaded = false; + this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"]. + getService(Ci.nsIChromeFrameMessageManager); +} + +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 }], + + observe: function(aSubject, aTopic, aData) + { + switch (aTopic) { + case "profile-after-change": + this.init(); + break; + + case "chrome-document-global-created": + if (!this._isFrameScriptLoaded) { + // Register for any messages our API needs us to handle + this._messageManager.addMessageListener("SPPrefService", this); + this._messageManager.addMessageListener("SPProcessCrashService", this); + this._messageManager.addMessageListener("SPPingService", this); + + this._messageManager.loadFrameScript(CHILD_SCRIPT, true); + this._isFrameScriptLoaded = true; + } + break; + + case "xpcom-shutdown": + this.uninit(); + break; + + case "plugin-crashed": + case "ipc:content-shutdown": + function addDumpIDToMessage(propertyName) { + var id = aSubject.getPropertyAsAString(propertyName); + if (id) { + message.dumpIDs.push(id); + } + } + + var message = { type: "crash-observed", dumpIDs: [] }; + aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2); + if (aTopic == "plugin-crashed") { + addDumpIDToMessage("pluginDumpID"); + addDumpIDToMessage("browserDumpID"); + } else { // ipc:content-shutdown + addDumpIDToMessage("dumpID"); + } + this._messageManager.sendAsyncMessage("SPProcessCrashService", message); + break; + } + }, + + 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); + this.removeProcessCrashObservers(); + }, + + addProcessCrashObservers: function() { + if (this._processCrashObserversRegistered) { + return; + } + + Services.obs.addObserver(this, "plugin-crashed", false); + Services.obs.addObserver(this, "ipc:content-shutdown", false); + this._processCrashObserversRegistered = true; + }, + + removeProcessCrashObservers: function() { + if (!this._processCrashObserversRegistered) { + return; + } + + Services.obs.removeObserver(this, "plugin-crashed"); + Services.obs.removeObserver(this, "ipc:content-shutdown"); + this._processCrashObserversRegistered = false; + }, + + getCrashDumpDir: function() { + if (!this._crashDumpDir) { + var directoryService = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + this._crashDumpDir = directoryService.get("ProfD", Ci.nsIFile); + this._crashDumpDir.append("minidumps"); + } + return this._crashDumpDir; + }, + + deleteCrashDumpFiles: function(aFilenames) { + var crashDumpDir = this.getCrashDumpDir(); + if (!crashDumpDir.exists()) { + return false; + } + + var success = aFilenames.length != 0; + aFilenames.forEach(function(crashFilename) { + var file = crashDumpDir.clone(); + file.append(crashFilename); + if (file.exists()) { + file.remove(false); + } else { + success = false; + } + }); + return success; + }, + + findCrashDumpFiles: function(aToIgnore) { + var crashDumpDir = this.getCrashDumpDir(); + var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries; + if (!entries) { + return []; + } + + var crashDumpFiles = []; + while (entries.hasMoreElements()) { + var file = entries.getNext().QueryInterface(Ci.nsIFile); + var path = String(file.path); + if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) { + crashDumpFiles.push(path); + } + } + return crashDumpFiles.concat(); + }, + + /** + * 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 if (aMessage.json.op == "clear") { + if (!prefName) + throw new SpecialPowersException("Invalid parameters for clear 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])); + case "": + if (aMessage.json.op == "clear") { + prefs.clearUserPref(prefName); + return; + } + } + break; + + case "SPProcessCrashService": + switch (aMessage.json.op) { + case "register-observer": + this.addProcessCrashObservers(); + break; + case "unregister-observer": + this.removeProcessCrashObservers(); + break; + case "delete-crash-dump-files": + return this.deleteCrashDumpFiles(aMessage.json.filenames); + case "find-crash-dump-files": + return this.findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore); + default: + throw new SpecialPowersException("Invalid operation for SPProcessCrashService"); + } + break; + + case "SPPingService": + if (aMessage.json.op == "ping") { + aMessage.target + .QueryInterface(Ci.nsIFrameLoaderOwner) + .frameLoader + .messageManager + .sendAsyncMessage("SPPingService", { op: "pong" }); + } + break; + + default: + throw new SpecialPowersException("Unrecognized Special Powers API"); + } + } +}; + +const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]); diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf new file mode 100644 index 000000000..db8de988e --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf @@ -0,0 +1,26 @@ + + + + + + special-powers@mozilla.org + 2010.07.23 + 2 + + + + + toolkit@mozilla.org + 3.0 + 7.0a1 + + + + + Special Powers + Special powers for use in testing. + Mozilla + + diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js new file mode 100644 index 000000000..d4b9d4130 --- /dev/null +++ b/test/resources/firefox/user.js @@ -0,0 +1,34 @@ +user_pref("browser.console.showInPanel", true); +user_pref("browser.dom.window.dump.enabled", true); +user_pref("browser.firstrun.show.localepicker", false); +user_pref("browser.firstrun.show.uidiscovery", false); +user_pref("dom.allow_scripts_to_close_windows", true); +user_pref("dom.disable_open_during_load", false); +user_pref("dom.max_script_run_time", 0); // no slow script dialogs +user_pref("dom.max_chrome_script_run_time", 0); +user_pref("dom.popup_maximum", -1); +user_pref("dom.send_after_paint_to_content", true); +user_pref("dom.successive_dialog_time_limit", 0); +user_pref("security.warn_submit_insecure", false); +user_pref("browser.shell.checkDefaultBrowser", false); +user_pref("shell.checkDefaultClient", false); +user_pref("browser.warnOnQuit", false); +user_pref("accessibility.typeaheadfind.autostart", false); +user_pref("javascript.options.showInConsole", true); +user_pref("devtools.errorconsole.enabled", true); +user_pref("layout.debug.enable_data_xbl", true); +user_pref("browser.EULA.override", true); +user_pref("javascript.options.tracejit.content", true); +user_pref("javascript.options.methodjit.content", true); +user_pref("javascript.options.jitprofiling.content", true); +user_pref("javascript.options.methodjit_always", false); +user_pref("gfx.color_management.force_srgb", true); +user_pref("network.manage-offline-status", false); +user_pref("test.mousescroll", true); +user_pref("network.http.prompt-temp-redirect", false); +user_pref("media.cache_size", 100); +user_pref("security.warn_viewing_mixed", false); +user_pref("app.update.enabled", false); +user_pref("browser.panorama.experienced_first_run", true); // Assume experienced +user_pref("dom.w3c_touch_events.enabled", true); +user_pref("extensions.checkCompatibility", false); diff --git a/test.py b/test/test.py similarity index 66% rename from test.py rename to test/test.py index 9eab0e80e..53f65f78b 100644 --- a/test.py +++ b/test/test.py @@ -1,11 +1,13 @@ -import json, os, sys, subprocess, urllib2 +import json, platform, os, shutil, sys, subprocess, tempfile, threading, urllib, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import SocketServer +from optparse import OptionParser from urlparse import urlparse -def prompt(question): - '''Return True iff the user answered "yes" to |question|.''' - inp = raw_input(question +' [yes/no] > ') - return inp == 'yes' +USAGE_EXAMPLE = "%prog" + +# The local web server uses the git repo as the document root. +DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) ANAL = True DEFAULT_MANIFEST_FILE = 'test_manifest.json' @@ -14,6 +16,34 @@ REFDIR = 'ref' TMPDIR = 'tmp' VERBOSE = False +class TestOptions(OptionParser): + def __init__(self, **kwargs): + OptionParser.__init__(self, **kwargs) + self.add_option("-m", "--masterMode", action="store_true", dest="masterMode", + help="Run the script in master mode.", default=False) + self.add_option("--manifestFile", action="store", type="string", dest="manifestFile", + help="A JSON file in the form of test_manifest.json (the default).") + self.add_option("-b", "--browser", action="store", type="string", dest="browser", + help="The path to a single browser (right now, only Firefox is supported).") + self.add_option("--browserManifestFile", action="store", type="string", + dest="browserManifestFile", + help="A JSON file in the form of those found in resources/browser_manifests") + self.set_usage(USAGE_EXAMPLE) + + def verifyOptions(self, options): + if options.masterMode and options.manifestFile: + self.error("--masterMode and --manifestFile must not be specified at the same time.") + if not options.manifestFile: + options.manifestFile = DEFAULT_MANIFEST_FILE + if options.browser and options.browserManifestFile: + print "Warning: ignoring browser argument since manifest file was also supplied" + return options + +def prompt(question): + '''Return True iff the user answered "yes" to |question|.''' + inp = raw_input(question +' [yes/no] > ') + return inp == 'yes' + MIMEs = { '.css': 'text/css', '.html': 'text/html', @@ -43,8 +73,11 @@ class Result: self.snapshot = snapshot self.failure = failure +class TestServer(SocketServer.TCPServer): + allow_reuse_address = True class PDFTestHandler(BaseHTTPRequestHandler): + # Disable annoying noise by default def log_request(code=0, size=0): if VERBOSE: @@ -54,13 +87,11 @@ class PDFTestHandler(BaseHTTPRequestHandler): url = urlparse(self.path) # Ignore query string path, _ = url.path, url.query - cwd = os.getcwd() - path = os.path.abspath(os.path.realpath(cwd + os.sep + path)) - cwd = os.path.abspath(cwd) - prefix = os.path.commonprefix(( path, cwd )) + path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) + prefix = os.path.commonprefix(( path, DOC_ROOT )) _, ext = os.path.splitext(path) - if not (prefix == cwd + if not (prefix == DOC_ROOT and os.path.isfile(path) and ext in MIMEs): self.send_error(404) @@ -102,13 +133,49 @@ class PDFTestHandler(BaseHTTPRequestHandler): State.done = (0 == State.remaining) +# this just does Firefox for now +class BrowserCommand(): + def __init__(self, browserRecord): + self.name = browserRecord["name"] + self.path = browserRecord["path"] -def setUp(manifestFile, masterMode): + if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): + self._fixupMacPath() + + if not os.path.exists(self.path): + throw("Path to browser '%s' does not exist." % self.path) + + def _fixupMacPath(self): + self.path = os.path.join(self.path, "Contents", "MacOS", "firefox-bin") + + def setup(self): + self.tempDir = tempfile.mkdtemp() + self.profileDir = os.path.join(self.tempDir, "profile") + print self.profileDir + shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"), + self.profileDir) + + def teardown(self): + shutil.rmtree(self.tempDir) + + def start(self, url): + cmds = [self.path] + if platform.system() == "Darwin": + cmds.append("-foreground") + cmds.extend(["-no-remote", "-profile", self.profileDir, url]) + subprocess.call(cmds) + +def makeBrowserCommands(browserManifestFile): + with open(browserManifestFile) as bmf: + browsers = [BrowserCommand(browser) for browser in json.load(bmf)] + return browsers + +def setUp(options): # Only serve files from a pdf.js clone - assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git') + assert not ANAL or os.path.isfile('../pdf.js') and os.path.isdir('../.git') - State.masterMode = masterMode - if masterMode and os.path.isdir(TMPDIR): + State.masterMode = options.masterMode + if options.masterMode and os.path.isdir(TMPDIR): print 'Temporary snapshot dir tmp/ is still around.' print 'tmp/ can be removed if it has nothing you need.' if prompt('SHOULD THIS SCRIPT REMOVE tmp/? THINK CAREFULLY'): @@ -116,14 +183,16 @@ def setUp(manifestFile, masterMode): assert not os.path.isdir(TMPDIR) - testBrowsers = [ b for b in - ( 'firefox5', 'firefox6', ) -#'chrome12', 'chrome13', 'firefox4', 'opera11' ): - if os.access(b, os.R_OK | os.X_OK) ] - - mf = open(manifestFile) - manifestList = json.load(mf) - mf.close() + testBrowsers = [] + if options.browserManifestFile: + testBrowsers = makeBrowserCommands(options.browserManifestFile) + elif options.browser: + testBrowsers = [BrowserCommand({"path":options.browser, "name":"firefox"})] + else: + print "No test browsers found. Use --browserManifest or --browser args." + + with open(options.manifestFile) as mf: + manifestList = json.load(mf) for item in manifestList: f, isLink = item['file'], item.get('link', False) @@ -143,23 +212,25 @@ def setUp(manifestFile, masterMode): print 'done' for b in testBrowsers: - State.taskResults[b] = { } + State.taskResults[b.name] = { } for item in manifestList: id, rounds = item['id'], int(item['rounds']) State.manifest[id] = item taskResults = [ ] for r in xrange(rounds): taskResults.append([ ]) - State.taskResults[b][id] = taskResults + State.taskResults[b.name][id] = taskResults State.remaining = len(testBrowsers) * len(manifestList) for b in testBrowsers: - print 'Launching', b - qs = 'browser='+ b +'&manifestFile='+ manifestFile - subprocess.Popen(( os.path.abspath(os.path.realpath(b)), - 'http://localhost:8080/test_slave.html?'+ qs)) - + try: + b.setup() + print 'Launching', b.name + qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) + b.start('http://localhost:8080/test/test_slave.html?'+ qs) + finally: + b.teardown() def check(task, results, browser): failed = False @@ -302,20 +373,20 @@ def processResults(): print 'done' -def main(args): - masterMode = False - manifestFile = DEFAULT_MANIFEST_FILE - if len(args) == 1: - masterMode = (args[0] == '-m') - manifestFile = args[0] if not masterMode else manifestFile +def main(): + optionParser = TestOptions() + options, args = optionParser.parse_args() + options = optionParser.verifyOptions(options) + if options == None: + sys.exit(1) - setUp(manifestFile, masterMode) - - server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) - while not State.done: - server.handle_request() + httpd = TestServer(('127.0.0.1', 8080), PDFTestHandler) + httpd_thread = threading.Thread(target=httpd.serve_forever) + httpd_thread.setDaemon(True) + httpd_thread.start() + setUp(options) processResults() if __name__ == '__main__': - main(sys.argv[1:]) + main() diff --git a/test_manifest.json b/test/test_manifest.json similarity index 70% rename from test_manifest.json rename to test/test_manifest.json index 036b7aafc..e4a7ada81 100644 --- a/test_manifest.json +++ b/test/test_manifest.json @@ -1,21 +1,21 @@ [ { "id": "tracemonkey-eq", - "file": "tests/tracemonkey.pdf", + "file": "pdfs/tracemonkey.pdf", "rounds": 1, "type": "eq" }, { "id": "tracemonkey-fbf", - "file": "tests/tracemonkey.pdf", + "file": "pdfs/tracemonkey.pdf", "rounds": 2, "type": "fbf" }, { "id": "html5-canvas-cheat-sheet-load", - "file": "tests/canvas.pdf", + "file": "pdfs/canvas.pdf", "rounds": 1, "type": "load" }, { "id": "pdfspec-load", - "file": "tests/pdf.pdf", + "file": "pdfs/pdf.pdf", "link": true, "rounds": 1, "type": "load" diff --git a/test_slave.html b/test/test_slave.html similarity index 93% rename from test_slave.html rename to test/test_slave.html index 718e887e0..1053025e1 100644 --- a/test_slave.html +++ b/test/test_slave.html @@ -2,9 +2,9 @@ pdf.js test slave - - - + + +