author | Drew Willcoxon <adw@mozilla.com> |
Fri, 29 Jun 2012 15:52:43 -0700 | |
changeset 97976 | df17f2f4ab63777c252b88b9e68211422a9c4501 |
parent 97975 | b3d91c0e323ba37003151f55e41b27120e1b5060 |
child 97977 | e8bab55ac425419721f047c4c9dfc799d838b5a8 |
push id | 11316 |
push user | dwillcoxon@mozilla.com |
push date | Fri, 29 Jun 2012 22:55:55 +0000 |
treeherder | mozilla-inbound@df17f2f4ab63 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | myk, felipc, ctalbert |
bugs | 733631 |
milestone | 16.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
|
--- a/testing/mochitest/browser-harness.xul +++ b/testing/mochitest/browser-harness.xul @@ -183,17 +183,23 @@ function setStatus(aStatusString) { document.getElementById("status").value = aStatusString; } function runTests() { var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1']. getService(Ci.nsIWindowMediator); - var testWin = windowMediator.getMostRecentWindow("navigator:browser"); + var winType = gConfig.testRoot == "browser" ? "navigator:browser" : + gConfig.testRoot == "webapprtChrome" ? "webapprt:webapp" : + null; + if (!winType) { + throw new Error("Unrecognized gConfig.testRoot: " + gConfig.testRoot); + } + var testWin = windowMediator.getMostRecentWindow(winType); setStatus("Running..."); testWin.focus(); var Tester = new testWin.Tester(listTests(), gDumper, testsFinished); Tester.start(); } function sum(a, b) {
--- a/testing/mochitest/browser-test.js +++ b/testing/mochitest/browser-test.js @@ -8,17 +8,17 @@ if (Cc === undefined) { var Cu = Components.utils; } window.addEventListener("load", testOnLoad, false); function testOnLoad() { window.removeEventListener("load", testOnLoad, false); gConfig = readConfig(); - if (gConfig.testRoot == "browser") { + if (gConfig.testRoot == "browser" || gConfig.testRoot == "webapprtChrome") { // Make sure to launch the test harness for the first opened window only var prefs = Cc["@mozilla.org/preferences-service;1"]. getService(Ci.nsIPrefBranch); if (prefs.prefHasUserValue("testing.browserTestHarness.running")) return; prefs.setBoolPref("testing.browserTestHarness.running", true); @@ -105,17 +105,20 @@ Tester.prototype = { }, waitForWindowsState: function Tester_waitForWindowsState(aCallback) { let timedOut = this.currentTest && this.currentTest.timedOut; let baseMsg = timedOut ? "Found a {elt} after previous test timed out" : this.currentTest ? "Found an unexpected {elt} at the end of test run" : "Found an unexpected {elt}"; - if (this.currentTest && window.gBrowser && gBrowser.tabs.length > 1) { + if (gConfig.testRoot == "browser" && + this.currentTest && + window.gBrowser && + gBrowser.tabs.length > 1) { while (gBrowser.tabs.length > 1) { let lastTab = gBrowser.tabContainer.lastChild; let msg = baseMsg.replace("{elt}", "tab") + ": " + lastTab.linkedBrowser.currentURI.spec; this.currentTest.addResult(new testResult(false, msg, "", false)); gBrowser.removeTab(lastTab); } }
--- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -134,16 +134,26 @@ class MochitestOptions(optparse.OptionPa help = "start in the given directory's tests") defaults["testPath"] = "" self.add_option("--browser-chrome", action = "store_true", dest = "browserChrome", help = "run browser chrome Mochitests") defaults["browserChrome"] = False + self.add_option("--webapprt-content", + action = "store_true", dest = "webapprtContent", + help = "run WebappRT content tests") + defaults["webapprtContent"] = False + + self.add_option("--webapprt-chrome", + action = "store_true", dest = "webapprtChrome", + help = "run WebappRT chrome tests") + defaults["webapprtChrome"] = False + self.add_option("--a11y", action = "store_true", dest = "a11y", help = "run accessibility Mochitests"); defaults["a11y"] = False self.add_option("--setenv", action = "append", type = "string", dest = "environment", metavar = "NAME=VALUE", @@ -299,16 +309,19 @@ See <http://mochikit.com/doc/html/MochiK options.runOnly = True if options.excludeTests: if not os.path.exists(os.path.abspath(options.excludeTests)): self.error("unable to find --exclude-tests file '%s'" % options.excludeTests); options.testManifest = options.excludeTests options.runOnly = False + if options.webapprtContent and options.webapprtChrome: + self.error("Only one of --webapprt-content and --webapprt-chrome may be given.") + return options ####################### # HTTP SERVER SUPPORT # ####################### class MochitestServer: @@ -318,30 +331,31 @@ class MochitestServer: self._automation = automation self._closeWhenDone = options.closeWhenDone self._utilityPath = options.utilityPath self._xrePath = options.xrePath self._profileDir = options.profilePath self.webServer = options.webServer self.httpPort = options.httpPort self.shutdownURL = "http://%(server)s:%(port)s/server/shutdown" % { "server" : self.webServer, "port" : self.httpPort } + self.testPrefix = "'webapprt_'" if options.webapprtContent else "undefined" def start(self): "Run the Mochitest server, returning the process ID of the server." env = self._automation.environment(xrePath = self._xrePath) env["XPCOM_DEBUG_BREAK"] = "warn" if self._automation.IS_WIN32: env["PATH"] = env["PATH"] + ";" + self._xrePath args = ["-g", self._xrePath, "-v", "170", "-f", "./" + "httpd.js", - "-e", "const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = '%(port)s'; const _SERVER_ADDR ='%(server)s';" % - {"profile" : self._profileDir.replace('\\', '\\\\'), "port" : self.httpPort, "server" : self.webServer }, + "-e", "const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = '%(port)s'; const _SERVER_ADDR = '%(server)s'; const _TEST_PREFIX = %(testPrefix)s;" % + {"profile" : self._profileDir.replace('\\', '\\\\'), "port" : self.httpPort, "server" : self.webServer, "testPrefix" : self.testPrefix }, "-f", "./" + "server.js"] xpcshell = os.path.join(self._utilityPath, "xpcshell" + self._automation.BIN_SUFFIX) self._process = self._automation.Process([xpcshell] + args, env = env) pid = self._process.pid if pid < 0: print "Error starting server." @@ -542,17 +556,17 @@ class Mochitest(object): thisChunk -- which chunk to run timeout -- per-test timeout in seconds repeat -- How many times to repeat the test, ie: repeat=1 will run the test twice. """ # allow relative paths for logFile if options.logFile: options.logFile = self.getLogFilePath(options.logFile) - if options.browserChrome or options.chrome or options.a11y: + if options.browserChrome or options.chrome or options.a11y or options.webapprtChrome: self.makeTestConfig(options) else: if options.autorun: self.urlOpts.append("autorun=1") if options.timeout: self.urlOpts.append("timeout=%d" % options.timeout) if options.closeWhenDone: self.urlOpts.append("closeWhenDone=1") @@ -636,31 +650,37 @@ class Mochitest(object): self.startWebServer(options) self.startWebSocketServer(options, debuggerInfo) testURL = self.buildTestPath(options) self.buildURLOptions(options, browserEnv) if len(self.urlOpts) > 0: testURL += "?" + "&".join(self.urlOpts) + if options.webapprtContent: + options.browserArgs.extend(('-test-mode', testURL)) + testURL = None + # Remove the leak detection file so it can't "leak" to the tests run. # The file is not there if leak logging was not enabled in the application build. if os.path.exists(self.leak_report_file): os.remove(self.leak_report_file) # then again to actually run mochitest if options.timeout: timeout = options.timeout + 30 elif not options.autorun: timeout = None else: timeout = 330.0 # default JS harness timeout is 300 seconds # it's a debug build, we can parse leaked DOMWindows and docShells - if Automation.IS_DEBUG_BUILD: + # but skip for WebappRT chrome tests, where DOMWindow "leaks" aren't + # meaningful. See https://bugzilla.mozilla.org/show_bug.cgi?id=733631#c46 + if Automation.IS_DEBUG_BUILD and not options.webapprtChrome: logger = ShutdownLeakLogger(self.automation.log) else: logger = None if options.vmwareRecording: self.startVMwareRecording(options); self.automation.log.info("INFO | runtests.py | Running tests: start.\n") @@ -728,17 +748,19 @@ class Mochitest(object): options.logFile = options.logFile.replace("\\", "\\\\") options.testPath = options.testPath.replace("\\", "\\\\") testRoot = 'chrome' if (options.browserChrome): testRoot = 'browser' elif (options.a11y): testRoot = 'a11y' - + elif (options.webapprtChrome): + testRoot = 'webapprtChrome' + if "MOZ_HIDE_RESULTS_TABLE" in os.environ and os.environ["MOZ_HIDE_RESULTS_TABLE"] == "1": options.hideResultsTable = True #TODO: when we upgrade to python 2.6, just use json.dumps(options.__dict__) content = "{" content += '"testRoot": "%s", ' % (testRoot) first = True for opt in options.__dict__.keys(): @@ -790,23 +812,25 @@ toolbar#nav-bar { manifestFile.write("content mochitests %s contentaccessible=yes\n" % chrometestDir) # Call installChromeJar(). jarDir = "mochijar" if not os.path.isdir(os.path.join(self.SCRIPT_DIRECTORY, jarDir)): self.automation.log.warning("TEST-UNEXPECTED-FAIL | invalid setup: missing mochikit extension") return None - # Support Firefox (browser), B2G (shell) and SeaMonkey (navigator). + # Support Firefox (browser), B2G (shell), SeaMonkey (navigator), and Webapp + # Runtime (webapp). chrome = "" - if options.browserChrome or options.chrome or options.a11y: + if options.browserChrome or options.chrome or options.a11y or options.webapprtChrome: chrome += """ overlay chrome://browser/content/browser.xul chrome://mochikit/content/browser-test-overlay.xul overlay chrome://browser/content/shell.xul chrome://mochikit/content/browser-test-overlay.xul overlay chrome://navigator/content/navigator.xul chrome://mochikit/content/browser-test-overlay.xul +overlay chrome://webapprt/content/webapp.xul chrome://mochikit/content/browser-test-overlay.xul """ self.installChromeJar(jarDir, chrome, options) return manifest def installChromeJar(self, jarDirName, chrome, options): """ copy mochijar directory to profile as an extension so we have chrome://mochikit for all harness code
--- a/testing/mochitest/server.js +++ b/testing/mochitest/server.js @@ -426,18 +426,20 @@ function list(requestPath, directory, re * a supporting file. */ function isTest(filename, pattern) { if (pattern) return pattern.test(filename); // File name is a URL style path to a test file, make sure that we check for - // tests that start with test_. - testPattern = /^test_/; + // tests that start with the appropriate prefix. + var testPrefix = typeof(_TEST_PREFIX) == "string" ? _TEST_PREFIX : "test_"; + var testPattern = new RegExp("^" + testPrefix); + pathPieces = filename.split('/'); return testPattern.test(pathPieces[pathPieces.length - 1]) && filename.indexOf(".js") == -1 && filename.indexOf(".css") == -1 && !/\^headers\^$/.test(filename); }
--- a/testing/testsuite-targets.mk +++ b/testing/testsuite-targets.mk @@ -124,16 +124,26 @@ endif ifeq (powerpc,$(TARGET_CPU)) $(RUN_MOCHITEST) --setpref=dom.ipc.plugins.enabled.ppc.test.plugin=false --test-path=dom/plugins/test endif else $(RUN_MOCHITEST) --setpref=dom.ipc.plugins.enabled=false --test-path=dom/plugins/test endif $(CHECK_TEST_ERROR) +ifeq ($(OS_ARCH),Darwin) +webapprt_stub_path = $(TARGET_DIST)/$(MOZ_MACBUNDLE_NAME)/Contents/MacOS/webapprt-stub$(BIN_SUFFIX) +webapprt-test-content: + $(RUN_MOCHITEST) --webapprt-content --appname $(webapprt_stub_path) + $(CHECK_TEST_ERROR) +webapprt-test-chrome: + $(RUN_MOCHITEST) --webapprt-chrome --appname $(webapprt_stub_path) --browser-arg -test-mode + $(CHECK_TEST_ERROR) +endif + # Usage: |make [EXTRA_TEST_ARGS=...] *test|. RUN_REFTEST = rm -f ./$@.log && $(PYTHON) _tests/reftest/runreftest.py \ $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) $(1) | tee ./$@.log REMOTE_REFTEST = rm -f ./$@.log && $(PYTHON) _tests/reftest/remotereftest.py \ --dm_trans=$(DM_TRANS) --ignore-window-size \ --app=$(TEST_PACKAGE_NAME) --deviceIP=${TEST_DEVICE} --xre-path=${MOZ_HOST_BIN} \ $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) $(1) | tee ./$@.log
--- a/webapprt/CommandLineHandler.js +++ b/webapprt/CommandLineHandler.js @@ -12,62 +12,116 @@ Cu.import("resource://gre/modules/Servic function CommandLineHandler() {} CommandLineHandler.prototype = { classID: Components.ID("{6d69c782-40a3-469b-8bfd-3ee366105a4a}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]), handle: function handle(cmdLine) { + let args = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); + let inTestMode = this._handleTestMode(cmdLine, args); + Services.obs.notifyObservers(args, "webapprt-command-line", null); + + // Initialize DOMApplicationRegistry by importing Webapps.jsm, but only + // after broadcasting webapprt-command-line. Webapps.jsm calls + // DOMApplicationRegistry.init() when it's first imported. init() accesses + // the WebappRegD directory, which in test mode is special-cased by + // DirectoryProvider.js after it observes webapprt-command-line. + Cu.import("resource://gre/modules/Webapps.jsm"); + + if (!inTestMode) { + startUp(); + } else { + // startUp() accesses WebappRT.config, which in test mode is not valid + // until WebappRT.jsm observes an app installation. + Services.obs.addObserver(function onInstall(subj, topic, data) { + Services.obs.removeObserver(onInstall, "webapprt-test-did-install"); + startUp(); + }, "webapprt-test-did-install", false); + } + // Open the window with arguments to identify it as the main window Services.ww.openWindow(null, "chrome://webapprt/content/webapp.xul", "_blank", "chrome,dialog=no,resizable,scrollbars,centerscreen", - []); + args); + }, - // Initialize window-independent handling of webapps- notifications - Cu.import("resource://webapprt/modules/WebappsHandler.jsm"); - WebappsHandler.init(); + _handleTestMode: function _handleTestMode(cmdLine, args) { + // -test-mode [url] + let idx = cmdLine.findFlag("test-mode", true); + if (idx < 0) + return false; + let url = null; + let urlIdx = idx + 1; + if (urlIdx < cmdLine.length) { + let potentialURL = cmdLine.getArgument(urlIdx); + if (potentialURL && potentialURL[0] != "-") { + url = potentialURL; + try { + Services.io.newURI(url, null, null); + } catch (err) { + throw Components.Exception( + "-test-mode argument is not a valid URL: " + url, + Components.results.NS_ERROR_INVALID_ARG); + } + cmdLine.removeArguments(urlIdx, urlIdx); + } + } + cmdLine.removeArguments(idx, idx); + args.setProperty("test-mode", url); + return true; }, helpInfo : "", }; let components = [CommandLineHandler]; let NSGetFactory = XPCOMUtils.generateNSGetFactory(components); /* There's some work we need to do on startup, before we load the webapp, * and this seems as good a place as any to do it, although it's possible * that in the future we will find a lazier place to do it. * * NOTE: it's very important that the stuff we do here doesn't prevent * the command-line handler from being registered/accessible, since otherwise * the app won't start, which is catastrophic failure. That's why it's all * wrapped in a try/catch block. */ - -try { - // Initialize DOMApplicationRegistry so it can receive/respond to messages. - Cu.import("resource://gre/modules/Webapps.jsm"); +function startUp(inTestMode) { + try { + if (!inTestMode) { + // Initialize window-independent handling of webapps- notifications. Skip + // this in test mode, since it interferes with test app installations. + // We'll have to revisit this when we actually want to test installations + // and other functionality provided by WebappsHandler. + Cu.import("resource://webapprt/modules/WebappsHandler.jsm"); + WebappsHandler.init(); + } - // On firstrun, set permissions to their default values. - if (!Services.prefs.getBoolPref("webapprt.firstrun")) { - Cu.import("resource://webapprt/modules/WebappRT.jsm"); - let uri = Services.io.newURI(WebappRT.config.app.origin, null, null); + // On firstrun, set permissions to their default values. + if (!Services.prefs.getBoolPref("webapprt.firstrun")) { + Cu.import("resource://webapprt/modules/WebappRT.jsm"); + let uri = Services.io.newURI(WebappRT.config.app.origin, null, null); - // Set AppCache-related permissions. - Services.perms.add(uri, "pin-app", Ci.nsIPermissionManager.ALLOW_ACTION); - Services.perms.add(uri, "offline-app", - Ci.nsIPermissionManager.ALLOW_ACTION); + // Set AppCache-related permissions. + Services.perms.add(uri, "pin-app", + Ci.nsIPermissionManager.ALLOW_ACTION); + Services.perms.add(uri, "offline-app", + Ci.nsIPermissionManager.ALLOW_ACTION); - Services.perms.add(uri, "indexedDB", Ci.nsIPermissionManager.ALLOW_ACTION); - Services.perms.add(uri, "indexedDB-unlimited", - Ci.nsIPermissionManager.ALLOW_ACTION); + Services.perms.add(uri, "indexedDB", + Ci.nsIPermissionManager.ALLOW_ACTION); + Services.perms.add(uri, "indexedDB-unlimited", + Ci.nsIPermissionManager.ALLOW_ACTION); - // Now that we've set the appropriate permissions, twiddle the firstrun flag - // so we don't try to do so again. - Services.prefs.setBoolPref("webapprt.firstrun", true); + // Now that we've set the appropriate permissions, twiddle the firstrun + // flag so we don't try to do so again. + Services.prefs.setBoolPref("webapprt.firstrun", true); + } + } catch(ex) { +#ifdef MOZ_DEBUG + dump(ex + "\n"); +#endif } -} catch(ex) { -#ifdef MOZ_DEBUG - dump(ex + "\n"); -#endif }
--- a/webapprt/DirectoryProvider.js +++ b/webapprt/DirectoryProvider.js @@ -7,27 +7,42 @@ const Ci = Components.interfaces; const Cu = Components.utils; const WEBAPP_REGISTRY_DIR = "WebappRegD"; const NS_APP_CHROME_DIR_LIST = "AChromDL"; Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://webapprt/modules/WebappRT.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +let gInTestMode = false; +Services.obs.addObserver(function observe(subj, topic, data) { + Services.obs.removeObserver(observe, "webapprt-command-line"); + let args = subj.QueryInterface(Ci.nsIPropertyBag2); + gInTestMode = args.hasKey("test-mode"); +}, "webapprt-command-line", false); function DirectoryProvider() {} DirectoryProvider.prototype = { classID: Components.ID("{e1799fda-4b2f-4457-b671-e0641d95698d}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider, Ci.nsIDirectoryServiceProvider2]), getFile: function(prop, persistent) { if (prop == WEBAPP_REGISTRY_DIR) { + if (gInTestMode) { + // In test mode, apps are registered in the runtime's profile. Note + // that in test mode WebappRT.config may not be valid at this point. + // It's only valid after WebappRT.jsm observes an app installation, and + // we can get here before any app is installed. + return FileUtils.getDir("ProfD", []); + } let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); dir.initWithPath(WebappRT.config.registryDir); return dir; } // We return null to show failure instead of throwing an error, // which works with the way the interface is called (per bug 529077). return null;
--- a/webapprt/Makefile.in +++ b/webapprt/Makefile.in @@ -41,16 +41,20 @@ EXTRA_PP_COMPONENTS = \ EXTRA_JS_MODULES = \ WebappRT.jsm \ WebappsHandler.jsm \ $(NULL) PREF_JS_EXPORTS = $(srcdir)/prefs.js \ $(NULL) +TEST_DIRS += \ + test \ + $(NULL) + include $(topsrcdir)/config/rules.mk ifdef MOZ_DEBUG DEFINES += -DMOZ_DEBUG=1 endif ifdef MOZILLA_OFFICIAL DEFINES += -DMOZILLA_OFFICIAL
--- a/webapprt/WebappRT.jsm +++ b/webapprt/WebappRT.jsm @@ -4,22 +4,46 @@ const EXPORTED_SYMBOLS = ["WebappRT"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyGetter(this, "FileUtils", function() { Cu.import("resource://gre/modules/FileUtils.jsm"); return FileUtils; }); +XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function() { + Cu.import("resource://gre/modules/Webapps.jsm"); + return DOMApplicationRegistry; +}); + +// In test mode, observe webapps-ask-install so tests can install apps. +Services.obs.addObserver(function observeCmdLine(subj, topic, data) { + Services.obs.removeObserver(observeCmdLine, "webapprt-command-line"); + let args = subj.QueryInterface(Ci.nsIPropertyBag2); + if (!args.hasKey("test-mode")) + return; + Services.obs.addObserver(function observeInstall(subj, topic, data) { + // observeInstall is present for the lifetime of the runtime. + let config = JSON.parse(data); + config.registryDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path; + delete WebappRT.config; + WebappRT.config = deepFreeze(config); + DOMApplicationRegistry.confirmInstall(config); + Services.obs.notifyObservers(null, "webapprt-test-did-install", + JSON.stringify(config)); + }, "webapps-ask-install", false); +}, "webapprt-command-line", false); + let WebappRT = { get config() { let webappFile = FileUtils.getFile("AppRegD", ["webapp.json"]); let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]. createInstance(Ci.nsIFileInputStream); inputStream.init(webappFile, -1, 0, 0); let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON); let config = json.decodeFromStream(inputStream, webappFile.fileSize);
--- a/webapprt/content/webapp.js +++ b/webapprt/content/webapp.js @@ -7,37 +7,62 @@ const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://webapprt/modules/WebappRT.jsm"); Cu.import("resource://gre/modules/Services.jsm"); function onLoad() { window.removeEventListener("load", onLoad, false); + let cmdLineArgs = window.arguments && window.arguments[0] ? + window.arguments[0].QueryInterface(Ci.nsIPropertyBag2) : + null; + + // In test mode, listen for test app installations and load the -test-mode URL + // if present. + if (cmdLineArgs && cmdLineArgs.hasKey("test-mode")) { + Services.obs.addObserver(function observe(subj, topic, data) { + // The observer is present for the lifetime of the runtime. + initWindow(false); + }, "webapprt-test-did-install", false); + let testURL = cmdLineArgs.get("test-mode"); + if (testURL) { + document.getElementById("content").loadURI(testURL); + } + return; + } + + initWindow(!!cmdLineArgs); +} + +window.addEventListener("load", onLoad, false); + +function initWindow(isMainWindow) { // Set the title of the window to the name of the webapp let manifest = WebappRT.config.app.manifest; document.documentElement.setAttribute("title", manifest.name); + updateMenuItems(); + // Listen for clicks to redirect <a target="_blank"> to the browser. // This doesn't capture clicks so content can capture them itself and do // something different if it doesn't want the default behavior. document.getElementById("content").addEventListener("click", onContentClick, false, true); // Only load the webapp on the initially launched main window - if ("arguments" in window) { + if (isMainWindow) { // Load the webapp's launch URL let installRecord = WebappRT.config.app; let url = Services.io.newURI(installRecord.origin, null, null); if (manifest.launch_path) url = Services.io.newURI(manifest.launch_path, null, url); document.getElementById("content").setAttribute("src", url.spec); } } -window.addEventListener("load", onLoad, false); /** * Direct a click on <a target="_blank"> to the user's default browser. * * In the long run, it might be cleaner to move this to an extension of * nsIWebBrowserChrome3::onBeforeLinkTraversal. * * @param {DOMEvent} event the DOM event @@ -61,33 +86,32 @@ function onContentClick(event) { launchWithURI(uri); // Prevent the runtime from loading the URL. We do this after directing it // to the browser to give the runtime a shot at handling the URL if we fail // to direct it to the browser for some reason. event.preventDefault(); } -#ifdef XP_MACOSX // On Mac, we dynamically create the label for the Quit menuitem, using // a string property to inject the name of the webapp into it. -window.addEventListener("load", function onLoadUpdateMenuItems() { - window.removeEventListener("load", onLoadUpdateMenuItems, false); +function updateMenuItems() { +#ifdef XP_MACOSX let installRecord = WebappRT.config.app; let manifest = WebappRT.config.app.manifest; let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties"); let quitLabel = bundle.formatStringFromName("quitApplicationCmdMac.label", [manifest.name], 1); let hideLabel = bundle.formatStringFromName("hideApplicationCmdMac.label", [manifest.name], 1); document.getElementById("menu_FileQuitItem").setAttribute("label", quitLabel); document.getElementById("menu_mac_hide_app").setAttribute("label", hideLabel); -}, false); #endif +} function updateEditUIVisibility() { #ifndef XP_MACOSX let editMenuPopupState = document.getElementById("menu_EditPopup").state; // The UI is visible if the Edit menu is opening or open, if the context menu // is open, or if the toolbar has been customized to include the Cut, Copy, // or Paste toolbar buttons.
--- a/webapprt/mac/webapprt.mm +++ b/webapprt/mac/webapprt.mm @@ -85,16 +85,19 @@ AttemptGRELoad(char *greDir) return rv; } int main(int argc, char **argv) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSDictionary *args = [[NSUserDefaults standardUserDefaults] + volatileDomainForName:NSArgumentDomain]; + NSString *firefoxPath = nil; NSString *alternateBinaryID = nil; //this is our version, to be compared with the version of the binary we are asked to use NSString* myVersion = [NSString stringWithFormat:@"%s", NS_STRINGIFY(GRE_BUILDID)]; NSLog(@"MY WEBAPPRT BUILDID: %@", myVersion); @@ -194,21 +197,16 @@ main(int argc, char **argv) if (!NS_SUCCEEDED(AttemptGRELoad(greDir))) { @throw MakeException(@"Error", @"Unable to load XUL files for application startup"); } // NOTE: The GRE has successfully loaded, so we can use XPCOM now NS_LogInit(); { // Scope for any XPCOM stuff we create - nsINIParser parser; - if (NS_FAILED(parser.Init(appEnv))) { - NSLog(@"%s was not found\n", appEnv); - @throw MakeException(@"Error", @"Unable to parse environment files for application startup"); - } // Get the path to the runtime directory. char rtDir[MAXPATHLEN]; snprintf(rtDir, MAXPATHLEN, "%s%s%s", [firefoxPath UTF8String], APP_CONTENTS_PATH, WEBAPPRT_PATH); // Get the path to the runtime's INI file. This is in the runtime // directory. snprintf(rtINIPath, MAXPATHLEN, "%s%s%s%s", [firefoxPath UTF8String], APP_CONTENTS_PATH, WEBAPPRT_PATH, WEBRTINI_NAME); @@ -229,23 +227,34 @@ main(int argc, char **argv) } nsXREAppData *webShellAppData; if (NS_FAILED(XRE_CreateAppData(rtINI, &webShellAppData))) { NSLog(@"Couldn't read WebappRT application.ini: %s", rtINIPath); @throw MakeException(@"Error", @"Unable to parse base INI file."); } - char profile[MAXPATHLEN]; - if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) { - NSLog(@"Unable to retrieve profile from web app INI file"); - @throw MakeException(@"Error", @"Unable to retrieve installation profile."); + NSString *profile = [args objectForKey:@"profile"]; + if (profile) { + NSLog(@"Profile specified with -profile: %@", profile); } - NSLog(@"setting app profile: %s", profile); - SetAllocatedString(webShellAppData->profile, profile); + else { + nsINIParser parser; + if (NS_FAILED(parser.Init(appEnv))) { + NSLog(@"%s was not found\n", appEnv); + @throw MakeException(@"Error", @"Unable to parse environment files for application startup"); + } + char profile[MAXPATHLEN]; + if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) { + NSLog(@"Unable to retrieve profile from web app INI file"); + @throw MakeException(@"Error", @"Unable to retrieve installation profile."); + } + NSLog(@"setting app profile: %s", profile); + SetAllocatedString(webShellAppData->profile, profile); + } nsCOMPtr<nsIFile> directory; if (NS_FAILED(XRE_GetFileFromPath(rtDir, getter_AddRefs(directory)))) { NSLog(@"Unable to open app dir"); @throw MakeException(@"Error", @"Unable to open application directory."); } nsCOMPtr<nsIFile> xreDir; @@ -304,16 +313,24 @@ DisplayErrorAlert(NSString* title, NSStr /* Find the currently installed Firefox, if any, and return * an absolute path to it. may return nil */ NSString *PathToWebRT(NSString* alternateBinaryID) { //default is firefox NSString *binaryPath = nil; + // We're run from the Firefox bundle during WebappRT chrome and content tests. + NSString *myBundlePath = [[NSBundle mainBundle] bundlePath]; + NSString *fxPath = [NSString stringWithFormat:@"%@%sfirefox-bin", + myBundlePath, APP_CONTENTS_PATH]; + if ([[NSFileManager defaultManager] fileExistsAtPath:fxPath]) { + return myBundlePath; + } + //we look for these flavors of Firefox, in this order NSArray* launchBinarySearchList = [NSArray arrayWithObjects: @"org.mozilla.nightly", @"org.mozilla.aurora", @"org.mozilla.firefox", nil]; //if they provided a manual override, use that. If they made an error, it will fail to launch if (alternateBinaryID != nil && ([alternateBinaryID length] > 0)) { binaryPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:alternateBinaryID];
new file mode 100644 --- /dev/null +++ b/webapprt/test/Makefile.in @@ -0,0 +1,17 @@ +# 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/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +DIRS += \ + chrome \ + content \ + $(NULL) + +include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/webapprt/test/chrome/Makefile.in @@ -0,0 +1,23 @@ +# 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/. + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = webapprt/test/chrome + +include $(DEPTH)/config/autoconf.mk +include $(topsrcdir)/config/rules.mk + +_BROWSER_TEST_FILES = \ + head.js \ + install.html \ + browser_sample.js \ + sample.webapp \ + sample.html \ + $(NULL) + +libs:: $(_BROWSER_TEST_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/webapprtChrome/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/webapprt/test/chrome/browser_sample.js @@ -0,0 +1,19 @@ +// This is a sample WebappRT chrome test. It's just a browser-chrome mochitest. + +function test() { + waitForExplicitFinish(); + ok(true, "true is true!"); + installWebapp("sample.webapp", undefined, function onInstall(appConfig) { + is(document.documentElement.getAttribute("title"), + appConfig.app.manifest.name, + "Window title should be webapp name"); + let content = document.getElementById("content"); + let msg = content.contentDocument.getElementById("msg"); + var observer = new MutationObserver(function (mutations) { + ok(/^Webapp getSelf OK:/.test(msg.textContent), + "The webapp should have successfully installed and updated its msg"); + finish(); + }); + observer.observe(msg, { childList: true }); + }); +}
new file mode 100644 --- /dev/null +++ b/webapprt/test/chrome/head.js @@ -0,0 +1,63 @@ +/* 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/. */ + +const INSTALL_URL = + "http://mochi.test:8888/webapprtChrome/webapprt/test/chrome/install.html"; + +Cu.import("resource://gre/modules/Services.jsm"); + +/** + * Installs the given webapp and navigates to it. + * + * @param manifestPath + * The path of the webapp's manifest relative to + * http://mochi.test:8888/webapprtChrome/webapprt/test/chrome/. + * @param parameters + * The value to pass as the "parameters" argument to + * mozIDOMApplicationRegistry.install, e.g., { receipts: ... }. Use + * undefined to pass nothing. + * @param callback + * Called when the newly installed webapp is navigated to. It's passed + * the webapp's config object. + */ +function installWebapp(manifestPath, parameters, onInstall) { + // Three steps: (1) Load install.html, (2) listen for webapprt-test-did- + // install to get the app config object, and then (3) listen for load of the + // webapp page to call the callback. webapprt-test-did-install will be + // broadcasted before install.html navigates to the webapp page. (This is due + // to some implementation details: WebappRT.jsm confirms the installation by + // calling DOMApplicationRegistry.confirmInstall, and then it immediately + // broadcasts webapprt-test-did-install. confirmInstall asynchronously + // notifies the mozApps consumer via onsuccess, which is when install.html + // navigates to the webapp page.) + + let content = document.getElementById("content"); + + Services.obs.addObserver(function observe(subj, topic, data) { + // step 2 + Services.obs.removeObserver(observe, "webapprt-test-did-install"); + let appConfig = JSON.parse(data); + + content.addEventListener("load", function onLoad(event) { + // step 3 + content.removeEventListener("load", onLoad, true); + let webappURL = appConfig.app.origin + appConfig.app.manifest.launch_path; + is(event.target.URL, webappURL, + "No other page should have loaded between installation and " + + "the webapp's page load: " + event.target.URL); + onInstall(appConfig); + }, true); + }, "webapprt-test-did-install", false); + + // step 1 + let args = [["manifestPath", manifestPath]]; + if (parameters !== undefined) { + args.push(["parameters", parameters]); + } + let queryStr = args.map(function ([key, val]) + key + "=" + encodeURIComponent(JSON.stringify(val))). + join("&"); + let installURL = INSTALL_URL + "?" + queryStr; + content.loadURI(installURL); +}
new file mode 100644 --- /dev/null +++ b/webapprt/test/chrome/install.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> + +<!-- + Chrome tests load this file to install their webapps. Pass manifestPath=path + in the query string to install the app with the manifest at + http://mochi.test:8888/webapprtChrome/webapprt/test/chrome/<path>. +--> + +<html> + <head> + <meta charset="utf-8"> + <script> + +function parseQueryStr() { + return window.location.search.substr(1).split("&"). + map(function (pairStr) pairStr.split("=")). + reduce(function (memo, [key, val]) { + memo[key] = JSON.parse(decodeURIComponent(val)); + return memo; + }, {}); +} + +function msg(str) { + document.getElementById("msg").textContent = str; +} + +function onLoad() { + var args = parseQueryStr(); + if (!args.manifestPath) { + msg("No manifest path given, so standing by."); + return; + } + var manifestURL = + "http://mochi.test:8888/webapprtChrome/webapprt/test/chrome/" + + args.manifestPath; + var installArgs = [manifestURL, args.parameters]; + msg("Installing webapp with arguments " + installArgs.toSource() + "..."); + var install = navigator.mozApps.install.apply(navigator.mozApps, installArgs); + install.onsuccess = function (event) { + msg("Webapp installed, now navigating to it."); + var testAppURL = install.result.origin + + install.result.manifest.launch_path; + window.location = testAppURL; + }; + install.onerror = function () { + msg("Webapp installation failed with " + install.error.name + + " for manifest " + manifestURL); + }; +} + + </script> + </head> + <body onload="onLoad();" onunload=""> + <p id="msg">Installation page waiting for page load...</p> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/webapprt/test/chrome/sample.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> + +<!-- + This is the webapp. It doesn't need to do anything in particular, just + whatever you want to test from your browser_ test file. +--> + +<html> + <head> + <meta charset="utf-8"> + <script> + +function onLoad() { + var msg = document.getElementById("msg"); + var self = navigator.mozApps.getSelf(); + self.onsuccess = function () { + msg.textContent = "Webapp getSelf OK: " + self.result; + }; + self.onerror = function () { + msg.textContent = "Webapp getSelf failed: " + self.error.name; + }; +} + + </script> + </head> + <body onload="onLoad();" onunload=""> + <p>This is the test webapp.</p> + <p id="msg">Webapp waiting for page load...</p> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/webapprt/test/chrome/sample.webapp @@ -0,0 +1,1 @@ +{"name": "Sample Test Webapp", "description": "A webapp that demonstrates how to make a WebappRT test.", "launch_path": "/webapprtChrome/webapprt/test/chrome/sample.html" } \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/webapprt/test/content/Makefile.in @@ -0,0 +1,22 @@ +# 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/. + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = webapprt/test/content + +include $(DEPTH)/config/autoconf.mk +include $(topsrcdir)/config/rules.mk + +_TEST_FILES = \ + helpers.js \ + webapprt_sample.html \ + sample.webapp \ + sample.html \ + $(NULL) + +libs:: $(_TEST_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/webapprt/test/content/helpers.js @@ -0,0 +1,57 @@ +/** + * Shows a message on the page. + * + * @param str + * The message to show. + */ +function msg(str) { + document.getElementById("msg").textContent = str; +} + +/** + * Installs the specified webapp and navigates to it in the iframe. + * + * @param manifestPath + * The path of the manifest relative to + * http://mochi.test:8888/tests/webapprt/test/content/. + * @param parameters + * The value to pass as the "parameters" argument to + * mozIDOMApplicationRegistry.install, e.g., { receipts: ... }. Use + * undefined to pass nothing. + */ +function installWebapp(manifestPath, parameters) { + var manifestURL = "http://mochi.test:8888/tests/webapprt/test/content/" + + manifestPath; + var installArgs = [manifestURL, parameters]; + msg("Installing webapp with arguments " + installArgs.toSource() + "..."); + var install = navigator.mozApps.install.apply(navigator.mozApps, installArgs); + install.onsuccess = function (event) { + msg("Webapp installed."); + var testAppURL = install.result.origin + + install.result.manifest.launch_path + + window.location.search; + document.getElementById("webapp-iframe").src = testAppURL; + }; + install.onerror = function () { + msg("Webapp installation failed with " + install.error.name + + " for manifest " + manifestURL); + }; +} + +/** + * If webapprt_foo.html is loaded in the window, this function installs the + * webapp whose manifest is named foo.webapp. + * + * @param parameters + * The value to pass as the "parameters" argument to + * mozIDOMApplicationRegistry.install, e.g., { receipts: ... }. Use + * undefined to pass nothing. + */ +function installOwnWebapp(parameters) { + var match = /webapprt_(.+)\.html$/.exec(window.location.pathname); + if (!match) { + throw new Error("Test URL is unconventional, so could not derive a " + + "manifest URL from it: " + window.location); + } + installWebapp(match[1] + ".webapp", parameters); +}
new file mode 100644 --- /dev/null +++ b/webapprt/test/content/sample.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> + +<!-- + This file is the actual test. It's a webapp installed by webapprt_sample.html + and loaded into its iframe. It's just a plain mochitest. +--> + +<html> + <head> + <meta charset="utf-8"> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + <body> + <p id="display"> + This is the test webapp. + </p> + <div id="content" style="display: none"></div> + <pre id="test"> + <script> + +SimpleTest.waitForExplicitFinish(); + +ok(true, "true is true!"); + +var self = navigator.mozApps.getSelf(); +self.onsuccess = function () { + ok(true, "onsuccess should be called"); + ok(self.result, "result should be nonnull"); + ok(self.result.manifest, "manifest should be nonnull"); + is(self.result.manifest.name, "Sample Test Webapp", "manifest.name"); + SimpleTest.finish(); +}; +self.onerror = function () { + ok(false, "onerror should not be called"); + SimpleTest.finish(); +}; + + </script> + </pre> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/webapprt/test/content/sample.webapp @@ -0,0 +1,1 @@ +{"name": "Sample Test Webapp", "description": "A webapp that demonstrates how to make a WebappRT test.", "launch_path": "/tests/webapprt/test/content/sample.html" } \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/webapprt/test/content/webapprt_sample.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> + +<!-- + Since its name is prefixed with webapprt_, this file is picked up by the + mochitest harness. Once loaded, it installs the webapp, and when the + installation completes, it loads the webapp into an iframe. The webapp is + the actual test; see sample.html. +--> + +<html> + <head> + <meta charset="utf-8"> + <script src="helpers.js"></script> + <script> + +// If your installation page needs to do anything other than call +// installOwnWebapp, you can do it here. + + </script> + </head> + <body onload="installOwnWebapp();"> + <p id="msg">Installation page waiting for page load...</p> + <iframe id="webapp-iframe" width="100%" height="93%"></iframe> + </body> +</html>