From 341475f778a73fa715ec84a6a914d525623dba0b Mon Sep 17 00:00:00 2001 From: Stefanos Kornilios Mitsis Poiitidis Date: Tue, 28 Jul 2015 14:40:27 +0200 Subject: [PATCH] nacl: Redirect printf/puts, basic html loader --- core/nacl/nacl.cpp | 33 ++- core/types.h | 6 + shell/nacl/background.js | 40 ++++ shell/nacl/common.js | 474 +++++++++++++++++++++++++++++++++++++++ shell/nacl/example.js | 25 +++ shell/nacl/icon128.png | Bin 0 -> 9897 bytes shell/nacl/index.html | 27 +++ 7 files changed, 601 insertions(+), 4 deletions(-) create mode 100644 shell/nacl/background.js create mode 100644 shell/nacl/common.js create mode 100644 shell/nacl/example.js create mode 100644 shell/nacl/icon128.png create mode 100644 shell/nacl/index.html diff --git a/core/nacl/nacl.cpp b/core/nacl/nacl.cpp index 1590a7dce..52d9a11ce 100644 --- a/core/nacl/nacl.cpp +++ b/core/nacl/nacl.cpp @@ -21,13 +21,19 @@ int msgboxf(const wchar* text,unsigned int type,...) return 0; } +int dc_init(int argc,wchar* argv[]); +void dc_run(); + namespace hello_world { + class HelloWorldInstance; + HelloWorldInstance* rei_instance; class HelloWorldInstance : public pp::Instance { public: explicit HelloWorldInstance(PP_Instance instance) : pp::Instance(instance) { - printf("HelloWorldInstance.\n"); + rei_instance = this; + printf("Reicast NACL loaded\n"); } virtual ~HelloWorldInstance() {} @@ -47,13 +53,19 @@ void HelloWorldInstance::HandleMessage(const pp::Var& var_message) { return; } - pp::Var return_var; + pp::Var return_var = "starting"; // Post the return result back to the browser. Note that HandleMessage() is // always called on the main thread, so it's OK to post the return message // directly from here. The return post is asynhronous: PostMessage returns // immediately. PostMessage(return_var); + + char *Args[3]; + Args[0] = "dc"; + + dc_init(1,Args); + dc_run(); } /// The Module class. The browser calls the CreateInstance() method to create @@ -63,7 +75,7 @@ void HelloWorldInstance::HandleMessage(const pp::Var& var_message) { class HelloWorldModule : public pp::Module { public: HelloWorldModule() : pp::Module() { - printf("Got here.\n"); + } virtual ~HelloWorldModule() {} @@ -77,6 +89,19 @@ class HelloWorldModule : public pp::Module { }; } // namespace hello_world +int nacl_printf(const wchar* text,...) +{ + va_list args; + + wchar temp[2048]; + va_start(args, text); + int rv = vsprintf(temp, text, args); + va_end(args); + + if (hello_world::rei_instance) + hello_world::rei_instance->PostMessage(pp::Var(temp)); + return rv; +} namespace pp { /// Factory function called by the browser when the module is first loaded. @@ -105,7 +130,7 @@ void os_SetWindowText(const char * text) { } void os_DoEvents() { - + } diff --git a/core/types.h b/core/types.h index 37070742a..55e533770 100644 --- a/core/types.h +++ b/core/types.h @@ -430,6 +430,12 @@ struct maple_device_instance #include #include +#if defined(TARGET_NACL32) + int nacl_printf(const wchar* Text,...); + #define printf nacl_printf + #define puts(X) printf("%s\n", X) +#endif + //includes from c++rt #include #include diff --git a/shell/nacl/background.js b/shell/nacl/background.js new file mode 100644 index 000000000..5c3b1b7c9 --- /dev/null +++ b/shell/nacl/background.js @@ -0,0 +1,40 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function makeURL(toolchain, config) { + return 'index.html?tc=' + toolchain + '&config=' + config; +} + +function createWindow(url) { + console.log('loading ' + url); + chrome.app.window.create(url, { + width: 1024, + height: 800, + frame: 'none' + }); +} + +function onLaunched(launchData) { + // Send and XHR to get the URL to load from a configuration file. + // Normally you won't need to do this; just call: + // + // chrome.app.window.create('', {...}); + // + // In the SDK we want to be able to load different URLs (for different + // toolchain/config combinations) from the commandline, so we to read + // this information from the file "run_package_config". + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'run_package_config', true); + xhr.onload = function() { + var toolchain_config = this.responseText.split(' '); + createWindow(makeURL.apply(null, toolchain_config)); + }; + xhr.onerror = function() { + // Can't find the config file, just load the default. + createWindow('index.html'); + }; + xhr.send(); +} + +chrome.app.runtime.onLaunched.addListener(onLaunched); diff --git a/shell/nacl/common.js b/shell/nacl/common.js new file mode 100644 index 000000000..a70001551 --- /dev/null +++ b/shell/nacl/common.js @@ -0,0 +1,474 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Set to true when the Document is loaded IFF "test=true" is in the query +// string. +var isTest = false; + +// Set to true when loading a "Release" NaCl module, false when loading a +// "Debug" NaCl module. +var isRelease = true; + +// Javascript module pattern: +// see http://en.wikipedia.org/wiki/Unobtrusive_JavaScript#Namespaces +// In essence, we define an anonymous function which is immediately called and +// returns a new object. The new object contains only the exported definitions; +// all other definitions in the anonymous function are inaccessible to external +// code. +var common = (function() { + + function isHostToolchain(tool) { + return tool == 'win' || tool == 'linux' || tool == 'mac'; + } + + /** + * Return the mime type for NaCl plugin. + * + * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. + * @return {string} The mime-type for the kind of NaCl plugin matching + * the given toolchain. + */ + function mimeTypeForTool(tool) { + // For NaCl modules use application/x-nacl. + var mimetype = 'application/x-nacl'; + if (isHostToolchain(tool)) { + // For non-NaCl PPAPI plugins use the x-ppapi-debug/release + // mime type. + if (isRelease) + mimetype = 'application/x-ppapi-release'; + else + mimetype = 'application/x-ppapi-debug'; + } else if (tool == 'pnacl') { + mimetype = 'application/x-pnacl'; + } + return mimetype; + } + + /** + * Check if the browser supports NaCl plugins. + * + * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. + * @return {bool} True if the browser supports the type of NaCl plugin + * produced by the given toolchain. + */ + function browserSupportsNaCl(tool) { + // Assume host toolchains always work with the given browser. + // The below mime-type checking might not work with + // --register-pepper-plugins. + if (isHostToolchain(tool)) { + return true; + } + var mimetype = mimeTypeForTool(tool); + return navigator.mimeTypes[mimetype] !== undefined; + } + + /** + * Inject a script into the DOM, and call a callback when it is loaded. + * + * @param {string} url The url of the script to load. + * @param {Function} onload The callback to call when the script is loaded. + * @param {Function} onerror The callback to call if the script fails to load. + */ + function injectScript(url, onload, onerror) { + var scriptEl = document.createElement('script'); + scriptEl.type = 'text/javascript'; + scriptEl.src = url; + scriptEl.onload = onload; + if (onerror) { + scriptEl.addEventListener('error', onerror, false); + } + document.head.appendChild(scriptEl); + } + + /** + * Run all tests for this example. + * + * @param {Object} moduleEl The module DOM element. + */ + function runTests(moduleEl) { + console.log('runTests()'); + common.tester = new Tester(); + + // All NaCl SDK examples are OK if the example exits cleanly; (i.e. the + // NaCl module returns 0 or calls exit(0)). + // + // Without this exception, the browser_tester thinks that the module + // has crashed. + common.tester.exitCleanlyIsOK(); + + common.tester.addAsyncTest('loaded', function(test) { + test.pass(); + }); + + if (typeof window.addTests !== 'undefined') { + window.addTests(); + } + + common.tester.waitFor(moduleEl); + common.tester.run(); + } + + /** + * Create the Native Client element as a child of the DOM element + * named "listener". + * + * @param {string} name The name of the example. + * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. + * @param {string} path Directory name where .nmf file can be found. + * @param {number} width The width to create the plugin. + * @param {number} height The height to create the plugin. + * @param {Object} attrs Dictionary of attributes to set on the module. + */ + function createNaClModule(name, tool, path, width, height, attrs) { + var moduleEl = document.createElement('embed'); + moduleEl.setAttribute('name', 'nacl_module'); + moduleEl.setAttribute('id', 'nacl_module'); + moduleEl.setAttribute('width', width); + moduleEl.setAttribute('height', height); + moduleEl.setAttribute('path', path); + moduleEl.setAttribute('src', path + '/' + name + '.nmf'); + + // Add any optional arguments + if (attrs) { + for (var key in attrs) { + moduleEl.setAttribute(key, attrs[key]); + } + } + + var mimetype = mimeTypeForTool(tool); + moduleEl.setAttribute('type', mimetype); + + // The element is wrapped inside a
, which has both a 'load' + // and a 'message' event listener attached. This wrapping method is used + // instead of attaching the event listeners directly to the element + // to ensure that the listeners are active before the NaCl module 'load' + // event fires. + var listenerDiv = document.getElementById('listener'); + listenerDiv.appendChild(moduleEl); + + // Request the offsetTop property to force a relayout. As of Apr 10, 2014 + // this is needed if the module is being loaded on a Chrome App's + // background page (see crbug.com/350445). + moduleEl.offsetTop; + + // Host plugins don't send a moduleDidLoad message. We'll fake it here. + var isHost = isHostToolchain(tool); + if (isHost) { + window.setTimeout(function() { + moduleEl.readyState = 1; + moduleEl.dispatchEvent(new CustomEvent('loadstart')); + moduleEl.readyState = 4; + moduleEl.dispatchEvent(new CustomEvent('load')); + moduleEl.dispatchEvent(new CustomEvent('loadend')); + }, 100); // 100 ms + } + + // This is code that is only used to test the SDK. + if (isTest) { + var loadNaClTest = function() { + injectScript('nacltest.js', function() { + runTests(moduleEl); + }); + }; + + // Try to load test.js for the example. Whether or not it exists, load + // nacltest.js. + injectScript('test.js', loadNaClTest, loadNaClTest); + } + } + + /** + * Add the default "load" and "message" event listeners to the element with + * id "listener". + * + * The "load" event is sent when the module is successfully loaded. The + * "message" event is sent when the naclModule posts a message using + * PPB_Messaging.PostMessage() (in C) or pp::Instance().PostMessage() (in + * C++). + */ + function attachDefaultListeners() { + var listenerDiv = document.getElementById('listener'); + listenerDiv.addEventListener('load', moduleDidLoad, true); + listenerDiv.addEventListener('message', handleMessage, true); + listenerDiv.addEventListener('error', handleError, true); + listenerDiv.addEventListener('crash', handleCrash, true); + if (typeof window.attachListeners !== 'undefined') { + window.attachListeners(); + } + } + + /** + * Called when the NaCl module fails to load. + * + * This event listener is registered in createNaClModule above. + */ + function handleError(event) { + // We can't use common.naclModule yet because the module has not been + // loaded. + var moduleEl = document.getElementById('nacl_module'); + updateStatus('ERROR [' + moduleEl.lastError + ']'); + } + + /** + * Called when the Browser can not communicate with the Module + * + * This event listener is registered in attachDefaultListeners above. + */ + function handleCrash(event) { + if (common.naclModule.exitStatus == -1) { + updateStatus('CRASHED'); + } else { + updateStatus('EXITED [' + common.naclModule.exitStatus + ']'); + } + if (typeof window.handleCrash !== 'undefined') { + window.handleCrash(common.naclModule.lastError); + } + } + + /** + * Called when the NaCl module is loaded. + * + * This event listener is registered in attachDefaultListeners above. + */ + function moduleDidLoad() { + common.naclModule = document.getElementById('nacl_module'); + updateStatus('RUNNING'); + + if (typeof window.moduleDidLoad !== 'undefined') { + window.moduleDidLoad(); + } + } + + /** + * Hide the NaCl module's embed element. + * + * We don't want to hide by default; if we do, it is harder to determine that + * a plugin failed to load. Instead, call this function inside the example's + * "moduleDidLoad" function. + * + */ + function hideModule() { + // Setting common.naclModule.style.display = "None" doesn't work; the + // module will no longer be able to receive postMessages. + common.naclModule.style.height = '0'; + } + + /** + * Remove the NaCl module from the page. + */ + function removeModule() { + common.naclModule.parentNode.removeChild(common.naclModule); + common.naclModule = null; + } + + /** + * Return true when |s| starts with the string |prefix|. + * + * @param {string} s The string to search. + * @param {string} prefix The prefix to search for in |s|. + */ + function startsWith(s, prefix) { + // indexOf would search the entire string, lastIndexOf(p, 0) only checks at + // the first index. See: http://stackoverflow.com/a/4579228 + return s.lastIndexOf(prefix, 0) === 0; + } + + /** Maximum length of logMessageArray. */ + var kMaxLogMessageLength = 20; + + /** An array of messages to display in the element with id "log". */ + var logMessageArray = []; + + /** + * Add a message to an element with id "log". + * + * This function is used by the default "log:" message handler. + * + * @param {string} message The message to log. + */ + function logMessage(message) { + logMessageArray.push(message); + if (logMessageArray.length > kMaxLogMessageLength) + logMessageArray.shift(); + + document.getElementById('log').textContent = logMessageArray.join('\n'); + console.log(message); + } + + /** + */ + var defaultMessageTypes = { + 'alert': alert, + 'log': logMessage + }; + + /** + * Called when the NaCl module sends a message to JavaScript (via + * PPB_Messaging.PostMessage()) + * + * This event listener is registered in createNaClModule above. + * + * @param {Event} message_event A message event. message_event.data contains + * the data sent from the NaCl module. + */ + function handleMessage(message_event) { + if (typeof message_event.data === 'string') { + for (var type in defaultMessageTypes) { + if (defaultMessageTypes.hasOwnProperty(type)) { + if (startsWith(message_event.data, type + ':')) { + func = defaultMessageTypes[type]; + func(message_event.data.slice(type.length + 1)); + return; + } + } + } + } + + if (typeof window.handleMessage !== 'undefined') { + window.handleMessage(message_event); + return; + } + + logMessage('Unhandled message: ' + message_event.data); + } + + /** + * Called when the DOM content has loaded; i.e. the page's document is fully + * parsed. At this point, we can safely query any elements in the document via + * document.querySelector, document.getElementById, etc. + * + * @param {string} name The name of the example. + * @param {string} tool The name of the toolchain, e.g. "glibc", "newlib" etc. + * @param {string} path Directory name where .nmf file can be found. + * @param {number} width The width to create the plugin. + * @param {number} height The height to create the plugin. + * @param {Object} attrs Optional dictionary of additional attributes. + */ + function domContentLoaded(name, tool, path, width, height, attrs) { + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + updateStatus('Page loaded.'); + if (!browserSupportsNaCl(tool)) { + updateStatus( + 'Browser does not support NaCl (' + tool + '), or NaCl is disabled'); + } else if (common.naclModule == null) { + updateStatus('Creating embed: ' + tool); + + // We use a non-zero sized embed to give Chrome space to place the bad + // plug-in graphic, if there is a problem. + width = typeof width !== 'undefined' ? width : 200; + height = typeof height !== 'undefined' ? height : 200; + attachDefaultListeners(); + createNaClModule(name, tool, path, width, height, attrs); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus('Waiting.'); + } + } + + /** Saved text to display in the element with id 'statusField'. */ + var statusText = 'NO-STATUSES'; + + /** + * Set the global status message. If the element with id 'statusField' + * exists, then set its HTML to the status message as well. + * + * @param {string} opt_message The message to set. If null or undefined, then + * set element 'statusField' to the message from the last call to + * updateStatus. + */ + function updateStatus(opt_message) { + if (opt_message) { + statusText = opt_message; + } + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + + // The symbols to export. + return { + /** A reference to the NaCl module, once it is loaded. */ + naclModule: null, + + attachDefaultListeners: attachDefaultListeners, + domContentLoaded: domContentLoaded, + createNaClModule: createNaClModule, + hideModule: hideModule, + removeModule: removeModule, + logMessage: logMessage, + updateStatus: updateStatus + }; + +}()); + +// Listen for the DOM content to be loaded. This event is fired when parsing of +// the page's document has finished. +document.addEventListener('DOMContentLoaded', function() { + var body = document.body; + + // The data-* attributes on the body can be referenced via body.dataset. + if (body.dataset) { + var loadFunction; + if (!body.dataset.customLoad) { + loadFunction = common.domContentLoaded; + } else if (typeof window.domContentLoaded !== 'undefined') { + loadFunction = window.domContentLoaded; + } + + // From https://developer.mozilla.org/en-US/docs/DOM/window.location + var searchVars = {}; + if (window.location.search.length > 1) { + var pairs = window.location.search.substr(1).split('&'); + for (var key_ix = 0; key_ix < pairs.length; key_ix++) { + var keyValue = pairs[key_ix].split('='); + searchVars[unescape(keyValue[0])] = + keyValue.length > 1 ? unescape(keyValue[1]) : ''; + } + } + + if (loadFunction) { + var toolchains = body.dataset.tools.split(' '); + var configs = body.dataset.configs.split(' '); + + var attrs = {}; + if (body.dataset.attrs) { + var attr_list = body.dataset.attrs.split(' '); + for (var key in attr_list) { + var attr = attr_list[key].split('='); + var key = attr[0]; + var value = attr[1]; + attrs[key] = value; + } + } + + var tc = toolchains.indexOf(searchVars.tc) !== -1 ? + searchVars.tc : toolchains[0]; + + // If the config value is included in the search vars, use that. + // Otherwise default to Release if it is valid, or the first value if + // Release is not valid. + if (configs.indexOf(searchVars.config) !== -1) + var config = searchVars.config; + else if (configs.indexOf('Release') !== -1) + var config = 'Release'; + else + var config = configs[0]; + + var pathFormat = body.dataset.path; + var path = pathFormat.replace('{tc}', tc).replace('{config}', config); + + isTest = searchVars.test === 'true'; + isRelease = path.toLowerCase().indexOf('release') != -1; + + loadFunction(body.dataset.name, tc, path, body.dataset.width, + body.dataset.height, attrs); + } + } +}); diff --git a/shell/nacl/example.js b/shell/nacl/example.js new file mode 100644 index 000000000..3de6ad5aa --- /dev/null +++ b/shell/nacl/example.js @@ -0,0 +1,25 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function $(id) { + return document.getElementById(id); +} + +// Add event listeners after the NaCl module has loaded. These listeners will +// forward messages to the NaCl module via postMessage() +function attachListeners() { + +} + +// Handle a message coming from the NaCl module. +function handleMessage(event) { + console.log(event); + $("status").textContent = event.data; + $("log").textContent += event.data; + + if (!(event.data instanceof Array)) + return; + if (event.data.length != 2) + return; +} diff --git a/shell/nacl/icon128.png b/shell/nacl/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..4e966a7523851af14908b6831473912c24a623f1 GIT binary patch literal 9897 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 + + + + + + reicast nacl + + + + + +

reicast nacl

+

Status: NO-STATUS

+ + +
+
+ +