summaryrefslogtreecommitdiffstats
path: root/native_client_sdk
diff options
context:
space:
mode:
authorbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-22 00:40:09 +0000
committerbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-22 00:40:09 +0000
commitc56a9dc7cc34d76e9588efee5ad8407f1c587128 (patch)
tree28bdd0560a8e94b12864cfb322f22153667c6a17 /native_client_sdk
parenta139481bb3c67824248e8371616637faea0bc265 (diff)
downloadchromium_src-c56a9dc7cc34d76e9588efee5ad8407f1c587128.zip
chromium_src-c56a9dc7cc34d76e9588efee5ad8407f1c587128.tar.gz
chromium_src-c56a9dc7cc34d76e9588efee5ad8407f1c587128.tar.bz2
[NaCl SDK] Google Drive example
Also: * generate app permissions automatically from examples * stop using --incognito in make run * use --user-data-dir instead, which is cleaned up in "make clean" * share CHROME_ARGS with "make run_package" and "make run" BUG=none R=noelallen@chromium.org Review URL: https://codereview.chromium.org/14500010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@201407 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
-rwxr-xr-xnative_client_sdk/src/build_tools/build_app.py13
-rw-r--r--native_client_sdk/src/build_tools/generate_make.py1
-rwxr-xr-xnative_client_sdk/src/build_tools/parse_dsc.py6
-rw-r--r--native_client_sdk/src/build_tools/sdk_files.list9
-rw-r--r--native_client_sdk/src/examples/common.js1
-rw-r--r--native_client_sdk/src/examples/demo/drive/drive.cc564
-rw-r--r--native_client_sdk/src/examples/demo/drive/example.dsc30
-rw-r--r--native_client_sdk/src/examples/demo/drive/example.js70
-rw-r--r--native_client_sdk/src/examples/demo/drive/index.html51
-rw-r--r--native_client_sdk/src/examples/resources/manifest.json.template7
-rw-r--r--native_client_sdk/src/tools/common.mk8
11 files changed, 751 insertions, 9 deletions
diff --git a/native_client_sdk/src/build_tools/build_app.py b/native_client_sdk/src/build_tools/build_app.py
index f9183aa..81af599 100755
--- a/native_client_sdk/src/build_tools/build_app.py
+++ b/native_client_sdk/src/build_tools/build_app.py
@@ -104,16 +104,17 @@ def main(args):
toolchains=toolchains, configs=[config],
first_toolchain=True)
+ # Collect permissions from each example, and aggregate them.
+ all_permissions = []
+ for _, project in parse_dsc.GenerateProjects(tree):
+ all_permissions.extend(project.get('PERMISSIONS', []))
+
template_dict = {
'name': 'Native Client SDK',
'description':
'Native Client SDK examples, showing API use and key concepts.',
- # TODO(binji): generate list of permissions from examples' DSC files.
- 'permissions': [
- 'fullscreen',
- 'pointerLock',
- 'unlimitedStorage',
- ],
+ 'key': False, # manifests with "key" are rejected when uploading to CWS.
+ 'permissions': all_permissions,
'version': build_version.ChromeVersionNoTrunk()
}
easy_template.RunTemplateFile(
diff --git a/native_client_sdk/src/build_tools/generate_make.py b/native_client_sdk/src/build_tools/generate_make.py
index f0a74e0..2c87f7c 100644
--- a/native_client_sdk/src/build_tools/generate_make.py
+++ b/native_client_sdk/src/build_tools/generate_make.py
@@ -141,6 +141,7 @@ def GenerateManifest(srcroot, dstroot, desc):
replace = {
'name': desc['TITLE'],
'description': '%s Example' % desc['TITLE'],
+ 'key': True,
'permissions': desc.get('PERMISSIONS', []),
'version': build_version.ChromeVersionNoTrunk()
}
diff --git a/native_client_sdk/src/build_tools/parse_dsc.py b/native_client_sdk/src/build_tools/parse_dsc.py
index 19c220c..fb04af8 100755
--- a/native_client_sdk/src/build_tools/parse_dsc.py
+++ b/native_client_sdk/src/build_tools/parse_dsc.py
@@ -191,6 +191,12 @@ def PrintProjectTree(tree):
print '\t' + val['NAME']
+def GenerateProjects(tree):
+ for key in tree:
+ for val in tree[key]:
+ yield key, val
+
+
def main(argv):
parser = optparse.OptionParser()
parser.add_option('-e', '--experimental',
diff --git a/native_client_sdk/src/build_tools/sdk_files.list b/native_client_sdk/src/build_tools/sdk_files.list
index 12bc688..4ec57ae 100644
--- a/native_client_sdk/src/build_tools/sdk_files.list
+++ b/native_client_sdk/src/build_tools/sdk_files.list
@@ -103,6 +103,15 @@ examples/api/websocket/manifest.json
examples/api/websocket/websocket.cc
examples/button_close_hover.png
examples/button_close.png
+examples/demo/drive/background.js
+examples/demo/drive/common.js
+examples/demo/drive/drive.cc
+examples/demo/drive/example.js
+examples/demo/drive/icon128.png
+examples/demo/drive/index.html
+[win]examples/demo/drive/make.bat
+examples/demo/drive/Makefile
+examples/demo/drive/manifest.json
[win]examples/demo/make.bat
examples/demo/Makefile
examples/demo/nacl_io/background.js
diff --git a/native_client_sdk/src/examples/common.js b/native_client_sdk/src/examples/common.js
index 79988ed..7a4400d2 100644
--- a/native_client_sdk/src/examples/common.js
+++ b/native_client_sdk/src/examples/common.js
@@ -185,6 +185,7 @@ var common = (function () {
if (startsWith(message_event.data, type + ':')) {
func = defaultMessageTypes[type];
func(message_event.data.slice(type.length + 1));
+ return;
}
}
}
diff --git a/native_client_sdk/src/examples/demo/drive/drive.cc b/native_client_sdk/src/examples/demo/drive/drive.cc
new file mode 100644
index 0000000..20449eb
--- /dev/null
+++ b/native_client_sdk/src/examples/demo/drive/drive.cc
@@ -0,0 +1,564 @@
+// 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.
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "json/reader.h"
+#include "json/writer.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/url_loader.h"
+#include "ppapi/cpp/url_request_info.h"
+#include "ppapi/cpp/url_response_info.h"
+#include "ppapi/cpp/var.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "ppapi/utility/threading/simple_thread.h"
+
+namespace {
+
+// When we upload files, we also upload the metadata at the same time. To do so,
+// we use the mimetype multipart/related. This mimetype requires specifying a
+// boundary between the JSON metadata and the file content.
+const char kBoundary[] = "NACL_BOUNDARY_600673";
+
+// This is a simple implementation of JavaScript's encodeUriComponent. We
+// assume the data is already UTF-8. See
+// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent.
+std::string EncodeUriComponent(const std::string& s) {
+ char hex[] = "0123456789ABCDEF";
+ std::string result;
+ for (size_t i = 0; i < s.length(); ++i) {
+ char c = s[i];
+ if (isalpha(c) || isdigit(c) || strchr("-_.!~*'()", c)) {
+ result += c;
+ } else {
+ result += '%';
+ result += hex[(c >> 4) & 0xf];
+ result += hex[c & 0xf];
+ }
+ }
+ return result;
+}
+
+std::string IntToString(int x) {
+ char buffer[32];
+ snprintf(&buffer[0], 32, "%d", x);
+ return &buffer[0];
+}
+
+void AddQueryParameter(std::string* s,
+ const std::string& key,
+ const std::string& value,
+ bool first) {
+ *s += first ? '?' : '&';
+ *s += EncodeUriComponent(key);
+ *s += '=';
+ *s += EncodeUriComponent(value);
+}
+
+void AddQueryParameter(std::string* s,
+ const std::string& key,
+ int value,
+ bool first) {
+ AddQueryParameter(s, key, IntToString(value), first);
+}
+
+void AddAuthTokenHeader(std::string* s, const std::string& auth_token) {
+ *s += "Authorization: Bearer ";
+ *s += auth_token;
+ *s += "\n";
+}
+
+void AddHeader(std::string* s, const char* key, const std::string& value) {
+ *s += key;
+ *s += ": ";
+ *s += value;
+ *s += "\n";
+}
+
+} // namespace
+
+//
+// ReadUrl
+//
+struct ReadUrlParams {
+ std::string url;
+ std::string method;
+ std::string request_headers;
+ std::string request_body;
+};
+
+// This function blocks so it needs to be called off the main thread.
+int32_t ReadUrl(pp::Instance* instance,
+ const ReadUrlParams& params,
+ std::string* output) {
+ pp::URLRequestInfo url_request(instance);
+ pp::URLLoader url_loader(instance);
+
+ url_request.SetURL(params.url);
+ url_request.SetMethod(params.method);
+ url_request.SetHeaders(params.request_headers);
+ url_request.SetRecordDownloadProgress(true);
+ if (params.request_body.size()) {
+ url_request.AppendDataToBody(params.request_body.data(),
+ params.request_body.size());
+ }
+
+ int32_t result = url_loader.Open(url_request, pp::BlockUntilComplete());
+ if (result != PP_OK) {
+ return result;
+ }
+
+ pp::URLResponseInfo url_response = url_loader.GetResponseInfo();
+ if (url_response.GetStatusCode() != 200)
+ return PP_ERROR_FAILED;
+
+ output->clear();
+
+ int64_t bytes_received = 0;
+ int64_t total_bytes_to_be_received = 0;
+ if (url_loader.GetDownloadProgress(&bytes_received,
+ &total_bytes_to_be_received)) {
+ if (total_bytes_to_be_received > 0) {
+ output->reserve(total_bytes_to_be_received);
+ }
+ }
+
+ url_request.SetRecordDownloadProgress(false);
+
+ const size_t kReadBufferSize = 16 * 1024;
+ uint8_t* buffer_ = new uint8_t[kReadBufferSize];
+
+ do {
+ result = url_loader.ReadResponseBody(
+ buffer_, kReadBufferSize, pp::BlockUntilComplete());
+ if (result > 0) {
+ assert(result <= kReadBufferSize);
+ size_t num_bytes = result;
+ output->insert(output->end(), buffer_, buffer_ + num_bytes);
+ }
+ } while (result > 0);
+
+ delete[] buffer_;
+
+ return result;
+}
+
+//
+// ListFiles
+//
+// This is a simplistic implementation of the files.list method defined here:
+// https://developers.google.com/drive/v2/reference/files/list
+//
+struct ListFilesParams {
+ int max_results;
+ std::string page_token;
+ std::string query;
+};
+
+int32_t ListFiles(pp::Instance* instance,
+ const std::string& auth_token,
+ const ListFilesParams& params,
+ Json::Value* root) {
+ static const char base_url[] = "https://www.googleapis.com/drive/v2/files";
+
+ ReadUrlParams p;
+ p.method = "GET";
+ p.url = base_url;
+ AddQueryParameter(&p.url, "maxResults", params.max_results, true);
+ if (params.page_token.length())
+ AddQueryParameter(&p.url, "pageToken", params.page_token, false);
+ AddQueryParameter(&p.url, "q", params.query, false);
+ // Request a "partial response". See
+ // https://developers.google.com/drive/performance#partial for more
+ // information.
+ AddQueryParameter(&p.url, "fields", "items(id,downloadUrl)", false);
+ AddAuthTokenHeader(&p.request_headers, auth_token);
+
+ std::string output;
+ int32_t result = ReadUrl(instance, p, &output);
+ if (result != PP_OK) {
+ return result;
+ }
+
+ Json::Reader reader(Json::Features::strictMode());
+ if (!reader.parse(output, *root, false)) {
+ return PP_ERROR_FAILED;
+ }
+
+ return PP_OK;
+}
+
+//
+// InsertFile
+//
+// This is a simplistic implementation of the files.update and files.insert
+// methods defined here:
+// https://developers.google.com/drive/v2/reference/files/insert
+// https://developers.google.com/drive/v2/reference/files/update
+//
+struct InsertFileParams {
+ // If file_id is empty, create a new file (files.insert). If file_id is not
+ // empty, update that file (files.update)
+ std::string file_id;
+ std::string content;
+ std::string description;
+ std::string mime_type;
+ std::string title;
+};
+
+std::string BuildRequestBody(const InsertFileParams& params) {
+ // This generates the multipart-upload request body for InsertFile. See
+ // https://developers.google.com/drive/manage-uploads#multipart for more
+ // information.
+ std::string result;
+ result += "--";
+ result += kBoundary;
+ result += "\nContent-Type: application/json; charset=UTF-8\n\n";
+
+ Json::Value value(Json::objectValue);
+ if (!params.description.empty())
+ value["description"] = Json::Value(params.description);
+
+ if (!params.mime_type.empty())
+ value["mimeType"] = Json::Value(params.mime_type);
+
+ if (!params.title.empty())
+ value["title"] = Json::Value(params.title);
+
+ Json::FastWriter writer;
+ std::string metadata = writer.write(value);
+
+ result += metadata;
+ result += "--";
+ result += kBoundary;
+ result += "\nContent-Type: ";
+ result += params.mime_type;
+ result += "\n\n";
+ result += params.content;
+ result += "\n--";
+ result += kBoundary;
+ result += "--";
+ return result;
+}
+
+int32_t InsertFile(pp::Instance* instance,
+ const std::string& auth_token,
+ const InsertFileParams& params,
+ Json::Value* root) {
+ static const char base_url[] =
+ "https://www.googleapis.com/upload/drive/v2/files";
+ const char* method = "POST";
+
+ ReadUrlParams p;
+ p.url = base_url;
+
+ // If file_id is defined, we are actually updating an existing file.
+ if (!params.file_id.empty()) {
+ p.url += "/";
+ p.url += params.file_id;
+ p.method = "PUT";
+ } else {
+ p.method = "POST";
+ }
+
+ // We always use the multipart upload interface, but see
+ // https://developers.google.com/drive/manage-uploads for other
+ // options.
+ AddQueryParameter(&p.url, "uploadType", "multipart", true);
+ // Request a "partial response". See
+ // https://developers.google.com/drive/performance#partial for more
+ // information.
+ AddQueryParameter(&p.url, "fields", "id,downloadUrl", false);
+ AddAuthTokenHeader(&p.request_headers, auth_token);
+ AddHeader(&p.request_headers,
+ "Content-Type",
+ std::string("multipart/related; boundary=") + kBoundary + "\n");
+ p.request_body = BuildRequestBody(params);
+
+ std::string output;
+ int32_t result = ReadUrl(instance, p, &output);
+ if (result != PP_OK) {
+ return result;
+ }
+
+ Json::Reader reader(Json::Features::strictMode());
+ if (!reader.parse(output, *root, false)) {
+ return PP_ERROR_FAILED;
+ }
+
+ return PP_OK;
+}
+
+//
+// Instance
+//
+class Instance : public pp::Instance {
+ public:
+ Instance(PP_Instance instance);
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+ virtual void HandleMessage(const pp::Var& var_message);
+
+ void PostMessagef(const char* format, ...);
+
+ private:
+ void ThreadSetAuthToken(int32_t, const std::string& auth_token);
+ void ThreadRequestThunk(int32_t);
+ bool ThreadRequest();
+ bool ThreadGetFileMetadata(const char* title, Json::Value* metadata);
+ bool ThreadCreateFile(const char* title,
+ const char* description,
+ const char* content,
+ Json::Value* metadata);
+ bool ThreadUpdateFile(const std::string& file_id,
+ const std::string& content,
+ Json::Value* metadata);
+ bool ThreadDownloadFile(const Json::Value& metadata, std::string* output);
+ bool GetMetadataKey(const Json::Value& metadata,
+ const char* key,
+ std::string* output);
+
+ pp::SimpleThread worker_thread_;
+ pp::CompletionCallbackFactory<Instance> callback_factory_;
+ std::string auth_token_;
+ bool is_processing_request_;
+};
+
+Instance::Instance(PP_Instance instance)
+ : pp::Instance(instance),
+ callback_factory_(this),
+ worker_thread_(this),
+ is_processing_request_(false) {}
+
+bool Instance::Init(uint32_t /*argc*/,
+ const char * [] /*argn*/,
+ const char * [] /*argv*/) {
+ worker_thread_.Start();
+ return true;
+}
+
+void Instance::HandleMessage(const pp::Var& var_message) {
+ const char kTokenMessage[] = "token:";
+ const size_t kTokenMessageLen = strlen(kTokenMessage);
+ const char kGetFileMessage[] = "getFile";
+
+ if (!var_message.is_string()) {
+ return;
+ }
+
+ std::string message = var_message.AsString();
+ printf("Got message: \"%s\"\n", message.c_str());
+ if (message.compare(0, kTokenMessageLen, kTokenMessage) == 0) {
+ // Auth token
+ std::string auth_token = message.substr(kTokenMessageLen);
+ worker_thread_.message_loop().PostWork(callback_factory_.NewCallback(
+ &Instance::ThreadSetAuthToken, auth_token));
+ } else if (message == kGetFileMessage) {
+ // Request
+ if (!is_processing_request_) {
+ is_processing_request_ = true;
+ worker_thread_.message_loop().PostWork(
+ callback_factory_.NewCallback(&Instance::ThreadRequestThunk));
+ }
+ }
+}
+
+void Instance::PostMessagef(const char* format, ...) {
+ const size_t kBufferSize = 1024;
+ char buffer[kBufferSize];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(&buffer[0], kBufferSize, format, args);
+
+ PostMessage(buffer);
+}
+
+void Instance::ThreadSetAuthToken(int32_t /*result*/,
+ const std::string& auth_token) {
+ printf("Got auth token: %s\n", auth_token.c_str());
+ auth_token_ = auth_token;
+}
+
+void Instance::ThreadRequestThunk(int32_t /*result*/) {
+ ThreadRequest();
+ is_processing_request_ = false;
+}
+
+bool Instance::ThreadRequest() {
+ static int request_count = 0;
+ static const char kTitle[] = "hello nacl.txt";
+ Json::Value metadata;
+ std::string output;
+
+ PostMessagef("log:\n Got request (#%d).\n", ++request_count);
+ PostMessagef("log: Looking for file: \"%s\".\n", kTitle);
+
+ if (!ThreadGetFileMetadata(kTitle, &metadata)) {
+ PostMessage("log: Not found! Creating a new file...\n");
+ // No data found, write a new file.
+ static const char kDescription[] = "A file generated by NaCl!";
+ static const char kInitialContent[] = "Hello, Google Drive!";
+
+ if (!ThreadCreateFile(kTitle, kDescription, kInitialContent, &metadata)) {
+ PostMessage("log: Creating the new file failed...\n");
+ return false;
+ }
+ } else {
+ PostMessage("log: Found it! Downloading the file...\n");
+ // Found the file, download it's data.
+ if (!ThreadDownloadFile(metadata, &output)) {
+ PostMessage("log: Downloading the file failed...\n");
+ return false;
+ }
+
+ // Modify it.
+ output += "\nHello, again Google Drive!";
+
+ std::string file_id;
+ if (!GetMetadataKey(metadata, "id", &file_id)) {
+ PostMessage("log: Couldn't find the file id...\n");
+ return false;
+ }
+
+ PostMessage("log: Updating the file...\n");
+ if (!ThreadUpdateFile(file_id, output, &metadata)) {
+ PostMessage("log: Failed to update the file...\n");
+ return false;
+ }
+ }
+
+ PostMessage("log: Done!\n");
+ PostMessage("log: Downloading the newly written file...\n");
+ if (!ThreadDownloadFile(metadata, &output)) {
+ PostMessage("log: Downloading the file failed...\n");
+ return false;
+ }
+
+ PostMessage("log: Done!\n");
+ PostMessage(output);
+ return true;
+}
+
+bool Instance::ThreadGetFileMetadata(const char* title, Json::Value* metadata) {
+ ListFilesParams p;
+ p.max_results = 1;
+ p.query = "title = \'";
+ p.query += title;
+ p.query += "\'";
+
+ Json::Value root;
+ int32_t result = ListFiles(this, auth_token_, p, &root);
+ if (result != PP_OK) {
+ PostMessagef("log: ListFiles failed with result %d\n", result);
+ return false;
+ }
+
+ // Extract the first item's metadata.
+ if (!root.isMember("items")) {
+ PostMessage("log: ListFiles returned no items...\n");
+ return false;
+ }
+
+ Json::Value items = root["items"];
+ if (!items.isValidIndex(0)) {
+ PostMessage("log: Expected items[0] to be valid.\n");
+ return false;
+ }
+
+ *metadata = items[0U];
+ return true;
+}
+
+bool Instance::ThreadCreateFile(const char* title,
+ const char* description,
+ const char* content,
+ Json::Value* metadata) {
+ InsertFileParams p;
+ p.content = content;
+ p.description = description;
+ p.mime_type = "text/plain";
+ p.title = title;
+
+ int32_t result = InsertFile(this, auth_token_, p, metadata);
+ if (result != PP_OK) {
+ PostMessagef("log: Creating file failed with result %d\n", result);
+ return false;
+ }
+
+ return true;
+}
+
+bool Instance::ThreadUpdateFile(const std::string& file_id,
+ const std::string& content,
+ Json::Value* metadata) {
+ InsertFileParams p;
+ p.file_id = file_id;
+ p.content = content;
+ p.mime_type = "text/plain";
+
+ int32_t result = InsertFile(this, auth_token_, p, metadata);
+ if (result != PP_OK) {
+ PostMessagef("log: Updating file failed with result %d\n", result);
+ return false;
+ }
+
+ return true;
+}
+
+bool Instance::ThreadDownloadFile(const Json::Value& metadata,
+ std::string* output) {
+ ReadUrlParams p;
+ p.method = "GET";
+
+ if (!GetMetadataKey(metadata, "downloadUrl", &p.url)) {
+ return false;
+ }
+
+ AddAuthTokenHeader(&p.request_headers, auth_token_);
+
+ int32_t result = ReadUrl(this, p, output);
+ if (result != PP_OK) {
+ PostMessagef("log: Downloading failed with result %d\n", result);
+ return false;
+ }
+
+ return true;
+}
+
+bool Instance::GetMetadataKey(const Json::Value& metadata,
+ const char* key,
+ std::string* output) {
+ Json::Value value = metadata[key];
+ if (!value.isString()) {
+ PostMessagef("log: Expected metadata.%s to be a string.\n", key);
+ return false;
+ }
+
+ *output = value.asString();
+ return true;
+}
+
+class Module : public pp::Module {
+ public:
+ Module() : pp::Module() {}
+ virtual ~Module() {}
+
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new Instance(instance);
+ }
+};
+
+namespace pp {
+
+Module* CreateModule() { return new ::Module(); }
+
+} // namespace pp
diff --git a/native_client_sdk/src/examples/demo/drive/example.dsc b/native_client_sdk/src/examples/demo/drive/example.dsc
new file mode 100644
index 0000000..8efe744
--- /dev/null
+++ b/native_client_sdk/src/examples/demo/drive/example.dsc
@@ -0,0 +1,30 @@
+{
+ 'TOOLS': ['newlib', 'glibc', 'pnacl'],
+ 'TARGETS': [
+ {
+ 'NAME' : 'drive',
+ 'TYPE' : 'main',
+ 'SOURCES' : ['drive.cc'],
+ 'LIBS': ['jsoncpp', 'ppapi_cpp', 'ppapi', 'pthread']
+ }
+ ],
+ 'PRE': """
+#
+# We use the chrome.experimental.identity API, which requires the
+# --enable-experimental-expension-apis flag.
+#
+CHROME_ARGS += --enable-experimental-extension-apis
+""",
+ 'DATA': [
+ 'example.js',
+ ],
+ 'DEST': 'examples/demo',
+ 'NAME': 'drive',
+ 'TITLE': 'Google Drive',
+ 'GROUP': 'Demo',
+ 'PERMISSIONS': [
+ 'experimental',
+ 'https://www.googleapis.com/*/drive/*',
+ 'https://*.googleusercontent.com/*'
+ ]
+}
diff --git a/native_client_sdk/src/examples/demo/drive/example.js b/native_client_sdk/src/examples/demo/drive/example.js
new file mode 100644
index 0000000..ce4279b
--- /dev/null
+++ b/native_client_sdk/src/examples/demo/drive/example.js
@@ -0,0 +1,70 @@
+// 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.
+
+"use strict";
+
+var authToken = '';
+
+function getAuthToken(interactive) {
+ chrome.experimental.identity.getAuthToken(
+ {'interactive': interactive}, onGetAuthToken);
+}
+
+function onGetAuthToken(authToken) {
+ var signInEl = document.getElementById('signIn');
+ var getFileEl = document.getElementById('getFile');
+ if (authToken) {
+ signInEl.setAttribute('hidden');
+ getFileEl.removeAttribute('hidden');
+ window.authToken = authToken;
+
+ // Send the auth token to the NaCl module.
+ common.naclModule.postMessage('token:'+authToken);
+ } else {
+ // There is no auth token; this means that the user has yet to authorize
+ // this app. Display a button to let the user sign in and authorize this
+ // application.
+ signInEl.removeAttribute('hidden');
+ getFileEl.setAttribute('hidden');
+ }
+};
+
+// Called by the common.js module.
+function moduleDidLoad() {
+ // The module is not hidden by default so we can easily see if the plugin
+ // failed to load.
+ common.hideModule();
+
+ // Make sure this example is running as a packaged app. If not, display a
+ // warning.
+ if (!chrome.experimental) {
+ common.updateStatus('Error: must be run as a packged app.');
+ return;
+ }
+
+ // Try to get the authorization token non-interactively. This will often work
+ // if the user has already authorized the app, and the token is cached.
+ getAuthToken(false);
+}
+
+function handleMessage(e) {
+ var msg = e.data;
+ document.getElementById('contents').textContent = msg;
+}
+
+// Called by the common.js module.
+function attachListeners() {
+ document.getElementById('signIn').addEventListener('click', function () {
+ // Get the authorization token interactively. A dialog box will pop up
+ // asking the user to authorize access to their Drive account.
+ getAuthToken(true);
+ });
+
+ document.getElementById('getFile').addEventListener('click', function () {
+ // Clear the file contents dialog box.
+ document.getElementById('contents').textContent = '';
+
+ common.naclModule.postMessage('getFile');
+ });
+}
diff --git a/native_client_sdk/src/examples/demo/drive/index.html b/native_client_sdk/src/examples/demo/drive/index.html
new file mode 100644
index 0000000..47fe15e
--- /dev/null
+++ b/native_client_sdk/src/examples/demo/drive/index.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<!--
+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.
+-->
+<head>
+ <meta http-equiv="Pragma" content="no-cache">
+ <meta http-equiv="Expires" content="-1">
+ <title>{{title}}</title>
+ <script type="text/javascript" src="common.js"></script>
+ <script type="text/javascript" src="example.js"></script>
+</head>
+<body {{attrs}}>
+ <h1>{{title}}</h1>
+ <h2>Status: <code id="statusField">NO-STATUS</code></h2>
+ <p>
+ This example demonstrates reading from and writing to your Google Drive
+ account. To run:
+ <ol>
+ <li>
+ Click the "Sign In" button below to authorize access to your
+ account.
+ </li>
+ <li>
+ Click the "Get File" button to read the file "hello nacl.txt" from your
+ account.
+ <ul>
+ <li>If it exists, it will be modified.</li>
+ <li>If it doesn't exist, it will be created.</li>
+ </ul>
+ </li>
+ <li>
+ The file will be downloaded, and the contents displayed below. Try
+ looking on
+ <a href="https://drive.google.com" target="_blank">Google Drive</a> to
+ see the changes as well!
+ </li>
+ </ol>
+ </p>
+ <button id="signIn" hidden>Sign In</button>
+ <button id="getFile" hidden>Get File</button>
+ <div id="listener"></div>
+ <pre id="log"></pre>
+ <div>
+ File Contents:
+ <pre style="border: 1px solid #ccc" id="contents"></pre>
+ </div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/resources/manifest.json.template b/native_client_sdk/src/examples/resources/manifest.json.template
index 8dba583..c4a111c 100644
--- a/native_client_sdk/src/examples/resources/manifest.json.template
+++ b/native_client_sdk/src/examples/resources/manifest.json.template
@@ -12,6 +12,13 @@
"scripts": ["background.js"]
}
},
+[[if key:]]
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMN716Qyu0l2EHNFqIJVqVysFcTR6urqhaGGqW4UK7slBaURz9+Sb1b4Ot5P1uQNE5c+CTU5Vu61wpqmSqMMxqHLWdPPMh8uRlyctsb2cxWwG6XoGSvpX29NsQVUFXd4v2tkJm3G9t+V0X8TYskrvWQmnyOW8OEIDvrBhUEfFxWQIDAQAB",
+[[]]
+ "oauth2": {
+ "client_id": "903965034255.apps.googleusercontent.com",
+ "scopes": ["https://www.googleapis.com/auth/drive"]
+ },
"permissions": [
[[if permissions:]]
[[ for i, perm in enumerate(permissions):]]
diff --git a/native_client_sdk/src/tools/common.mk b/native_client_sdk/src/tools/common.mk
index 8045a06..f9e13ce 100644
--- a/native_client_sdk/src/tools/common.mk
+++ b/native_client_sdk/src/tools/common.mk
@@ -209,7 +209,8 @@ LIBDIR ?= $(NACL_SDK_ROOT)/lib
.PHONY: clean
clean:
$(RM) -f $(TARGET).nmf
- $(RM) -fr $(OUTDIR)
+ $(RM) -rf $(OUTDIR)
+ $(RM) -rf user-data-dir
#
@@ -396,7 +397,8 @@ RUN_PY := python $(NACL_SDK_ROOT)/tools/run.py
CHROME_ENV ?=
# Additional arguments to pass to Chrome.
-CHROME_ARGS += --enable-nacl --enable-pnacl --incognito --ppapi-out-of-process
+CHROME_ARGS += --enable-nacl --enable-pnacl --no-first-run
+CHROME_ARGS += --user-data-dir=$(CURDIR)/user-data-dir
# Paths to Debug and Release versions of the Host Pepper plugins
@@ -418,7 +420,7 @@ endif
.PHONY: run_package
run_package: check_for_chrome all
- $(CHROME_PATH) --load-and-launch-app=$(CURDIR) --enable-nacl --enable-pnacl
+ $(CHROME_PATH) --load-and-launch-app=$(CURDIR) $(CHROME_ARGS)
SYSARCH = $(shell python $(NACL_SDK_ROOT)/tools/getos.py --nacl-arch)