dom/inputmethod/MozKeyboard.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const Cc = Components.classes;
     8 const Ci = Components.interfaces;
     9 const Cu = Components.utils;
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    12 Cu.import("resource://gre/modules/Services.jsm");
    13 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
    15 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
    16   "@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender");
    18 XPCOMUtils.defineLazyServiceGetter(this, "tm",
    19   "@mozilla.org/thread-manager;1", "nsIThreadManager");
    21 /*
    22  * A WeakMap to map input method iframe window to its active status.
    23  */
    24 let WindowMap = {
    25   // WeakMap of <window, boolean> pairs.
    26   _map: null,
    28   /*
    29    * Check if the given window is active.
    30    */
    31   isActive: function(win) {
    32     if (!this._map || !win) {
    33       return false;
    34     }
    35     return this._map.get(win, false);
    36   },
    38   /*
    39    * Set the active status of the given window.
    40    */
    41   setActive: function(win, isActive) {
    42     if (!win) {
    43       return;
    44     }
    45     if (!this._map) {
    46       this._map = new WeakMap();
    47     }
    48     this._map.set(win, isActive);
    49   }
    50 };
    52 /**
    53  * ==============================================
    54  * InputMethodManager
    55  * ==============================================
    56  */
    57 function MozInputMethodManager(win) {
    58   this._window = win;
    59 }
    61 MozInputMethodManager.prototype = {
    62   _supportsSwitching: false,
    63   _window: null,
    65   classID: Components.ID("{7e9d7280-ef86-11e2-b778-0800200c9a66}"),
    67   QueryInterface: XPCOMUtils.generateQI([]),
    69   showAll: function() {
    70     if (!WindowMap.isActive(this._window)) {
    71       return;
    72     }
    73     cpmm.sendAsyncMessage('Keyboard:ShowInputMethodPicker', {});
    74   },
    76   next: function() {
    77     if (!WindowMap.isActive(this._window)) {
    78       return;
    79     }
    80     cpmm.sendAsyncMessage('Keyboard:SwitchToNextInputMethod', {});
    81   },
    83   supportsSwitching: function() {
    84     if (!WindowMap.isActive(this._window)) {
    85       return false;
    86     }
    87     return this._supportsSwitching;
    88   },
    90   hide: function() {
    91     if (!WindowMap.isActive(this._window)) {
    92       return;
    93     }
    94     cpmm.sendAsyncMessage('Keyboard:RemoveFocus', {});
    95   }
    96 };
    98 /**
    99  * ==============================================
   100  * InputMethod
   101  * ==============================================
   102  */
   103 function MozInputMethod() { }
   105 MozInputMethod.prototype = {
   106   _inputcontext: null,
   107   _layouts: {},
   108   _window: null,
   109   _isSystem: false,
   110   _isKeyboard: true,
   112   classID: Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"),
   114   QueryInterface: XPCOMUtils.generateQI([
   115     Ci.nsIDOMGlobalPropertyInitializer,
   116     Ci.nsIObserver,
   117     Ci.nsISupportsWeakReference
   118   ]),
   120   init: function mozInputMethodInit(win) {
   121     this._window = win;
   122     this._mgmt = new MozInputMethodManager(win);
   123     this.innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
   124                             .getInterface(Ci.nsIDOMWindowUtils)
   125                             .currentInnerWindowID;
   127     Services.obs.addObserver(this, "inner-window-destroyed", false);
   129     let principal = win.document.nodePrincipal;
   130     let perm = Services.perms.testExactPermissionFromPrincipal(principal,
   131                                                                "input-manage");
   132     if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) {
   133       this._isSystem = true;
   134     }
   136     // Check if we can use keyboard related APIs.
   137     let testing = false;
   138     try {
   139       testing = Services.prefs.getBoolPref("dom.mozInputMethod.testing");
   140     } catch (e) {
   141     }
   142     perm = Services.perms.testExactPermissionFromPrincipal(principal, "input");
   143     if (!testing && perm !== Ci.nsIPermissionManager.ALLOW_ACTION) {
   144       this._isKeyboard = false;
   145       return;
   146     }
   148     cpmm.addWeakMessageListener('Keyboard:FocusChange', this);
   149     cpmm.addWeakMessageListener('Keyboard:SelectionChange', this);
   150     cpmm.addWeakMessageListener('Keyboard:GetContext:Result:OK', this);
   151     cpmm.addWeakMessageListener('Keyboard:LayoutsChange', this);
   152   },
   154   uninit: function mozInputMethodUninit() {
   155     this._window = null;
   156     this._mgmt = null;
   157     Services.obs.removeObserver(this, "inner-window-destroyed");
   158     if (!this._isKeyboard) {
   159       return;
   160     }
   162     cpmm.removeWeakMessageListener('Keyboard:FocusChange', this);
   163     cpmm.removeWeakMessageListener('Keyboard:SelectionChange', this);
   164     cpmm.removeWeakMessageListener('Keyboard:GetContext:Result:OK', this);
   165     cpmm.removeWeakMessageListener('Keyboard:LayoutsChange', this);
   166     this.setActive(false);
   167   },
   169   receiveMessage: function mozInputMethodReceiveMsg(msg) {
   170     if (!WindowMap.isActive(this._window)) {
   171       return;
   172     }
   174     let json = msg.json;
   176     switch(msg.name) {
   177       case 'Keyboard:FocusChange':
   178         if (json.type !== 'blur') {
   179           // XXX Bug 904339 could receive 'text' event twice
   180           this.setInputContext(json);
   181         }
   182         else {
   183           this.setInputContext(null);
   184         }
   185         break;
   186       case 'Keyboard:SelectionChange':
   187         if (this.inputcontext) {
   188           this._inputcontext.updateSelectionContext(json);
   189         }
   190         break;
   191       case 'Keyboard:GetContext:Result:OK':
   192         this.setInputContext(json);
   193         break;
   194       case 'Keyboard:LayoutsChange':
   195         this._layouts = json;
   196         break;
   197     }
   198   },
   200   observe: function mozInputMethodObserve(subject, topic, data) {
   201     let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
   202     if (wId == this.innerWindowID)
   203       this.uninit();
   204   },
   206   get mgmt() {
   207     return this._mgmt;
   208   },
   210   get inputcontext() {
   211     if (!WindowMap.isActive(this._window)) {
   212       return null;
   213     }
   214     return this._inputcontext;
   215   },
   217   set oninputcontextchange(handler) {
   218     this.__DOM_IMPL__.setEventHandler("oninputcontextchange", handler);
   219   },
   221   get oninputcontextchange() {
   222     return this.__DOM_IMPL__.getEventHandler("oninputcontextchange");
   223   },
   225   setInputContext: function mozKeyboardContextChange(data) {
   226     if (this._inputcontext) {
   227       this._inputcontext.destroy();
   228       this._inputcontext = null;
   229       this._mgmt._supportsSwitching = false;
   230     }
   232     if (data) {
   233       this._mgmt._supportsSwitching = this._layouts[data.type] ?
   234         this._layouts[data.type] > 1 :
   235         false;
   237       this._inputcontext = new MozInputContext(data);
   238       this._inputcontext.init(this._window);
   239     }
   241     let event = new this._window.Event("inputcontextchange",
   242                                        Cu.cloneInto({}, this._window));
   243     this.__DOM_IMPL__.dispatchEvent(event);
   244   },
   246   setActive: function mozInputMethodSetActive(isActive) {
   247     if (WindowMap.isActive(this._window) === isActive) {
   248       return;
   249     }
   251     WindowMap.setActive(this._window, isActive);
   253     if (isActive) {
   254       // Activate current input method.
   255       // If there is already an active context, then this will trigger
   256       // a GetContext:Result:OK event, and we can initialize ourselves.
   257       // Otherwise silently ignored.
   258       cpmm.sendAsyncMessage('Keyboard:Register', {});
   259       cpmm.sendAsyncMessage("Keyboard:GetContext", {});
   260     } else {
   261       // Deactive current input method.
   262       cpmm.sendAsyncMessage('Keyboard:Unregister', {});
   263       if (this._inputcontext) {
   264         this.setInputContext(null);
   265       }
   266     }
   267   },
   269   setValue: function(value) {
   270     this._ensureIsSystem();
   271     cpmm.sendAsyncMessage('System:SetValue', {
   272       'value': value
   273     });
   274   },
   276   setSelectedOption: function(index) {
   277     this._ensureIsSystem();
   278     cpmm.sendAsyncMessage('System:SetSelectedOption', {
   279       'index': index
   280     });
   281   },
   283   setSelectedOptions: function(indexes) {
   284     this._ensureIsSystem();
   285     cpmm.sendAsyncMessage('System:SetSelectedOptions', {
   286       'indexes': indexes
   287     });
   288   },
   290   removeFocus: function() {
   291     this._ensureIsSystem();
   292     cpmm.sendAsyncMessage('System:RemoveFocus', {});
   293   },
   295   _ensureIsSystem: function() {
   296     if (!this._isSystem) {
   297       throw new this._window.DOMError("Security",
   298                                       "Should have 'input-manage' permssion.");
   299     }
   300   }
   301 };
   303  /**
   304  * ==============================================
   305  * InputContext
   306  * ==============================================
   307  */
   308 function MozInputContext(ctx) {
   309   this._context = {
   310     inputtype: ctx.type,
   311     inputmode: ctx.inputmode,
   312     lang: ctx.lang,
   313     type: ["textarea", "contenteditable"].indexOf(ctx.type) > -1 ?
   314               ctx.type :
   315               "text",
   316     selectionStart: ctx.selectionStart,
   317     selectionEnd: ctx.selectionEnd,
   318     textBeforeCursor: ctx.textBeforeCursor,
   319     textAfterCursor: ctx.textAfterCursor
   320   };
   322   this._contextId = ctx.contextId;
   323 }
   325 MozInputContext.prototype = {
   326   __proto__: DOMRequestIpcHelper.prototype,
   328   _window: null,
   329   _context: null,
   330   _contextId: -1,
   332   classID: Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
   334   QueryInterface: XPCOMUtils.generateQI([
   335     Ci.nsIObserver,
   336     Ci.nsISupportsWeakReference
   337   ]),
   339   init: function ic_init(win) {
   340     this._window = win;
   341     this._utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
   342                      .getInterface(Ci.nsIDOMWindowUtils);
   343     this.initDOMRequestHelper(win,
   344       ["Keyboard:GetText:Result:OK",
   345        "Keyboard:GetText:Result:Error",
   346        "Keyboard:SetSelectionRange:Result:OK",
   347        "Keyboard:ReplaceSurroundingText:Result:OK",
   348        "Keyboard:SendKey:Result:OK",
   349        "Keyboard:SendKey:Result:Error",
   350        "Keyboard:SetComposition:Result:OK",
   351        "Keyboard:EndComposition:Result:OK",
   352        "Keyboard:SequenceError"]);
   353   },
   355   destroy: function ic_destroy() {
   356     let self = this;
   358     // All requests that are still pending need to be invalidated
   359     // because the context is no longer valid.
   360     this.forEachPromiseResolver(function(k) {
   361       self.takePromiseResolver(k).reject("InputContext got destroyed");
   362     });
   363     this.destroyDOMRequestHelper();
   365     // A consuming application might still hold a cached version of
   366     // this object. After destroying all methods will throw because we
   367     // cannot create new promises anymore, but we still hold
   368     // (outdated) information in the context. So let's clear that out.
   369     for (var k in this._context) {
   370       if (this._context.hasOwnProperty(k)) {
   371         this._context[k] = null;
   372       }
   373     }
   375     this._window = null;
   376   },
   378   receiveMessage: function ic_receiveMessage(msg) {
   379     if (!msg || !msg.json) {
   380       dump('InputContext received message without data\n');
   381       return;
   382     }
   384     let json = msg.json;
   385     let resolver = this.takePromiseResolver(json.requestId);
   387     if (!resolver) {
   388       return;
   389     }
   391     switch (msg.name) {
   392       case "Keyboard:SendKey:Result:OK":
   393         resolver.resolve();
   394         break;
   395       case "Keyboard:SendKey:Result:Error":
   396         resolver.reject(json.error);
   397         break;
   398       case "Keyboard:GetText:Result:OK":
   399         resolver.resolve(json.text);
   400         break;
   401       case "Keyboard:GetText:Result:Error":
   402         resolver.reject(json.error);
   403         break;
   404       case "Keyboard:SetSelectionRange:Result:OK":
   405       case "Keyboard:ReplaceSurroundingText:Result:OK":
   406         resolver.resolve(
   407           Cu.cloneInto(json.selectioninfo, this._window));
   408         break;
   409       case "Keyboard:SequenceError":
   410         // Occurs when a new element got focus, but the inputContext was
   411         // not invalidated yet...
   412         resolver.reject("InputContext has expired");
   413         break;
   414       case "Keyboard:SetComposition:Result:OK": // Fall through.
   415       case "Keyboard:EndComposition:Result:OK":
   416         resolver.resolve();
   417         break;
   418       default:
   419         dump("Could not find a handler for " + msg.name);
   420         resolver.reject();
   421         break;
   422     }
   423   },
   425   updateSelectionContext: function ic_updateSelectionContext(ctx) {
   426     if (!this._context) {
   427       return;
   428     }
   430     let selectionDirty = this._context.selectionStart !== ctx.selectionStart ||
   431           this._context.selectionEnd !== ctx.selectionEnd;
   432     let surroundDirty = this._context.textBeforeCursor !== ctx.textBeforeCursor ||
   433           this._context.textAfterCursor !== ctx.textAfterCursor;
   435     this._context.selectionStart = ctx.selectionStart;
   436     this._context.selectionEnd = ctx.selectionEnd;
   437     this._context.textBeforeCursor = ctx.textBeforeCursor;
   438     this._context.textAfterCursor = ctx.textAfterCursor;
   440     if (selectionDirty) {
   441       this._fireEvent("selectionchange", {
   442         selectionStart: ctx.selectionStart,
   443         selectionEnd: ctx.selectionEnd
   444       });
   445     }
   447     if (surroundDirty) {
   448       this._fireEvent("surroundingtextchange", {
   449         beforeString: ctx.textBeforeCursor,
   450         afterString: ctx.textAfterCursor
   451       });
   452     }
   453   },
   455   _fireEvent: function ic_fireEvent(eventName, aDetail) {
   456     let detail = {
   457       detail: aDetail
   458     };
   460     let event = new this._window.Event(eventName,
   461                                        Cu.cloneInto(aDetail, this._window));
   462     this.__DOM_IMPL__.dispatchEvent(event);
   463   },
   465   // tag name of the input field
   466   get type() {
   467     return this._context.type;
   468   },
   470   // type of the input field
   471   get inputType() {
   472     return this._context.inputtype;
   473   },
   475   get inputMode() {
   476     return this._context.inputmode;
   477   },
   479   get lang() {
   480     return this._context.lang;
   481   },
   483   getText: function ic_getText(offset, length) {
   484     let self = this;
   485     return this._sendPromise(function(resolverId) {
   486       cpmm.sendAsyncMessage('Keyboard:GetText', {
   487         contextId: self._contextId,
   488         requestId: resolverId,
   489         offset: offset,
   490         length: length
   491       });
   492     });
   493   },
   495   get selectionStart() {
   496     return this._context.selectionStart;
   497   },
   499   get selectionEnd() {
   500     return this._context.selectionEnd;
   501   },
   503   get textBeforeCursor() {
   504     return this._context.textBeforeCursor;
   505   },
   507   get textAfterCursor() {
   508     return this._context.textAfterCursor;
   509   },
   511   setSelectionRange: function ic_setSelectionRange(start, length) {
   512     let self = this;
   513     return this._sendPromise(function(resolverId) {
   514       cpmm.sendAsyncMessage("Keyboard:SetSelectionRange", {
   515         contextId: self._contextId,
   516         requestId: resolverId,
   517         selectionStart: start,
   518         selectionEnd: start + length
   519       });
   520     });
   521   },
   523   get onsurroundingtextchange() {
   524     return this.__DOM_IMPL__.getEventHandler("onsurroundingtextchange");
   525   },
   527   set onsurroundingtextchange(handler) {
   528     this.__DOM_IMPL__.setEventHandler("onsurroundingtextchange", handler);
   529   },
   531   get onselectionchange() {
   532     return this.__DOM_IMPL__.getEventHandler("onselectionchange");
   533   },
   535   set onselectionchange(handler) {
   536     this.__DOM_IMPL__.setEventHandler("onselectionchange", handler);
   537   },
   539   replaceSurroundingText: function ic_replaceSurrText(text, offset, length) {
   540     let self = this;
   541     return this._sendPromise(function(resolverId) {
   542       cpmm.sendAsyncMessage('Keyboard:ReplaceSurroundingText', {
   543         contextId: self._contextId,
   544         requestId: resolverId,
   545         text: text,
   546         offset: offset || 0,
   547         length: length || 0
   548       });
   549     });
   550   },
   552   deleteSurroundingText: function ic_deleteSurrText(offset, length) {
   553     return this.replaceSurroundingText(null, offset, length);
   554   },
   556   sendKey: function ic_sendKey(keyCode, charCode, modifiers, repeat) {
   557     let self = this;
   558     return this._sendPromise(function(resolverId) {
   559       cpmm.sendAsyncMessage('Keyboard:SendKey', {
   560         contextId: self._contextId,
   561         requestId: resolverId,
   562         keyCode: keyCode,
   563         charCode: charCode,
   564         modifiers: modifiers,
   565         repeat: repeat
   566       });
   567     });
   568   },
   570   setComposition: function ic_setComposition(text, cursor, clauses) {
   571     let self = this;
   572     return this._sendPromise(function(resolverId) {
   573       cpmm.sendAsyncMessage('Keyboard:SetComposition', {
   574         contextId: self._contextId,
   575         requestId: resolverId,
   576         text: text,
   577         cursor: cursor || text.length,
   578         clauses: clauses || null
   579       });
   580     });
   581   },
   583   endComposition: function ic_endComposition(text) {
   584     let self = this;
   585     return this._sendPromise(function(resolverId) {
   586       cpmm.sendAsyncMessage('Keyboard:EndComposition', {
   587         contextId: self._contextId,
   588         requestId: resolverId,
   589         text: text || ''
   590       });
   591     });
   592   },
   594   _sendPromise: function(callback) {
   595     let self = this;
   596     return this.createPromise(function(resolve, reject) {
   597       let resolverId = self.getPromiseResolverId({ resolve: resolve, reject: reject });
   598       if (!WindowMap.isActive(self._window)) {
   599         self.removePromiseResolver(resolverId);
   600         reject('Input method is not active.');
   601         return;
   602       }
   603       callback(resolverId);
   604     });
   605   }
   606 };
   608 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozInputMethod]);

mercurial