Bug 737050 - Add support for the WEBAPP intent r=blassey r=bnicholson r=gavin
authorMark Finkle <mfinkle@mozilla.com>
Fri, 13 Apr 2012 22:45:25 -0400
changeset 94970 2ce1f809b765b798653929777c8a23f989ea1368
parent 94969 190a237b1456185228f50f124a9cd0de68a82919
child 94971 7b71bbd94961c5113067c7368bbe982b4a63c885
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey, bnicholson, gavin
bugs737050
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 737050 - Add support for the WEBAPP intent r=blassey r=bnicholson r=gavin
dom/interfaces/base/nsIBrowserDOMWindow.idl
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoEvent.java
mobile/android/base/GeckoThread.java
mobile/android/chrome/content/browser.js
mobile/android/components/BrowserCLH.js
widget/android/AndroidJavaWrappers.cpp
widget/android/nsAppShell.cpp
--- a/dom/interfaces/base/nsIBrowserDOMWindow.idl
+++ b/dom/interfaces/base/nsIBrowserDOMWindow.idl
@@ -71,16 +71,21 @@ interface nsIBrowserDOMWindow : nsISuppo
    * Open in a new window.
    */
   const short OPEN_NEWWINDOW     = 2;
   /**
    * Open in a new content tab in the toplevel browser window corresponding to
    * this nsIBrowserDOMWindow.
    */
   const short OPEN_NEWTAB        = 3;
+  /**
+   * Open in an existing content tab based on the URI. If a match can't be
+   * found, revert to OPEN_NEWTAB behavior.
+   */
+  const short OPEN_SWITCHTAB     = 4;
 
   /**
    * Values for openURI's aContext parameter.  These affect the behavior of
    * OPEN_DEFAULTWINDOW.
    */
   /**
    * external link (load request from another application, xremote, etc).
    */
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1946,36 +1946,36 @@ abstract public class GeckoApp
             Log.i(LOGTAG, "Intent : ACTION_DEBUG - waiting 5s before launching");
             return;
         }
         if (checkLaunchState(LaunchState.WaitForDebugger) || intent == getIntent())
             return;
 
         if (Intent.ACTION_MAIN.equals(action)) {
             Log.i(LOGTAG, "Intent : ACTION_MAIN");
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createLoadEvent(""));
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(""));
         }
         else if (ACTION_LOAD.equals(action)) {
             String uri = intent.getDataString();
             loadUrl(uri, AwesomeBar.Type.EDIT);
             Log.i(LOGTAG,"onNewIntent: " + uri);
         }
         else if (Intent.ACTION_VIEW.equals(action)) {
             String uri = intent.getDataString();
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createLoadEvent(uri));
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
             Log.i(LOGTAG,"onNewIntent: " + uri);
         }
         else if (ACTION_WEBAPP.equals(action)) {
             String uri = getURIFromIntent(intent);
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createLoadEvent(uri));
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createWebappLoadEvent(uri));
             Log.i(LOGTAG,"Intent : WEBAPP - " + uri);
         }
         else if (ACTION_BOOKMARK.equals(action)) {
             String uri = getURIFromIntent(intent);
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createLoadEvent(uri));
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createBookmarkLoadEvent(uri));
             Log.i(LOGTAG,"Intent : BOOKMARK - " + uri);
         }
     }
 
     /*
      * Handles getting a uri from and intent in a way that is backwards
      * compatable with our previous implementations
      */
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -425,33 +425,35 @@ public class GeckoAppShell
             DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
 
             GeckoAppShell.putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator());
             GeckoAppShell.putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator());
             GeckoAppShell.putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize());
         }
     }
 
-    public static void runGecko(String apkPath, String args, String url, boolean restoreSession) {
+    public static void runGecko(String apkPath, String args, String url, String type, boolean restoreSession) {
         // run gecko -- it will spawn its own thread
         GeckoAppShell.nativeInit();
 
         Log.i(LOGTAG, "post native init");
 
         // Tell Gecko where the target byte buffer is for rendering
         GeckoAppShell.setLayerClient(GeckoApp.mAppContext.getLayerClient());
 
         Log.i(LOGTAG, "setLayerClient called");
 
         // First argument is the .apk path
         String combinedArgs = apkPath + " -greomni " + apkPath;
         if (args != null)
             combinedArgs += " " + args;
         if (url != null)
-            combinedArgs += " -remote " + url;
+            combinedArgs += " -url " + url;
+        if (type != null)
+            combinedArgs += " " + type;
         if (restoreSession)
             combinedArgs += " -restoresession";
 
         DisplayMetrics metrics = new DisplayMetrics();
         GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
         combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels;
 
         GeckoApp.mAppContext.runOnUiThread(new Runnable() {
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -437,19 +437,34 @@ public class GeckoEvent {
           .append(", \"y\" : ").append(origin.y)
           .append(", \"zoom\" : ").append(viewport.getZoomFactor())
           .append(", \"displayPort\" :").append(displayPort.toJSON())
           .append('}');
         event.mCharactersExtra = sb.toString();
         return event;
     }
 
-    public static GeckoEvent createLoadEvent(String uri) {
+    public static GeckoEvent createURILoadEvent(String uri) {
         GeckoEvent event = new GeckoEvent(LOAD_URI);
         event.mCharacters = uri;
+        event.mCharactersExtra = "";
+        return event;
+    }
+
+    public static GeckoEvent createWebappLoadEvent(String uri) {
+        GeckoEvent event = new GeckoEvent(LOAD_URI);
+        event.mCharacters = uri;
+        event.mCharactersExtra = "-webapp";
+        return event;
+    }
+
+    public static GeckoEvent createBookmarkLoadEvent(String uri) {
+        GeckoEvent event = new GeckoEvent(LOAD_URI);
+        event.mCharacters = uri;
+        event.mCharactersExtra = "-bookmark";
         return event;
     }
 
     public static GeckoEvent createVisitedEvent(String data) {
         GeckoEvent event = new GeckoEvent(VISITED);
         event.mCharacters = data;
         return event;
     }
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -96,16 +96,23 @@ public class GeckoThread extends Thread 
         Locale.setDefault(locale);
         Resources res = app.getBaseContext().getResources();
         Configuration config = res.getConfiguration();
         config.locale = locale;
         res.updateConfiguration(config, res.getDisplayMetrics());
 
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - runGecko");
 
+        // find the right intent type
+        final String action = mIntent.getAction();
+        String type = GeckoApp.ACTION_WEBAPP.equals(action) ? "-webapp" :
+                      GeckoApp.ACTION_BOOKMARK.equals(action) ? "-bookmark" :
+                      null;
+
         // and then fire us up
-        Log.w(LOGTAG, "RunGecko - URI = " + mUri);
+        Log.i(LOGTAG, "RunGecko - URI = " + mUri);
         GeckoAppShell.runGecko(app.getApplication().getPackageResourcePath(),
                                mIntent.getStringExtra("args"),
                                mUri,
+                               type,
                                mRestoreSession);
     }
 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -248,25 +248,28 @@ var BrowserApp = {
     // Init LoginManager
     Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
     // Init FormHistory
     Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2);
 
     let loadParams = {};
     let url = "about:home";
     let forceRestore = false;
+    let pinned = false;
     if ("arguments" in window) {
       if (window.arguments[0])
         url = window.arguments[0];
       if (window.arguments[1])
         forceRestore = window.arguments[1];
       if (window.arguments[2])
         gScreenWidth = window.arguments[2];
       if (window.arguments[3])
         gScreenHeight = window.arguments[3];
+      if (window.arguments[4])
+        pinned = window.arguments[4];
     }
 
     if (url == "about:empty")
       loadParams.flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
 
     // XXX maybe we don't do this if the launch was kicked off from external
     Services.io.offline = false;
 
@@ -284,16 +287,17 @@ var BrowserApp = {
       sendMessageToJava({
         gecko: {
           type: "Session:RestoreBegin"
         }
       });
 
       // Open any commandline URLs, except the homepage
       if (url && url != "about:home") {
+        loadParams.pinned = pinned;
         this.addTab(url, loadParams);
       } else {
         // Let the session make a restored tab active
         restoreToFront = true;
       }
 
       // Be ready to handle any restore failures by making sure we have a valid tab opened
       let restoreCleanup = {
@@ -314,16 +318,17 @@ var BrowserApp = {
         }
       };
       Services.obs.addObserver(restoreCleanup, "sessionstore-windows-restored", false);
 
       // Start the restore
       ss.restoreLastSession(restoreToFront, forceRestore);
     } else {
       loadParams.showProgress = (url != "about:home");
+      loadParams.pinned = pinned;
       this.addTab(url, loadParams);
 
       // show telemetry door hanger if we aren't restoring a session
       this._showTelemetryPrompt();
     }
 
     if (this.isAppUpdated())
       this.onAppUpdated();
@@ -565,16 +570,22 @@ var BrowserApp = {
 
     let newTab = new Tab(aURI, aParams);
     this._tabs.push(newTab);
 
     let selected = "selected" in aParams ? aParams.selected : true;
     if (selected)
       this.selectedTab = newTab;
 
+    let pinned = "pinned" in aParams ? aParams.pinned : false;
+    if (pinned) {
+      let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+      ss.setTabValue(newTab, "appOrigin", aURI);
+    }
+
     let evt = document.createEvent("UIEvents");
     evt.initUIEvent("TabOpen", true, false, window, null);
     newTab.browser.dispatchEvent(evt);
 
     return newTab;
   },
 
   // Use this method to close a tab from JS. This method sends a message
@@ -1435,32 +1446,53 @@ nsBrowserAccess.prototype = {
     let referrer;
     if (aOpener) {
       try {
         let location = aOpener.location;
         referrer = Services.io.newURI(location, null, null);
       } catch(e) { }
     }
 
-    let newTab = (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW || aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB);
+    let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+    let pinned = false;
+
+    if (aURI && aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB) {
+      pinned = true;
+      let spec = aURI.spec;
+      let tabs = BrowserApp.tabs;
+      for (let i = 0; i < tabs.length; i++) {
+        let appOrigin = ss.getTabValue(tabs[i], "appOrigin");
+        if (appOrigin == spec) {
+          let tab = tabs[i];
+          BrowserApp.selectTab(tab);
+          return tab.browser;
+        }
+      }
+    }
+
+    let newTab = (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW ||
+                  aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB ||
+                  aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB);
 
     if (newTab) {
       let parentId = -1;
       if (!isExternal) {
         let parent = BrowserApp.getTabForWindow(aOpener.top);
         if (parent)
           parentId = parent.id;
       }
 
       // BrowserApp.addTab calls loadURIWithFlags with the appropriate params
       let tab = BrowserApp.addTab(aURI ? aURI.spec : "about:blank", { flags: loadflags,
                                                                       referrerURI: referrer,
                                                                       external: isExternal,
                                                                       parentId: parentId,
-                                                                      selected: true });
+                                                                      selected: true,
+                                                                      pinned: pinned });
+
       return tab.browser;
     }
 
     // OPEN_CURRENTWINDOW and illegal values
     let browser = BrowserApp.selectedBrowser;
     if (aURI && browser)
       browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
 
@@ -4613,22 +4645,20 @@ var WebappsUI = {
     let tabs = BrowserApp.tabs;
     let tab = null;
     for (let i = 0; i < tabs.length; i++) {
       let appOrigin = ss.getTabValue(tabs[i], "appOrigin");
       if (appOrigin == aOrigin)
         tab = tabs[i];
     }
 
-    if (tab) {
+    if (tab)
       BrowserApp.selectTab(tab);
-    } else {
-      tab = BrowserApp.addTab(aURI);
-      ss.setTabValue(tab, "appOrigin", aOrigin);
-    }
+    else
+      BrowserApp.addTab(aURI, { pinned: true });
   }
 }
 
 var RemoteDebugger = {
   init: function rd_init() {
     Services.prefs.addObserver("remote-debugger.", this, false);
 
     if (this._isEnabled())
--- a/mobile/android/components/BrowserCLH.js
+++ b/mobile/android/components/BrowserCLH.js
@@ -9,31 +9,34 @@ Cu.import("resource://gre/modules/Servic
 function dump(a) {
   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
 }
 
 function openWindow(aParent, aURL, aTarget, aFeatures, aArgs) {
   let argsArray = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
   let urlString = null;
   let restoreSessionBool = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+  let pinnedBool = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
   let widthInt = Cc["@mozilla.org/supports-PRInt32;1"].createInstance(Ci.nsISupportsPRInt32);
   let heightInt = Cc["@mozilla.org/supports-PRInt32;1"].createInstance(Ci.nsISupportsPRInt32);
 
   if ("url" in aArgs) {
     urlString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
     urlString.data = aArgs.url;
   }
   restoreSessionBool.data = "restoreSession" in aArgs ? aArgs.restoreSession : false;
   widthInt.data = "width" in aArgs ? aArgs.width : 1;
   heightInt.data = "height" in aArgs ? aArgs.height : 1;
+  pinnedBool.data = "pinned" in aArgs ? aArgs.pinned : false;
 
   argsArray.AppendElement(urlString, false);
   argsArray.AppendElement(restoreSessionBool, false);
   argsArray.AppendElement(widthInt, false);
   argsArray.AppendElement(heightInt, false);
+  argsArray.AppendElement(pinnedBool, false);
   return Services.ww.openWindow(aParent, aURL, aTarget, aFeatures, argsArray);
 }
 
 
 function resolveURIInternal(aCmdLine, aArgument) {
   let uri = aCmdLine.resolveURI(aArgument);
   if (uri)
     return uri;
@@ -47,45 +50,54 @@ function resolveURIInternal(aCmdLine, aA
 
   return uri;
 }
 
 function BrowserCLH() {}
 
 BrowserCLH.prototype = {
   handle: function fs_handle(aCmdLine) {
-    let urlParam = "about:home";
+    let openURL = "about:home";
+    let pinned = false;
+
     let restoreSession = false;
     let width = 1;
     let height = 1;
+
     try {
-      urlParam = aCmdLine.handleFlagWithParam("remote", false);
+      openURL = aCmdLine.handleFlagWithParam("url", false);
     } catch (e) { /* Optional */ }
     try {
+      pinned = aCmdLine.handleFlag("webapp", false);
+    } catch (e) { /* Optional */ }
+
+    try {
       restoreSession = aCmdLine.handleFlag("restoresession", false);
     } catch (e) { /* Optional */ }
     try {
       width = aCmdLine.handleFlagWithParam("width", false);
     } catch (e) { /* Optional */ }
     try {
       height = aCmdLine.handleFlagWithParam("height", false);
     } catch (e) { /* Optional */ }
 
     try {
-      let uri = resolveURIInternal(aCmdLine, urlParam);
+      let uri = resolveURIInternal(aCmdLine, openURL);
       if (!uri)
         return;
 
       let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
       if (browserWin) {
-        browserWin.browserDOMWindow.openURI(uri, null, Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+        let whereFlags = pinned ? Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB : Ci.nsIBrowserDOMWindow.OPEN_NEWTAB;
+        browserWin.browserDOMWindow.openURI(uri, null, whereFlags, Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
       } else {
         let args = {
-          url: urlParam,
+          url: openURL,
           restoreSession: restoreSession,
+          pinned: pinned,
           width: width,
           height: height
         };
         browserWin = openWindow(null, "chrome://browser/content/browser.xul", "_blank", "chrome,dialog=no,all", args);
       }
 
       aCmdLine.preventDefault = true;
     } catch (x) {
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -480,16 +480,17 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
         case LOCATION_EVENT: {
             jobject location = jenv->GetObjectField(jobj, jLocationField);
             mGeoPosition = AndroidLocation::CreateGeoPosition(jenv, location);
             break;
         }
 
         case LOAD_URI: {
             ReadCharactersField(jenv);
+            ReadCharactersExtraField(jenv);
             break;
         }
 
         case VIEWPORT:
         case BROADCAST: {
             ReadCharactersField(jenv);
             ReadCharactersExtraField(jenv);
             break;
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -412,25 +412,30 @@ nsAppShell::ProcessNextNativeEvent(bool 
 
         if (curEvent->Characters().Length() == 0)
             break;
 
         char *uri = ToNewUTF8String(curEvent->Characters());
         if (!uri)
             break;
 
-        const char *argv[3] = {
+        char *flag = ToNewUTF8String(curEvent->CharactersExtra());
+
+        const char *argv[4] = {
             "dummyappname",
-            "-remote",
-            uri
+            "-url",
+            uri,
+            flag ? flag : ""
         };
-        nsresult rv = cmdline->Init(3, const_cast<char **>(argv), nsnull, nsICommandLine::STATE_REMOTE_AUTO);
+        nsresult rv = cmdline->Init(4, const_cast<char **>(argv), nsnull, nsICommandLine::STATE_REMOTE_AUTO);
         if (NS_SUCCEEDED(rv))
             cmdline->Run();
         nsMemory::Free(uri);
+        if (flag)
+            nsMemory::Free(flag);
         break;
     }
 
     case AndroidGeckoEvent::SIZE_CHANGED: {
         // store the last resize event to dispatch it to new windows with a FORCED_RESIZE event
         if (curEvent != gLastSizeChange) {
             gLastSizeChange = new AndroidGeckoEvent(curEvent);
         }