diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-22 00:40:09 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-22 00:40:09 +0000 |
commit | c56a9dc7cc34d76e9588efee5ad8407f1c587128 (patch) | |
tree | 28bdd0560a8e94b12864cfb322f22153667c6a17 /native_client_sdk | |
parent | a139481bb3c67824248e8371616637faea0bc265 (diff) | |
download | chromium_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-x | native_client_sdk/src/build_tools/build_app.py | 13 | ||||
-rw-r--r-- | native_client_sdk/src/build_tools/generate_make.py | 1 | ||||
-rwxr-xr-x | native_client_sdk/src/build_tools/parse_dsc.py | 6 | ||||
-rw-r--r-- | native_client_sdk/src/build_tools/sdk_files.list | 9 | ||||
-rw-r--r-- | native_client_sdk/src/examples/common.js | 1 | ||||
-rw-r--r-- | native_client_sdk/src/examples/demo/drive/drive.cc | 564 | ||||
-rw-r--r-- | native_client_sdk/src/examples/demo/drive/example.dsc | 30 | ||||
-rw-r--r-- | native_client_sdk/src/examples/demo/drive/example.js | 70 | ||||
-rw-r--r-- | native_client_sdk/src/examples/demo/drive/index.html | 51 | ||||
-rw-r--r-- | native_client_sdk/src/examples/resources/manifest.json.template | 7 | ||||
-rw-r--r-- | native_client_sdk/src/tools/common.mk | 8 |
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) |