├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codereview.settings ├── common ├── common.css └── common.js ├── docs ├── design_notes.md ├── explainer.md ├── images │ ├── README.md │ └── ballista-300.png └── native.md ├── handler.yaml ├── handler ├── README.md ├── icon │ ├── 32.png │ └── ic_mode_edit_black_48dp.png ├── index.html ├── index.js ├── manifest.webmanifest └── sw.js ├── polyfill ├── README.md ├── ballista-polyfill.js └── proxy-iframe.html ├── proxy.yaml ├── proxy ├── README.md ├── choose.html ├── choose.js ├── common.css ├── icon │ └── 32.png ├── index.html ├── index.js ├── register.html ├── register.js └── registry.js ├── requester.yaml └── requester ├── README.md ├── icon ├── 32.png └── ic_cloud_done_black_48dp.png ├── index.html ├── index.js └── sw.js /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 6 | (CLA), which you can do online. The CLA is necessary mainly because you own the 7 | copyright to your changes, even after your contribution becomes part of our 8 | codebase, so we need your permission to use and distribute your code. We also 9 | need to be sure of various other things—for instance that you'll tell us if you 10 | know that your code infringes on other people's patents. You don't have to sign 11 | the CLA until after you've submitted your code for review and a member has 12 | approved it, but you must do it before we can put your code into our codebase. 13 | Before you start working on a larger contribution, you should get in touch with 14 | us first through the issue tracker with your idea so that we can help out and 15 | possibly guide you. Coordinating up front makes it much easier to avoid 16 | frustration later on. 17 | 18 | ### Code reviews 19 | All submissions, including submissions by project members, require review. We 20 | use GitHub pull requests for this purpose. 21 | 22 | ### The small print 23 | Contributions made by corporations are covered by a different agreement than 24 | the one above, the 25 | [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ballista 2 | 3 | **Date**: 2015-09-25 4 | **Updated**: 2019-11-08 5 | 6 | ![A ballista](docs/images/ballista-300.png) 7 | 8 | **Ballista** was a project to explore inter-website and web/native 9 | communication; specifically, communication between one website and another site 10 | or native app of the user's choosing. We explored ways for the user to be able 11 | to *share* or *edit* documents in another website or app that the first website 12 | has never even heard of, *choose* documents from another website, or register a 13 | website as a *native file handler*. We were attempting to solve similar problems 14 | to the intents system on Android, but also address other use cases like document 15 | editing. Essentially, we set out to create an **interoperability system for the 16 | web**. 17 | 18 | Our [explainer document](docs/explainer.md) dives deeper into the problem space 19 | and outlines our early draft of an API that we thought solved this problem. But 20 | this was less about proposing an API, and more about starting a conversation. 21 | 22 | Epilogue (2019): The Ballista project started in 2015 as an ambitious 23 | exploration of the above concepts. The project evolved into a number of more 24 | specialized and smaller proposals to achieve these goals, several of which are 25 | now generally available. These include [Web 26 | Share](https://github.com/w3c/web-share), [Web Share 27 | Target](https://github.com/WICG/web-share-target) and [File 28 | Handling](https://github.com/WICG/file-handling). 29 | 30 | ## Spin-off proposals 31 | 32 | These standards-track proposals were created as a result of the explorations 33 | begun in the Ballista project: 34 | 35 | * [Web Share](https://github.com/w3c/web-share). 36 | * [Web Share Target](https://github.com/WICG/web-share-target). 37 | * [File Handling](https://github.com/WICG/file-handling). 38 | 39 | ## Demo 40 | 41 | We have a prototype that works in Chrome and Firefox. Try this: 42 | 43 | 1. Go to 44 | [handler-dot-chromium-ballista.appspot.com](https://handler-dot-chromium-ballista.appspot.com) 45 | (Ballista Editor Demo), and click "OK" to register it as an action handler. 46 | 2. Go to 47 | [requester-dot-chromium-ballista.appspot.com](https://requester-dot-chromium-ballista.appspot.com) 48 | (Ballista Cloud Demo), and open a file with "Ballista Editor Demo". 49 | 50 | These two apps don't know about each other, yet the editor can edit files from 51 | the cloud app. Using our polyfill, you can write a web app that interoperates 52 | with our demo apps in the same way. 53 | 54 | You can view and manage app registrations at 55 | [chromium-ballista.appspot.com](https://chromium-ballista.appspot.com). In the 56 | final product, the registration, picking and management UI would be part of the 57 | browser. 58 | 59 | ## Resources 60 | 61 | * For a detailed overview, see [Ballista Explained](docs/explainer.md). 62 | * In the [`polyfill`](polyfill) directory, there is a polyfill that you can use 63 | to write a requester that can fire actions at any handler, or a handler that 64 | can receive actions from any requester. 65 | * The [`handler`](handler) and [`requester`](requester) directories contain the 66 | source code for the demo apps described above. 67 | 68 | See the `README.md` file in each directory for details. Many caveats apply. 69 | 70 | ## Who is behind Ballista? 71 | 72 | The Google Chrome team, including: 73 | 74 | * Matt Giuca <> 75 | * Sam McNally <> 76 | * Ben Wells <> 77 | 78 | This is not an official Google product (experimental or otherwise), it is just 79 | code that happens to be owned by Google. 80 | 81 | Copyright 2016 Google Inc. All Rights Reserved. 82 | 83 | Licensed under the Apache License, Version 2.0 (the "License"); 84 | you may not use this file except in compliance with the License. 85 | You may obtain a copy of the License at 86 | 87 | > 88 | 89 | Unless required by applicable law or agreed to in writing, software 90 | distributed under the License is distributed on an "AS IS" BASIS, 91 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 92 | See the License for the specific language governing permissions and 93 | limitations under the License. 94 | -------------------------------------------------------------------------------- /codereview.settings: -------------------------------------------------------------------------------- 1 | # This file is used by git cl to get repository specific information. 2 | # Note: For trivial code reviews (no discussion expected), use a GitHub pull 3 | # request. For anything non-trivial, use git cl upload and Reitveld code review. 4 | CODE_REVIEW_SERVER: codereview.chromium.org 5 | VIEW_VC: https://github.com/chromium/ballista/blob/master 6 | PROJECT: ballista 7 | -------------------------------------------------------------------------------- /common/common.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | body { 17 | background-color: #efefef; 18 | } 19 | 20 | .ballista .mdl-layout__header-row { 21 | padding-left: 40px; 22 | } 23 | 24 | #contents_textfield { 25 | width: 600px; 26 | } 27 | 28 | .compact-mdl-textfield { 29 | padding: 0; 30 | } 31 | 32 | .edit-button { 33 | margin-bottom: 10px; 34 | } 35 | -------------------------------------------------------------------------------- /common/common.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | "use strict"; 16 | 17 | // Reads a blob as text. Returns a promise, which supplies the text. 18 | function readBlobAsText(blob) { 19 | return new Promise((resolve, reject) => { 20 | var reader = new FileReader(); 21 | 22 | reader.addEventListener('load', () => resolve(reader.result)); 23 | reader.addEventListener('abort', () => reject(new Error("aborted"))); 24 | reader.addEventListener('error', () => reject(reader.error)); 25 | 26 | reader.readAsText(blob); 27 | }); 28 | } 29 | 30 | // Creates a element for a table with simple text in each cell (MDL style). 31 | // |cells| is an array of strings. 32 | function createTableRow(cells) { 33 | var tr = document.createElement('tr'); 34 | for (var i = 0; i < cells.length; i++) { 35 | var td = document.createElement('td'); 36 | td.setAttribute('class', 'mdl-data-table__cell--non-numeric'); 37 | td.appendChild(document.createTextNode(cells[i])); 38 | tr.appendChild(td); 39 | } 40 | return tr; 41 | } 42 | 43 | // Call this to re-run the MDL upgrade step on a table (to regenerate the 44 | // checkboxes). This should be called whenever |table| is changed. 45 | function reUpgradeTable(table, selectionChangedCallback) { 46 | // Delete all the checkbox cells. 47 | var trs = table.querySelectorAll('tr'); 48 | for (var i = 0; i < trs.length; i++) { 49 | var tr = trs[i]; 50 | var firstCell = tr.querySelector('th,td'); 51 | var firstCellInput = firstCell.querySelector('input'); 52 | if (firstCellInput != null && firstCellInput.type == 'checkbox') 53 | tr.removeChild(firstCell); 54 | } 55 | 56 | // Force MDL to regenerate the checkbox cells. (The removal of data-upgraded 57 | // is required due to 58 | // https://github.com/google/material-design-lite/issues/984; this is a 59 | // proposed work-around.) 60 | table.removeAttribute('data-upgraded'); 61 | componentHandler.upgradeElement(table); 62 | 63 | // Add event handlers to the checkboxes. 64 | for (var i = 0; i < trs.length; i++) { 65 | var tr = trs[i]; 66 | var firstCell = tr.querySelector('th,td'); 67 | var checkbox = firstCell.querySelector('input'); 68 | if (checkbox != null && selectionChangedCallback != null) 69 | checkbox.addEventListener('change', selectionChangedCallback); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /docs/design_notes.md: -------------------------------------------------------------------------------- 1 | # Ballista Design Notes 2 | 3 | **Date**: 2016-02-26 4 | 5 | In this document, I'll go over some of the design decisions in the current 6 | Ballista [API proposal](explainer.md), and highlight how we address some common 7 | problems with similar APIs. 8 | 9 | As the explainer sets out, nothing is set in stone. This doc just covers our 10 | current thinking. 11 | 12 | ## Alternative proposals 13 | 14 | **How is this different from other web interoperability systems?** 15 | 16 | This is an important question, so I put it in the 17 | [explainer](explainer.md#how-is-this-different-from-other-web-interoperability-systems). 18 | I'll go into more detail on some of those in the following questions. 19 | 20 | **Why not build on Mozilla's Web Activities?** 21 | 22 | [Web 23 | Activities](https://developer.mozilla.org/en-US/docs/Web/API/Web_Activities) is 24 | a Firefox-only API for sending and receiving messages between user-selectable 25 | apps. (If you are familiar with Android, this is the Firefox OS version of 26 | the intents system.) 27 | 28 | This sounds promising. Although it is not currently on a standards track, we 29 | could have built our proposal around Web Activities. The main problem is 30 | architectural: we want to use service workers (which were invented after Web 31 | Activities) to solve some important problems (see [Resilience in the face of 32 | death](#resilience-in-the-face-of-death)). That made our API fundamentally 33 | incompatible with Web Activities, and since Web Activities is not standard, we 34 | decided to just start from scratch. 35 | 36 | **Why not just use intent:// URIs?** 37 | 38 | On Android, both Chrome and Firefox let web applications fire Android system 39 | intents by opening a URI with the ["intent://" 40 | scheme](https://developer.chrome.com/multidevice/android/intents). This only 41 | lets us create requesters (not handlers), and only fire one-directional actions. 42 | But let's talk about this feature specifically for the Share use case. The main 43 | thing it has going for it is it works right now! But how well does it solve the 44 | Share use case, and how can we build on it? 45 | 46 | Firstly, this is Android-only, and not a web standard. Could we generalize it 47 | and standardize? In its current form, I think it's a bit too tightly coupled 48 | with Android; for example, it directly uses Android intent names and package 49 | names (like `com.google.zxing.client.android`). Also, it has some abilities that 50 | I consider outside the scope of Ballista, like being able to directly launch a 51 | specific native application. These sort of things don't belong in a web standard 52 | because they rely too much on the host operating system. 53 | 54 | OK, what if we start from scratch, but take inspiration from intent:// URIs. 55 | What if we invent an action:// URI that isn't Android-specific, as Paul Kinlan 56 | suggests 57 | [here](https://paul.kinlan.me/every-browser-should-support-intent-urls/). That 58 | sounds better, but I worry it will be limited to one-way actions with a small 59 | amount of data. Yes, we could find ways to establish a message channel between 60 | the handler and requester, but a) I can't imagine what that would look like on 61 | the side of the requester opening a URI, and b) a message channel is too general 62 | (it requires all participating sites to agree upon and implement a protocol). 63 | 64 | The way I see it, if we're going to start from scratch, we may as well design an 65 | API out of JavaScript functions that matches the way it will be used, instead of 66 | trying to cram a two-way communications framework into a URI. 67 | 68 | ## The development process 69 | 70 | **This proposal has too many parts. Can we just simplify/specialize on a 71 | particular use case?** 72 | 73 | The best way to get nothing done is to try and solve all the problems at the 74 | same time. Many people have expressed concern that our proposal is too broad in 75 | scope. In particular: 76 | 77 | * That if we try to solve many different use cases, we'll have an overly broad 78 | API, and overly broad UI, and we'll have to fight all the battles at once. 79 | * That if we try to make a general-purpose ("any verb you like") system, there 80 | will be fragmentation and not enough agreement about what a particular verb 81 | means ("I sent you a "tweet" action, but you only accept "share" actions."). 82 | 83 | So yes, we do want to start by addressing just a couple of important use cases: 84 | Share and Edit. We want to at least start with a closed set of verbs. But we're 85 | starting with two cases, not one, because we also don't want an over-constrained 86 | system that can't be extended later. For example, by considering the Edit case, 87 | we were able to design around the long-term bidirectional scenario, which we 88 | wouldn't have been able to extend into had we only considered Share. 89 | 90 | Ultimately, we may want to make a "V1" spec that only includes the simpler Share 91 | use case, but I think it's helpful for discussions to consider both cases in 92 | depth. 93 | 94 | ## Resilience in the face of death 95 | 96 | **Why is the requester's foreground page only allowed to perform one-directional 97 | actions?** 98 | 99 | If a foreground page could perform a bidirectional action, and the user closed 100 | the foreground page before closing the handler, updates from the handler could 101 | be lost. 102 | 103 | We could allow this, but we decided to forbid it, in order to encourage best 104 | practices for developers. See the following question for more details. 105 | 106 | **What if the requester's tab closes while it is waiting for a response?** 107 | 108 | If we want to handle long-term editing sessions, we have to handle this case. 109 | Say the requester is a cloud file store, and the editor is a text editor: if the 110 | user closes the cloud store's foreground tab, they should still be able to save 111 | their work to the cloud. 112 | 113 | If the JavaScript context in the requester's foreground page is waiting for a 114 | response (maybe a promise being resolved, or an event), then once the foreground 115 | page is closed, updates will simply be lost. That's why, for bidirectional 116 | actions, we require that the request comes from a service worker, so we can 117 | receive updates in the background, even after the foreground tab closes. 118 | 119 | **What if the requester's service worker gets killed?** 120 | 121 | Service workers can be killed at any time, and when that happens, the JavaScript 122 | context (all the global variables, objects, any unresolved promises, registered 123 | events, etc) is destroyed. 124 | 125 | We have carefully designed the API so that the requester state is resilient to 126 | service worker death. The service worker can be killed while an action is in 127 | flight, and when an update event occurs, the user agent restarts the requester's 128 | worker, and delivers the update event. 129 | 130 | More details on that in the follow-up questions. 131 | 132 | **Why are updates delivered as events, rather than by resolving a promise?** 133 | 134 | Firstly, because promises can only be resolved once, and we want to be able to 135 | deliver multiple updates. 136 | 137 | Secondly, because of the service worker lifetime problem discussed above. 138 | Promises can't live beyond the lifetime of the worker. Neither can event 139 | registrations, of course, but the events are re-registered when the worker 140 | restarts. 141 | 142 | **Why do update events have an int ID? Why not make it nicely object oriented?** 143 | 144 | Our original design was to represent every action with a neat `Action` object. 145 | Update events would be delivered directly to the `Action` (rather than to a 146 | global object), which allowed you to write this nice, simple code in the 147 | requester's service worker: 148 | 149 | ```js 150 | navigator.actions.performAction('open', {file: file}) 151 | .then(action => { 152 | action.addEventListener( 153 | 'update', event => storeFileInCloud(filename, event.data.file)); 154 | }); 155 | ``` 156 | 157 | Unfortunately, we realised that those event listeners, and the `Action` objects 158 | themselves, would be destroyed when the service worker is killed. In order to 159 | keep listening for updates even while stopped, all of the state would have to be 160 | serializable (which isn't possible for event handlers). 161 | 162 | So we ditched the `Action` object and used an opaque integer ID to represent 163 | actions. The requester can keep track of these IDs across service worker 164 | restarts. Of course, this still requires some effort on the developer's part: 165 | you need to keep a map from action IDs to relevant metadata (e.g., the file's ID 166 | in the server's database) in some persistent store, like IndexedDB. But that is 167 | a general necessity when writing a service worker. 168 | -------------------------------------------------------------------------------- /docs/explainer.md: -------------------------------------------------------------------------------- 1 | # Ballista Explained 2 | 3 | **Date**: 2015-09-25 4 | 5 | **Ballista** is a project to explore inter-website communication; specifically, 6 | communication between one website and another site of the user's choosing. 7 | Imagine being able to: 8 | 9 | * Click a "share" button, then choose which social network or other web/native 10 | app to share it with, based on which apps *you* have installed (not a 11 | pre-defined list chosen by the site you're on). 12 | * Click an "edit" button on a photo or document in a cloud drive or web IDE, 13 | then choose a web/native app to edit it with. 14 | * Click an "attach" button in a webmail app, then pick a photo or file from a 15 | cloud photo collection or drive, instead of your local disk. 16 | * Register a web editor as the default editor for certain file types in the 17 | native file browser. 18 | 19 | We want to enable all of these use cases, and more like them. And, we want to be 20 | able to integrate with native apps where it makes sense (for example, using a 21 | native app to edit a document directly from the web). Essentially, we want to 22 | create an **interoperability system for the web**. 23 | 24 | We've come up with a basic API as a first cut at solving this problem. That API 25 | is detailed below, and a polyfill is provided in this repository. But it's early 26 | days, and we expect it to evolve over time. We're less interested in pushing 27 | this particular API than we are in restarting the conversation in this problem 28 | space. 29 | 30 | Ballista is all about helping web applications become first-class apps on 31 | desktop and mobile, interoperating with native apps and the underlying local 32 | file system, as well as with each other. 33 | 34 | See also: 35 | * [Design Notes](design_notes.md), an informal Q&A about the design. 36 | 37 | ## Spun-off proposals 38 | 39 | We are planning to present each piece of Ballista as a separate standards 40 | proposal. 41 | 42 | * [Web Share API](https://github.com/mgiuca/web-share). 43 | * [Web Share Target API](https://github.com/mgiuca/web-share-target). 44 | 45 | ## How is this different from other web interoperability systems? 46 | 47 | There have been several past attempts at doing this, notably [Web 48 | Intents](http://webintents.org), which is no longer under development, and 49 | Mozilla's [Web 50 | Activities](https://developer.mozilla.org/en-US/docs/Web/API/Web_Activities), 51 | which is proprietary to Firefox OS and Firefox for Android. 52 | 53 | Indeed, this proposal covers the bulk of the use cases of Web Intents and Web 54 | Activities, but a few things are different now: 55 | 56 | * The web platform has gotten some new features since 2013 that solve some of 57 | the issues of Web Intents: [Service 58 | Workers](http://slightlyoff.github.io/ServiceWorker/spec/service_worker/) give 59 | us a place for handlers to receive events without opening a foreground page, 60 | and for requesters to receive responses to even if the user has closed their 61 | tabs. [Web App Manifests](https://w3c.github.io/manifest/) give us a place to 62 | declaratively specify handlers. 63 | * Ballista is designed to interoperate with native apps on mobile and desktop, 64 | which solves the bootstrapping problem. Ballista is also designed to address 65 | the use case of a handler sending multiple updates back. 66 | * There is now a push for building [installable app-like 67 | websites](https://w3c.github.io/manifest/#installable-web-applications). 68 | Installable apps should be registerable as file handlers. 69 | 70 | OK, what about some less ambitious approaches? 71 | 72 | * [intent:// URLs](https://developer.chrome.com/multidevice/android/intents): 73 | Works today, but it's specific to Android, and only allows us to 74 | send intents to native apps. 75 | * [registerProtocolHandler](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler) 76 | and registerContentHandler: Can be [used for this 77 | purpose](https://blog.mozilla.org/webdev/2010/07/26/registerprotocolhandler-enhancing-the-federated-web/), 78 | but it's a hack. We'd rather design an API that properly solves these use 79 | cases. 80 | 81 | ## Overview 82 | 83 | The rest of this document outlines our current thinking about the shape of the 84 | Ballista API. 85 | 86 | Websites can use Ballista in two different ways: 87 | 88 | * As a **Requester**: Websites can request an "action" be handled by another 89 | site or app of the user's choosing. For example, a site can send a file to be 90 | edited externally. 91 | * As a **Handler**: Websites can request to be registered as action handlers. 92 | For example, a site registered as a handler may be called upon to edit a file 93 | from another site or native file system. 94 | 95 | A site / app can be either a requester or a handler, or both. 96 | 97 | > **Note**: The native integration would be an implementation detail of 98 | > browsers, not part of the standard. In essence, a browser could act as a 99 | > special requester or register itself as a special handler and act as a proxy 100 | > to the underlying OS; this would be allowed by the spec but not required. 101 | 102 | * A basic action is **one-way**: the requester sends a *single message* (with 103 | optional payload) to an appropriate handler of the user's choice. It receives 104 | confirmation that the action was sent, but doesn't expect a response from the 105 | handler. 106 | * Actions can also be **bidirectional**: after the initial action, the handler 107 | can send *updates* back to the requester. The handler can send multiple 108 | updates; each is a newer version of the object being edited (it is not a data 109 | stream). 110 | 111 | ## Sample code 112 | 113 | ### Sharing (requester) 114 | 115 | See [Web Share 116 | explainer](https://github.com/mgiuca/web-share/blob/master/docs/explainer.md). 117 | 118 | ### Share handler 119 | 120 | See [Web Share Target 121 | explainer](https://github.com/mgiuca/web-share-target/blob/master/docs/explainer.md). 122 | 123 | ### Edit a file (requester) 124 | 125 | A web-based cloud drive can add an "edit" button to let the user edit a file 126 | with any registered editor for that file type. 127 | 128 | #### foreground.js 129 | 130 | ```js 131 | editButton.addEventListener('click', () => { 132 | navigator.serviceWorker.controller.postMessage( 133 | {type: 'open', filename: selectedFilename}); 134 | }); 135 | ``` 136 | 137 | #### serviceworker.js 138 | 139 | ```js 140 | self.addEventListener('message', event => { 141 | if (event.data.type != 'open') 142 | return; 143 | 144 | var filename = event.data.filename; 145 | getFileFromCloud(filename).then(file => { 146 | // |file| is a File object. 147 | navigator.actions.performAction( 148 | {verb: 'open', bidirectional: true, type: file.type}, {file: file}) 149 | .then(action => { 150 | var onUpdate = event => { 151 | // Only respond to updates to the current action. 152 | if (event.id != action.id) 153 | return; 154 | 155 | // Can be called multiple times for a single action. 156 | // |event.data.file| is a new File object with updated text. 157 | storeFileInCloud(filename, event.data.file); 158 | if (event.done) 159 | navigator.actions.removeEventListener('update', onUpdate); 160 | }; 161 | navigator.actions.addEventListener('update', onUpdate); 162 | }); 163 | }); 164 | }); 165 | ``` 166 | 167 | #### User experience 168 | 169 | 1. The user selects a file and clicks the "edit" button. The browser shows a 170 | list of registered edit handlers for this file type, and the user can pick 171 | one. 172 | 2. The file opens in the external editor (which may be a native application, or 173 | another browser tab). (For native editors, the file is stored in a temp 174 | directory.) 175 | 3. The user clicks "save" in the external editor. This fires the "update" event 176 | to the requester's service worker, which in this case writes the updated file 177 | to the cloud. 178 | 179 | ### Text editor (handler) 180 | 181 | Here's how to register a website as a handler for editing text files. As above, 182 | we need a web app manifest and a service worker. 183 | 184 | #### manifest.webmanifest 185 | 186 | ```JSON 187 | { 188 | "name": "WebEditor", 189 | "short_name": "Editor", 190 | "icons": [...], 191 | "actions": [ 192 | { 193 | "verb": "open", 194 | "bidirectional": true, 195 | "types": ["text/*"] 196 | } 197 | ] 198 | } 199 | ``` 200 | 201 | > **Note:** `"bidirectional"` means that the handler can send updates back to 202 | > the requester. 203 | 204 | #### serviceworker.js 205 | 206 | ```js 207 | navigator.actions.addEventListener('handle', event => { 208 | if (event.options.verb == 'open') { 209 | if (event.data.file === undefined) { 210 | event.reject(new Error('Did not contain file.')); 211 | return; 212 | } 213 | 214 | // This function in our service worker opens a new browser tab and 215 | // returns a handle (in a promise) that receives a "save" event when the 216 | // user clicks a button in the tab's foreground page. 217 | openFileInNewWindow(event.data.file) 218 | .then(client => { 219 | var id = event.id; 220 | client.addEventListener('save', event => { 221 | navigator.actions.update(id, {file: new File([event.newText], ...)}); 222 | }); 223 | }); 224 | } 225 | }); 226 | ``` 227 | 228 | #### User experience 229 | 230 | 1. When the user is on your site, the browser provides a button to register the 231 | app as an editor for text files. 232 | 2. The user clicks this button. The app is registered and appears in the list of 233 | text file editors that the browser shows to the user. It is also registered 234 | as a file association for `*.txt` files in the host OS. 235 | 3. When the user opens a text file from another website, the browser lets them 236 | pick your app. Or, the user can right-click on a text file in their local 237 | file browser and choose to open it with your app. Either way, this pops open 238 | your app in a new browser tab. 239 | 4. When the user clicks "Save" in your app, the updated file is passed back to 240 | the requester website, or written back to disk. 241 | -------------------------------------------------------------------------------- /docs/images/README.md: -------------------------------------------------------------------------------- 1 | The images in this directory are licensed as follows: 2 | 3 | * ballista-300.png: Public Domain. 4 | [Source](https://commons.wikimedia.org/wiki/File:Ballista\_(PSF)\_vector.svg): 5 | "This file has been (or is hereby) donated to the Wikimedia Foundation and 6 | released into the public domain by Pearson Scott Foresman. This applies 7 | worldwide." 8 | -------------------------------------------------------------------------------- /docs/images/ballista-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chromium/ballista/76750b5b606d458dcdb0fcdebb7c12f6fcf0ee5a/docs/images/ballista-300.png -------------------------------------------------------------------------------- /docs/native.md: -------------------------------------------------------------------------------- 1 | # Ballista Native Integration Survey 2 | 3 | **Date**: 2016-04-28 4 | 5 | This document is an informal and incomplete survey of various operating systems' 6 | native actions-like systems, for exploring how a user agent might automatically 7 | map the [Ballista API](explainer.md) into the native system. 8 | 9 | *Note:* I (mgiuca@chromium.org) am not very familiar with these details. I 10 | gathered this information from reading the online documentation and 11 | experimenting with apps, and have not had experience programming against these 12 | APIs. I would appreciate being informed of any errors. 13 | 14 | ## Share verb 15 | 16 | See [Share API Native Integration 17 | Survey](https://github.com/mgiuca/web-share/blob/master/docs/native.md) and 18 | [Share Target API Native Integration 19 | Survey](https://github.com/mgiuca/web-share-target/blob/master/docs/native.md). 20 | 21 | ## Edit verb 22 | 23 | ### Windows (Desktop), Mac, Linux (Desktop), Chrome OS 24 | 25 | The ideal implementation for the "edit" verb on traditional desktop platforms 26 | would be tied into the [file 27 | associations](https://en.wikipedia.org/wiki/File_association) system that has 28 | been ubiquitous since the early days of desktop computing. 29 | 30 | * Web-to-native: When a web requester sends an edit action, native apps 31 | associated with that file type are presented in the action picker. If one is 32 | chosen, the file to be edited is saved to a temporary file on disk, and the 33 | native application is launched with that file as an argument. The user agent 34 | watches the file for changes; upon seeing a change, an `update` event is fired 35 | with the updated file contents. 36 | * A problem with this approach is that it is not generally possible for the 37 | user agent to tell when the user has closed the file in the native 38 | application, so the web requester can't be informed when the action is 39 | closed. 40 | * Native-to-web: The user agent registers web handlers into the native file 41 | association system (with a command line that launches the user agent with 42 | special arguments). When the user opens the file, the user agent is launched 43 | with the filename, and can deliver it to the web handler in an event. When the 44 | handler responds with updates, the user agent can overwrite the file on disk. 45 | * Windows presents significant trouble here because it does not allow an app 46 | (i.e. the user agent) to register as multiple handlers, nor does it allow 47 | the app to control its name/icon in the Open With menu. This can be worked 48 | around by creating a shim .exe file for each web handler (some of the 49 | Ballista team previously worked on [this precise 50 | feature](https://bugs.chromium.org/p/chromium/issues/detail?id=130455) for 51 | Chrome Apps but it never shipped... eventually it evolved into the Ballista 52 | project). 53 | * I don't remember if we had the same trouble on Mac. Linux Desktop 54 | environments are generally fine with this, though. 55 | * On Chrome OS, the Chrome team could implement this functionality directly 56 | into the File Browser app. 57 | 58 | ### Android 59 | 60 | * I haven't researched this deeply. Android's 61 | [`ACTION_EDIT`](http://developer.android.com/reference/android/content/Intent.html#ACTION_EDIT) 62 | allows the user to select an app to edit a document corresponding to a URL 63 | (which points at a file on the file system). It seems like we could implement 64 | a similar mechanism to the desktop version described above (create a temporary 65 | file, send its URL in an `ACTION_EDIT` intent). 66 | * Android's [Document Provider 67 | API](http://developer.android.com/guide/topics/providers/document-provider.html) 68 | supports the reverse process (letting the user pick a document to edit). 69 | 70 | ### iOS 71 | 72 | * I haven't researched this deeply, but it looks like there is a way for App 73 | Extensions to edit documents. As before, there is no way to dynamically 74 | register an App Extension. 75 | 76 | ### Others 77 | 78 | * No known file editing mechanism on Windows Mobile. 79 | 80 | ## Summary 81 | 82 | Throughout this survey there is a recurring theme: web-to-native is generally 83 | easy because there is usually a way to package up an action and send it to 84 | native apps. Native-to-web is usually hard or impossible for one reason: 85 | operating systems don't like letting one application dynamically register 86 | multiple handlers for things (with the notable exception of Android M+). There 87 | are usually work-arounds but it is hard to make web applications receive actions 88 | like first class apps. 89 | -------------------------------------------------------------------------------- /handler.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | module: handler 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | # Cannot use top-level static_dir due to / -> index.html redirect. 8 | handlers: 9 | - url: /([^/]*\.js) 10 | mime_type: text/javascript 11 | static_files: handler/\1 12 | upload: handler/.*\.js 13 | secure: always 14 | 15 | - url: /([^/]*\.webmanifest) 16 | mime_type: application/manifest+json 17 | static_files: handler/\1 18 | upload: handler/.*\.webmanifest 19 | secure: always 20 | 21 | - url: / 22 | static_files: handler/index.html 23 | upload: handler/index.html 24 | mime_type: text/html; charset=utf-8 25 | secure: always 26 | 27 | - url: /icon 28 | static_dir: handler/icon 29 | secure: always 30 | 31 | - url: /common 32 | static_dir: common 33 | secure: always 34 | 35 | - url: /polyfill 36 | static_dir: polyfill 37 | secure: always 38 | -------------------------------------------------------------------------------- /handler/README.md: -------------------------------------------------------------------------------- 1 | # Ballista Editor Demo 2 | 3 | **Author:** Matt Giuca <> 4 | 5 | Live at: 6 | [handler-dot-chromium-ballista.appspot.com](https://handler-dot-chromium-ballista.appspot.com) 7 | 8 | This is a simple web-based text editor that registers as a Ballista handler for 9 | text files. As it uses the Ballista polyfill, it works with any requester app 10 | using the same polyfill (like [Ballista Cloud 11 | Demo](https://requester-dot-chromium-ballista.appspot.com)). 12 | 13 | The app requires a web browser with support for [Service 14 | Workers](http://www.w3.org/TR/service-workers/) and [Arrow 15 | functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). 16 | This includes newer versions of Google Chrome / Chromium and Mozilla Firefox. 17 | (Tested on Chrome 46 and Firefox 42.) 18 | 19 | ## Usage instructions 20 | 21 | The demo runs on [Google App Engine](https://cloud.google.com/appengine/docs). 22 | You can try it out locally using the App Engine dev appserver. 23 | 24 | 1. Download and install the [App Engine Python 25 | SDK](https://cloud.google.com/appengine/downloads). 26 | 2. `cd` to the `ballista` directory. 27 | 3. Run the dev appserver: `dev_appserver.py handler.yaml`. 28 | 4. Open the [handler](http://localhost:8080) in a supported browser. You will be 29 | prompted to register this site as an action handler. Click "OK". Close the 30 | page. 31 | 5. You can view and delete handler registrations in the [polyfill control 32 | panel](https://chromium-ballista.appspot.com). 33 | 6. Open a requester app. For this example, we'll use [Ballista Cloud 34 | Demo](https://requester-dot-chromium-ballista.appspot.com). 35 | 7. From the requester, click "Open". Select "Ballista Editor Demo". This opens 36 | a new tab with the handler. 37 | 8. Edit the text in the handler, then click "Save". 38 | 9. The new text will be visible in the requester. 39 | -------------------------------------------------------------------------------- /handler/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chromium/ballista/76750b5b606d458dcdb0fcdebb7c12f6fcf0ee5a/handler/icon/32.png -------------------------------------------------------------------------------- /handler/icon/ic_mode_edit_black_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chromium/ballista/76750b5b606d458dcdb0fcdebb7c12f6fcf0ee5a/handler/icon/ic_mode_edit_black_48dp.png -------------------------------------------------------------------------------- /handler/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | Ballista Editor Demo 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |

Ballista Editor Demo 36 |

37 |
38 |
39 |
40 |
41 | This text field contains a file given to use via Ballista: 42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 | 56 |
57 |
58 |
59 | 60 |
61 | Save the file back to the requester using Ballista 62 |
63 |
64 |
65 |
66 |
67 | Fork me on GitHub 68 | 69 | 70 | -------------------------------------------------------------------------------- /handler/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Foreground page 16 | "use strict"; 17 | 18 | var filename = null; 19 | var mimetype = null; 20 | 21 | // ID of the currently open action. 22 | var actionId = null; 23 | 24 | function onLoad() { 25 | 26 | if ('serviceWorker' in navigator) { 27 | navigator.serviceWorker.register('/sw.js').then(registration => { 28 | // Registration was successful 29 | console.log('ServiceWorker registration successful with scope: ', 30 | registration.scope); 31 | }).catch(err => { 32 | // registration failed :( 33 | console.log('ServiceWorker registration failed: ', err); 34 | }); 35 | } 36 | 37 | document.getElementById('save_button') 38 | .addEventListener('click', saveButtonClick); 39 | } 40 | 41 | function onMessage(event) { 42 | var data = event.data; 43 | var type = data.type; 44 | if (type == 'loadFile') { 45 | var file = data.file; 46 | updateUIFromFile(file); 47 | filename = file.name; 48 | mimetype = file.type; 49 | actionId = data.actionId; 50 | } else { 51 | console.log('Got unknown message:', data); 52 | } 53 | } 54 | 55 | // Updates |contents_textfield| with the contents of |file|, asynchronously. 56 | function updateUIFromFile(file) { 57 | var contents_textfield = document.getElementById('contents_textfield'); 58 | var filename_textfield = document.getElementById('filename_textfield'); 59 | return readBlobAsText(file).then(text => { 60 | contents_textfield.value = text; 61 | // Need to call this method to update the placeholder text. 62 | filename_textfield.parentNode.MaterialTextfield.change(file.name); 63 | }); 64 | } 65 | 66 | function saveButtonClick() { 67 | var contents_textfield = document.getElementById('contents_textfield'); 68 | var contents = contents_textfield.value; 69 | var file = new File([contents], filename, {type: mimetype}); 70 | 71 | var message = {type: 'update', actionId: actionId, file: file}; 72 | navigator.serviceWorker.controller.postMessage(message); 73 | } 74 | 75 | window.addEventListener('load', onLoad, false); 76 | navigator.serviceWorker.addEventListener('message', onMessage); 77 | -------------------------------------------------------------------------------- /handler/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ballista Editor Demo", 3 | "short_name": "Editor Demo", 4 | "icons": [ 5 | { 6 | "src": "icon/32.png", 7 | "sizes": "32x32", 8 | "type": "image/png" 9 | } 10 | ], 11 | "actions": { 12 | "open": { 13 | "bidirectional": true, 14 | "types": ["text/*"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /handler/sw.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Service Worker 16 | "use strict"; 17 | 18 | // The name of the cache in the client-side cache database. 19 | var CACHE_NAME = 'my-site-cache-v1'; 20 | 21 | // The files we want to cache 22 | var urlsToCache = [ 23 | '/', 24 | '/index.js' 25 | ]; 26 | 27 | // Version number: 1 28 | // (Increment this when the script changes, to force a reload.) 29 | importScripts('polyfill/ballista-polyfill.js'); 30 | 31 | // Set the callback for the install step 32 | self.addEventListener('install', event => { 33 | // Perform install steps 34 | console.log('install'); 35 | 36 | /* 37 | event.waitUntil(caches.open(CACHE_NAME).then(cache => { 38 | console.log('Opened cache'); 39 | return cache.addAll(urlsToCache); 40 | })); 41 | */ 42 | }); 43 | 44 | self.addEventListener('activate', event => { 45 | console.log('activate'); 46 | }); 47 | 48 | self.addEventListener('fetch', event => { 49 | console.log('fetch: ' + event.request.url); 50 | /* 51 | event.respondWith( 52 | caches.match(event.request) 53 | .then(response => { 54 | // Cache hit - return response 55 | if (response) { 56 | console.log('cache hit: ' + event.request.url); 57 | return response; 58 | } 59 | 60 | console.log('cache miss: ' + event.request.url); 61 | return fetch(event.request.url); 62 | } 63 | ) 64 | ); 65 | */ 66 | }); 67 | 68 | // Finds the most recently opened top-level client belonging to this service 69 | // worker. Returns a promise. Rejects the promise if none found. 70 | function findLastTopLevelClient() { 71 | return clients.matchAll().then(allClients => { 72 | for (var i = 0; i < allClients.length; i++) { 73 | if (allClients[i].frameType == 'top-level') 74 | return allClients[i]; 75 | } 76 | 77 | throw new Error('No available clients; please open a tab.'); 78 | }); 79 | } 80 | 81 | // Opens a new window and loads the file up. Returns the Client object 82 | // associated with the window, in a promise. 83 | function openFileInNewWindow(file, actionId) { 84 | // TODO(mgiuca): We'd like to use clients.openWindow() to open a new client 85 | // here. Unfortunately, this is not allowed here because it is not in direct 86 | // response to a user gesture. Hopefully, the final API will allow it, but for 87 | // the polyfill, we just need to take control of an existing client. 88 | return findLastTopLevelClient().then(client => { 89 | var message = {type: 'loadFile', file: file, actionId: actionId}; 90 | client.postMessage(message); 91 | return client; 92 | }); 93 | } 94 | 95 | self.addEventListener('message', event => { 96 | var data = event.data; 97 | var type = data.type; 98 | var actionId = data.actionId; 99 | 100 | if (type == 'update') { 101 | var file = data.file; 102 | navigator.actions.update(actionId, {file: file}); 103 | } else { 104 | console.log('Got unknown message:', data); 105 | } 106 | }); 107 | 108 | navigator.actions.addEventListener('handle', event => { 109 | if (event.options.verb == 'open') { 110 | if (event.data.file === undefined) { 111 | event.reject(new Error('Did not contain file.')); 112 | return; 113 | } 114 | 115 | // TODO(mgiuca): This can fail, but the current Ballista API provides no way 116 | // for a handler to designate failure when handling an 'action' event. 117 | openFileInNewWindow(event.data.file, event.id); 118 | } else { 119 | console.log('Received unknown action:', event.options.verb); 120 | } 121 | }); 122 | -------------------------------------------------------------------------------- /polyfill/README.md: -------------------------------------------------------------------------------- 1 | # Ballista polyfill 2 | 3 | **Author:** Matt Giuca <> 4 | 5 | This partial polyfill lets you try out Ballista right now, with some (serious) 6 | limitations, in supported browsers. By design, the API does things that aren't 7 | possible in a polyfill, so we can't support everything. 8 | 9 | ## Supported browsers 10 | 11 | The polyfill requires a web browser with support for [Service 12 | Workers](http://www.w3.org/TR/service-workers/) and [Arrow 13 | functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). 14 | This includes newer versions of Google Chrome / Chromium and Mozilla Firefox. 15 | (Tested on Chrome 46 and Firefox 42.) 16 | 17 | ## Limitations of the polyfill 18 | 19 | * No registration of handlers (the web app manifest is not used). Instead, the 20 | requester must explicitly nominate the URL of the handler. 21 | * If the requester API (`navigator.actions.performAction`) is used from a 22 | service worker, a foreground page on that domain must be open (but it does not 23 | need to remain open to receive updates). 24 | * Not able to send actions to native applications. 25 | * Not possible to use `clients.openWindow` from the `'action'` event handler 26 | (when the handler's service worker receives an action). The polyfill will 27 | automatically open the handler in a new tab for you. 28 | 29 | ## Usage instructions 30 | 31 | 1. Add `ballista-polyfill.js` to your requester or handler JavaScript context 32 | (as a ` 36 | -------------------------------------------------------------------------------- /proxy.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | module: default 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | # Cannot use top-level static_dir due to / -> index.html redirect. 8 | handlers: 9 | - url: /([^/]*\.js) 10 | mime_type: text/javascript 11 | static_files: proxy/\1 12 | upload: proxy/.*\.js 13 | secure: always 14 | 15 | - url: /([^/]*\.css) 16 | mime_type: text/css 17 | static_files: proxy/\1 18 | upload: proxy/.*\.css 19 | secure: always 20 | 21 | - url: / 22 | static_files: proxy/index.html 23 | upload: proxy/index.html 24 | mime_type: text/html; charset=utf-8 25 | secure: always 26 | 27 | - url: /choose 28 | static_files: proxy/choose.html 29 | upload: proxy/choose.html 30 | mime_type: text/html; charset=utf-8 31 | secure: always 32 | 33 | - url: /register 34 | static_files: proxy/register.html 35 | upload: proxy/register.html 36 | mime_type: text/html; charset=utf-8 37 | secure: always 38 | 39 | - url: /icon 40 | static_dir: proxy/icon 41 | secure: always 42 | 43 | - url: /common 44 | static_dir: common 45 | secure: always 46 | -------------------------------------------------------------------------------- /proxy/README.md: -------------------------------------------------------------------------------- 1 | # Ballista polyfill proxy 2 | 3 | **Author:** Matt Giuca <> 4 | 5 | This is the proxy site that facilitates connections between the requester and 6 | handler (for the purpose of making the polyfill work; this site won't be 7 | required in a browser that actually implements Ballista). 8 | 9 | ## Usage instructions 10 | 11 | 1. Download and install the [App Engine Python 12 | SDK](https://cloud.google.com/appengine/downloads). 13 | 2. `cd` to the `ballista` directory. 14 | 3. Run the dev appserver: `dev_appserver.py proxy.yaml`. 15 | 16 | The proxy is now running at [http://localhost:8080](http://localhost:8080). 17 | Visiting that site allows you to view, add and remove handler registrations. 18 | 19 | To test the polyfill against your local version of the proxy, update 20 | `polyfill/ballista-polyfill.js`, changing `kProxySite` to 21 | `"http://localhost:8080"`. 22 | 23 | ## Details 24 | 25 | The proxy is an implementation detail which neither users nor developers need to 26 | deal with directly (aside from accessing the above control panel). The polyfill 27 | will automatically talk to the proxy. 28 | 29 | The reason we have a proxy site is to provide a common origin for all requesters 30 | and handlers to talk to (while we are using the polyfill). Handler registrations 31 | are stored in an IndexedDB in the client, associated with the proxy domain (the 32 | proxy is just a collection of static files; no data is sent to the server). 33 | 34 | As a developer using the Ballista API, you should generally *not* run your own 35 | proxy site. That would defeat the whole point of the proxy, which is to have a 36 | single origin for all clients to talk to. You only need to run the proxy site if 37 | you are planning to modify it. 38 | 39 | The handler registration flow is as follows: 40 | 41 | 1. Upon loading a page which a) imports the polyfill, and b) has a manifest, the 42 | polyfill automatically parses the manifest, and then loads the proxy site 43 | (/register) in an IFrame. 44 | 2. The polyfill posts a message to the proxy with details about the handler 45 | (from the manifest). The proxy displays this information to the user. 46 | 3. If the user clicks "OK", the details of the handler are added into the 47 | proxy's IndexedDB, and stored locally on the client. 48 | 49 | The requester "performAction" flow is as follows: 50 | 51 | 1. The requester calls `performAction` (a function in the polyfill). 52 | 2. The polyfill loads the proxy site (/choose) in an IFrame. 53 | 3. The polyfill posts a message to the proxy with details about the action (its 54 | `options` dictionary) along with a 55 | [MessagePort](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) 56 | object. 57 | 4. The proxy looks up its IndexedDB, filtering handlers based on the `options` 58 | dictionary. It displays the list of viable handlers to the user. 59 | 5. If the user picks a handler, the proxy loads the handler's URL in an IFrame 60 | (nested within its own IFrame). 61 | 6. The proxy posts the MessagePort to the handler. 62 | 7. The polyfill (in the handler) receives the MessagePort, and sends a 63 | confirmation message back to the requester. 64 | 8. The polyfill (in the requester) closes the IFrame containing the proxy. 65 | 66 | Now the handler and requester are connected directly via a MessageChannel, and 67 | there is no further mediation required from the proxy. 68 | 69 | Again, you don't need to know any of this to use the API. This is all taken care 70 | of by the interactions between the polyfill and proxy site. 71 | 72 | ## Why didn't we just use registerProtocolHandler? 73 | 74 | As detailed 75 | [here](https://blog.mozilla.org/webdev/2010/07/26/registerprotocolhandler-enhancing-the-federated-web/) 76 | and 77 | [here](http://www.backalleycoder.com/2015/10/13/app-to-app-interaction-apis/), 78 | you can achieve a similar thing using 79 | [registerProtocolHandler](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler) 80 | and IFrames. Why did we make this complicated proxy? 81 | 82 | * Using RPH would force us to use the native RPH registration, picking and 83 | management UI, which differs from browser to browser, is of fairly poor 84 | quality (in Chrome and Firefox --- for example, in Chrome, you must choose a 85 | default handler and cannot choose on a case-by-case basis), and exposes the 86 | protocol name to the user. This way we can experiment with our own UI. 87 | * RPH does not let you do interesting filters (such as filtering on MIME types, 88 | wildcards, etc). 89 | * There are bugs involving use of RPH within IFrames (such as [this Firefox 90 | bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1196151)). 91 | -------------------------------------------------------------------------------- /proxy/choose.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | Open With 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |

Open with

33 |
34 |
35 | There are no registered apps for this action. Please register one, then 36 | try again. 37 |
38 | 41 | 42 |
43 | 46 | 49 |
50 |
51 | 52 | 53 |