Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (function (nativeMsContentScript) {
- var nativeJSON = window.JSON;
- // Synchronous function are from syncmethodhandler.h. They should be kept in alphabetical order.
- const functionId_getIsInPrivate = 2;
- const functionId_getMessage = 3;
- const functionId_getUILanguage = 4;
- const functionId_permissionsCheckInternal = 5;
- // These values should never be touched.
- const componentId_extensionTrident = 1;
- const componentId_manager = 4;
- // These values are pulled from bhxmessages.h. You may notice they are in alphabetical order (hint, hint).
- // During the routing, the starting enumeration value is added/subtracted for outgoing/incoming messages
- // respectively, so the id values here should be the offsets.
- // Bhx::Data::ExtensionManager::EventId
- const eventId_storageOnChanged = 8;
- // Bhx::Message::ExtensionManager
- const functionId_notifyEventListener = 10;
- const functionId_storageLocalGet = 17;
- const functionId_storageLocalRemove = 24;
- const functionId_storageLocalSet = 18;
- // Permission values.
- const permissionId_storage = 14;
- // API used constants
- const i18n_maxAllowedSubstitutions = 9;
- var storedHandlers = [];
- var storedCallbacks = [];
- var nextCallback = 0;
- Object.defineProperty(window, "msBrowser", {
- get: delayInitMsBrowser,
- configurable: true,
- enumerable: true
- });
- function delayInitMsBrowser() {
- var wrapMsBrowser = Object.defineProperties({}, {
- "extension": {
- get: delayInitExtension,
- configurable: true,
- enumerable: true
- },
- "i18n": {
- get: delayInitI18n,
- configurable: true,
- enumerable: true
- },
- "runtime": {
- get: delayInitRuntime,
- configurable: true,
- enumerable: true
- },
- "storage": {
- get: delayInitStorage,
- configurable: true,
- enumerable: true
- }
- });
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window, "msBrowser", {
- value: wrapMsBrowser,
- configurable: true,
- enumerable: true
- });
- return wrapMsBrowser;
- }
- function delayInitExtension() {
- var wrapExtension = Object.defineProperties({}, {
- "inPrivateContext": {
- get: extension_inPrivateContext_indirect,
- configurable: true,
- enumerable: true
- }
- });
- function extension_inPrivateContext_indirect() {
- var isInPrivate = false;
- var undef;
- var syncValue = ExecuteGenericSynchronousFunction(functionId_getIsInPrivate, undef);
- if (typeof(syncValue) == "boolean") {
- isInPrivate = syncValue;
- }
- return isInPrivate;
- }
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser, "extension", {
- value: wrapExtension,
- configurable: true,
- enumerable: true
- });
- return wrapExtension;
- }
- function delayInitI18n() {
- var wrapI18n = Object.defineProperties({}, {
- "getUILanguage": {
- get: i18n_getUILanguage_indirect,
- configurable: true,
- enumerable: true
- },
- "getMessage": {
- value: i18n_getMessage_indirect,
- configurable: true,
- enumerable: true
- }
- });
- function i18n_getUILanguage_indirect() {
- var UILanguage = "";
- var undef;
- var syncValue = ExecuteGenericSynchronousFunction(functionId_getUILanguage, undef);
- if (typeof (syncValue) == "string") {
- UILanguage = syncValue;
- }
- return UILanguage;
- }
- function i18n_getMessage_indirect() {
- var expectedArguments = [
- { type: "string", name: "messageName" },
- { type: "string or array", name: "substitutions", optional: true }];
- TestParameterTypes("i18n.getMessage", arguments, expectedArguments);
- var message = null;
- var messageName = null;
- var substCount = 0; // 0 if there's no substring
- if (arguments.length == 2) {
- if (GetTypeName(arguments[1]) === "array") {
- substCount = arguments[1].length;
- if (substCount > i18n_maxAllowedSubstitutions) {
- throw "Error: Substitution strings exceed maximum allowance";
- }
- for (var i = 0; i < substCount; i++) {
- if (typeof (arguments[0][i]) !== "string") {
- throw "Error: Invalid value for argument 1. All items in the array must be strings.";
- }
- }
- }
- else if (GetTypeName(arguments[1]) === "string") {
- substCount = 1;
- }
- }
- var parameters = {};
- parameters.messageName = arguments[0];
- parameters.substCount = substCount;
- parameters.substitutions = (substCount > 0) ? arguments[1] : null;
- var messageValue = ExecuteGenericSynchronousFunction(functionId_getMessage, parameters);
- if (typeof (messageValue) == "string") {
- message = messageValue;
- }
- return message;
- };
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser, "i18n", {
- value: wrapI18n,
- configurable: true,
- enumerable: true
- });
- return wrapI18n;
- }
- function delayInitRuntime() {
- var wrapRuntime = Object.defineProperties({}, {
- "getURL": {
- value: getUrlIndirect.bind(nativeMsContentScript),
- configurable: true,
- enumerable: true
- },
- "sendMessage": {
- value: sendMessageIndirect.bind(nativeMsContentScript),
- configurable: true,
- enumerable: true
- },
- "onMessage": {
- get: delayInitOnMessage,
- configurable: true,
- enumerable: true
- }
- });
- function getUrlIndirect(resource) {
- var extensionId = this.getExtensionId();
- var url = "ms-browser-extension://" + extensionId;
- if(resource.substr(0, 1) !== "/")
- url += "/";
- url += resource;
- return url;
- }
- function callbackIndirect(callback) {
- return function () {
- var args = nativeJSON.parse(arguments[0]);
- var result = {};
- function sendResponse(response) {
- result.response = nativeJSON.stringify(response);
- }
- var msg = nativeJSON.parse(args.msg);
- result.async = callback.call(this, msg, args.sender, sendResponse.bind(window)) ? true : false;
- return result;
- // Need to handle sending the response async - http://osgvsowi/674353
- }
- }
- function delayInitOnMessage() {
- var wrapOnMessage = Object.defineProperties({}, {
- "addListener": {
- value: function(callback) { nativeMsContentScript.addEventListener("message", callbackIndirect(callback)); },
- configurable: true,
- enumerable: true
- }
- });
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser.runtime, "onMessage", {
- value: wrapOnMessage,
- configurable: true,
- enumerable: true
- });
- return wrapOnMessage;
- }
- function delayInitOnMessageExternal() {
- var wrapOnMessageExternal = Object.defineProperties({}, {
- "addListener": {
- value: function(callback) { nativeMsContentScript.addEventListener("externalMessage", callbackIndirect(callback)); },
- configurable: true,
- enumerable: true
- }
- });
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser.runtime, "onMessageExternal", {
- value: wrapOnMessageExternal,
- configurable: true,
- enumerable: true
- });
- return wrapOnMessageExternal;
- }
- function sendMessageIndirect(/* optional string extensionId, any message, optional object options, optional function responseCallback */) {
- if (arguments.length < 1) {
- return null;
- }
- var index = arguments.length - 1;
- var responseCallback = null;
- if (typeof arguments[index] === "function") {
- responseCallback = arguments[index];
- --index;
- }
- var options = null;
- if (index >= 2) {
- // More than two arguments remain so options param must present.
- options = arguments[index];
- --index;
- }
- else if (index === 2) {
- // The logic: assume that the first param is the optional extension id if it is present and is a string, hence no options argument in this case.
- if (!(arguments[0] === null || typeof arguments[0] === "string")) {
- options = arguments[index];
- --index;
- }
- }
- var message = arguments[index]; // Must be present.
- --index;
- var extensionId = null;
- if (index >= 0) {
- extensionId = arguments[index];
- --index;
- }
- if (index !== -1) {
- // Requires exact number of arguments, no extra arguments allowed.
- return null;
- } else {
- // Always stringify on sendMessage side and always parse on the callback side.
- // Otherwise, callback cannot distinuish between a message sending an object and another message sending its string equivalent.
- if (extensionId !== null) {
- return this.postExternalMessage(extensionId, nativeJSON.stringify(message), responseCallback);
- } else {
- return this.postMessage(nativeJSON.stringify(message), responseCallback);
- }
- }
- };
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser, "runtime", {
- value: wrapRuntime,
- configurable: true,
- enumerable: true
- });
- return wrapRuntime;
- }
- function delayInitStorage() {
- var wrapStorage;
- var hasPermission = ExecuteGenericSynchronousFunction(functionId_permissionsCheckInternal, permissionId_storage);
- if (typeof(hasPermission) == "boolean" && hasPermission) {
- wrapStorage = Object.defineProperties({}, {
- "local": {
- get: delayInitLocal,
- configurable: true,
- enumerable: true
- },
- "onChanged": {
- get: delayInitOnChanged,
- configurable: true,
- enumerable: true
- }
- });
- }
- function parseValueFromStorage(value) {
- // TODO: handle regex http://osgvsowi/2521658
- if ((typeof (value) === "string") && (value.substr(0, 5) === "Date:")) {
- return new Date(JSON.parse(value.substr(5)));
- } else if (typeof (value) === "string") {
- // Everything else uses JSON
- return JSON.parse(value);
- }
- // implicitly return undefined if value wasn't a string
- }
- function delayInitLocal() {
- var wrapLocal = Object.defineProperties({}, {
- "clear": {
- value: storage_local_clear_indirect.bind(nativeMsContentScript),
- configurable: true,
- enumerable: true
- },
- "get": {
- value: storage_local_get_indirect.bind(nativeMsContentScript),
- configurable: true,
- enumerable: true
- },
- "getBytesInUse": {
- value: storage_local_getBytesInUse_indirect.bind(nativeMsContentScript),
- configurable: true,
- enumerable: true
- },
- "remove": {
- value: storage_local_remove_indirect.bind(nativeMsContentScript),
- configurable: true,
- enumerable: true
- },
- "set": {
- value: storage_local_set_indirect.bind(nativeMsContentScript),
- configurable: true,
- enumerable: true
- }
- });
- function TransformValuesForManager(items) {
- var keys = Object.keys(items);
- var values = {};
- for (var i = 0; i < keys.length; i++) {
- var value = items[keys[i]];
- // TODO: handle regex http://osgvsowi/2521658
- if (value instanceof Date) {
- values[keys[i]] = "Date:" + JSON.stringify(value);
- } else if (typeof value === "function") {
- // Handle functions
- values[keys[i]] = "{}";
- } else {
- // All others use JSON
- values[keys[i]] = JSON.stringify(value);
- }
- }
- return values;
- }
- function storage_local_clear_indirect() {
- // TODO (brentcou): http://osgvsowi/319342, implement storage.local.clear
- }
- function storage_local_get_indirect() {
- var expectedArguments = [
- { type: "string or array or object", name: "keys", optional: true },
- { type: "function", name: "callback" }];
- TestParameterTypes("storage.local.get", arguments, expectedArguments);
- if (GetTypeName(arguments[0]) === "array") {
- for (var i = 0; i < arguments[0].length; i++) {
- if (typeof (arguments[0][i]) !== "string") {
- ThrowArgumentError(0, "All items in the array must be strings");
- }
- }
- }
- var keys = null;
- var callbackIndex = 0;
- if (arguments.length == 2) {
- keys = arguments[0];
- callbackIndex = 1;
- }
- if (GetTypeName(keys) === "string") {
- // convert the single string case to an array with one element to simplify back end processing
- keys = [keys];
- }
- if (GetTypeName(keys) === "object") {
- // if it is an object, then the values need transforming the same way they do on set.
- keys = TransformValuesForManager(keys);
- }
- var callback = arguments[callbackIndex];
- var getCallbackWrapper = function (serializedItems) {
- var items = {};
- for (var key in serializedItems) {
- items[key] = parseValueFromStorage(serializedItems[key]);
- }
- callback(items);
- }
- return ExecuteGenericFunction(
- { functionId: functionId_storageLocalGet, component: componentId_manager },
- keys,
- getCallbackWrapper);
- };
- function storage_local_getBytesInUse_indirect() {
- // TODO (brentcou): http://osgvsowi/319318, implement storage.local.getBytesInUse
- }
- function storage_local_remove_indirect() {
- var expectedArguments = [
- { type: "string or array", name: "keys" },
- { type: "function", name: "callback", optional: true }];
- TestParameterTypes("storage.local.remove", arguments, expectedArguments);
- if (GetTypeName(arguments[0]) === "array") {
- for (var i = 0; i < arguments[0].length; i++) {
- if (typeof (arguments[0][i]) !== "string") {
- ThrowArgumentError(0, "All items in the array must be strings");
- }
- }
- }
- var keys = arguments[0];
- if (GetTypeName(keys) === "string") {
- // convert the single string case to an array with one element to simplify back end processing
- keys = [keys];
- }
- var responseCallback = arguments.length > 1 ? arguments[1] : null;
- return ExecuteGenericFunction(
- { functionId: functionId_storageLocalRemove, component: componentId_manager },
- keys,
- responseCallback);
- }
- function storage_local_set_indirect() {
- var expectedArguments = [
- { type: "object", name: "items" },
- { type: "function", name: "callback", optional: true }];
- TestParameterTypes("storage.local.set", arguments, expectedArguments);
- var values = TransformValuesForManager(arguments[0]);
- var responseCallback = arguments.length > 1 ? arguments[1] : null;
- return ExecuteGenericFunction(
- { functionId: functionId_storageLocalSet, component: componentId_manager },
- values,
- responseCallback);
- };
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser.storage, "local", {
- value: wrapLocal,
- configurable: true,
- enumerable: true
- });
- return wrapLocal;
- }
- function delayInitOnChanged() {
- var onChangedObject = StoreNoFilterHandler(eventId_storageOnChanged, function (sender, storageChangeData, listeners) {
- var changes = {};
- for (var key in storageChangeData.changes) {
- var changeData = storageChangeData.changes[key];
- var parsedChangeData = {};
- parsedChangeData.oldValue = parseValueFromStorage(changeData.oldValue);
- parsedChangeData.newValue = parseValueFromStorage(changeData.newValue);
- changes[key] = parsedChangeData;
- }
- for (var i = 0; i < listeners.length; i++) {
- if (typeof (listeners[i]) == "function") {
- listeners[i](changes, "local");
- }
- }
- });
- var wrapOnChanged = Object.defineProperties({}, {
- "addListener": {
- value: onChangedObject.addListener.bind(onChangedObject),
- configurable: true,
- enumerable: true
- },
- "removeListener": {
- value: onChangedObject.removeListener.bind(onChangedObject),
- configurable: true,
- enumerable: true
- },
- "hasListener": {
- value: onChangedObject.hasListener.bind(onChangedObject),
- configurable: true,
- enumerable: true
- }
- });
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser.storage, "onChanged", {
- value: wrapOnChanged,
- configurable: true,
- enumerable: true
- });
- return wrapOnChanged;
- }
- // Replace delayInit with the initialized object to avoid repeated calls to delayInit.
- Object.defineProperty(window.msBrowser, "storage", {
- value: wrapStorage,
- configurable: true,
- enumerable: true
- });
- return wrapStorage;
- }
- function GetTypeName(argument) {
- var type = typeof(argument);
- if (type === "number") {
- if (parseFloat(argument) == parseInt(argument)) {
- type = "integer";
- }
- } else if (type === "object") {
- if (!argument) {
- type = "null";
- } else if (Array.isArray(argument)) {
- type = "array";
- }
- }
- return type;
- }
- function ExecuteGenericSynchronousFunction(functionId, parameters)
- {
- var stringifiedParameters;
- if (GetTypeName(parameters) != "undefined") {
- stringifiedParameters = nativeJSON.stringify(parameters);
- }
- var syncReturnString = nativeMsContentScript.genericSynchronousFunction(functionId, stringifiedParameters);
- var syncReturnValue;
- if (syncReturnString) {
- syncReturnValue = nativeJSON.parse(syncReturnString);
- }
- return syncReturnValue;
- }
- // The functions below are for parameter validation.
- function GetTypeName(argument) {
- var type = typeof (argument);
- if (type === "number") {
- if (parseFloat(argument) == parseInt(argument)) {
- type = "integer";
- }
- } else if (type === "object") {
- if (!argument) {
- type = "null";
- } else if (Array.isArray(argument)) {
- type = "array";
- }
- }
- return type;
- }
- function TestArgumentType(argumentType, expectedArgumentType) {
- if (expectedArgumentType === "any") {
- return true;
- } else {
- var splitArgumentTypes = expectedArgumentType.split(" or ");
- for (var i = 0; i < splitArgumentTypes.length; i++) {
- if (argumentType === splitArgumentTypes[i]) {
- return true;
- }
- }
- }
- return false;
- }
- function ThrowTypeError(functionName, providedArguments, expectedArguments) {
- var argumentString = "";
- for (var i = 0; i < providedArguments.length; i++) {
- if (i > 0) {
- argumentString += ", ";
- }
- argumentString += GetTypeName(providedArguments[i]);
- }
- var expectedArgumentString = "";
- for (var i = 0; i < expectedArguments.length; i++) {
- if (i > 0) {
- expectedArgumentString += ", ";
- }
- if (expectedArguments[i].optional) {
- expectedArgumentString += "optional ";
- }
- expectedArgumentString += expectedArguments[i].type + " " + expectedArguments[i].name;
- }
- throw "Error: Invocation of form " + functionName + "(" + argumentString + ") doesn't match definition " + functionName + "(" + expectedArgumentString + ").";
- }
- function TestParameterTypes(functionName, providedArguments, expectedArguments) {
- var valid = false;
- var skipBits = 0;
- var optionalParameterChecked = true;
- while (!valid && optionalParameterChecked) {
- var currentArgumentIndex = 0;
- var currentExpectedIndex = 0;
- var optionalParameterIndex = 0;
- optionalParameterChecked = false;
- valid = true; // Innocent until proven guilty.
- for (var currentExpectedIndex = 0; currentExpectedIndex < expectedArguments.length && currentArgumentIndex < providedArguments.length && valid; currentExpectedIndex++) {
- if (expectedArguments[currentExpectedIndex].optional) {
- var bitToCheck = 1 << optionalParameterIndex;
- optionalParameterIndex++;
- if (skipBits & bitToCheck === bitToCheck) {
- continue;
- } else {
- optionalParameterChecked = true;
- }
- }
- var argType = GetTypeName(providedArguments[currentArgumentIndex]);
- if (TestArgumentType(argType, expectedArguments[currentExpectedIndex].type)) {
- currentArgumentIndex++;
- } else if (!expectedArguments[currentExpectedIndex].optional) {
- valid = false;
- break;
- } else if (argType === "null" || argType === "undefined") {
- // optional arguments can have null or undefined in their place.
- currentArgumentIndex++;
- }
- }
- if (valid) {
- for (; currentExpectedIndex < expectedArguments.length; currentExpectedIndex++) {
- if (!expectedArguments[currentExpectedIndex].optional) {
- valid = false;
- break;
- }
- }
- }
- if (valid && currentArgumentIndex < providedArguments.length) {
- valid = false;
- }
- skipBits++;
- }
- if (!valid) {
- ThrowTypeError(functionName, providedArguments, expectedArguments);
- }
- return valid;
- }
- function ThrowArgumentError(argumentIndex, errorString) {
- throw "Error: Invalid value for argument " + (argumentIndex + 1) + ". " + errorString + ".";
- }
- function TestObjectProperties(argumentIndex, objectToTest, propertyInformation) {
- var errorString = "";
- var valid = true; // Innocent until proven guilty.
- var requiredProperties = {};
- for (var i = 0; i < propertyInformation.length; i++) {
- if (!propertyInformation[i].optional) {
- requiredProperties[propertyInformation[i].name] = true;
- }
- }
- var stringPrefix = "";
- for (var prop in objectToTest) {
- var found = false;
- for (var i = 0; i < propertyInformation.length; i++) {
- if (prop === propertyInformation[i].name) {
- if (!propertyInformation[i].optional) {
- requiredProperties[propertyInformation[i].name] = false;
- }
- var argType = GetTypeName(objectToTest[prop]);
- if (TestArgumentType(argType, propertyInformation[i].type)) {
- if (propertyInformation[i].validator) {
- var validationError = propertyInformation[i].validator(objectToTest[prop]);
- if (validationError) {
- errorString += stringPrefix + "Property '" + prop + "': " + validationError;
- stringPrefix = ", ";
- valid = false;
- }
- }
- } else if (!propertyInformation[i].optional || (argType !== "null" && argType !== "undefined")) {
- if (propertyInformation[i].type.indexOf(" or ") === -1) {
- errorString += stringPrefix + "Property '" + prop + "': Expected '" + propertyInformation[i].type + "' but got '" + GetTypeName(objectToTest[prop]) + "'";
- } else {
- errorString += stringPrefix + "Property '" + prop + "': Value does not match any valid type choices";
- }
- stringPrefix = ", ";
- valid = false;
- }
- found = true;
- break;
- }
- }
- if (!found) {
- errorString += stringPrefix + "Property '" + prop + "': Unexpected property";
- stringPrefix = ", ";
- valid = false;
- }
- }
- for (var prop in requiredProperties) {
- if (requiredProperties[prop]) {
- errorString += stringPrefix + "Property '" + prop + "': Property is required";
- stringPrefix = ", ";
- valid = false;
- }
- }
- if (!valid) {
- ThrowArgumentError(argumentIndex, errorString);
- }
- return valid;
- }
- function StoreCallback(callback) {
- var storedId = nextCallback;
- storedCallbacks[nextCallback++] = callback;
- return storedId;
- }
- function InvokeCallback(callbackId, parameterString) {
- if (callbackId < storedCallbacks.length && storedCallbacks[callbackId]) {
- var parameters;
- if (parameterString) {
- parameters = nativeJSON.parse(parameterString);
- }
- storedCallbacks[callbackId](parameters);
- storedCallbacks[callbackId] = null;
- } else {
- throw "invalid callback id";
- }
- }
- function StoreNoFilterHandler(eventId, handlerFunc) {
- var listenerHolder = {
- event: eventId, handler: handlerFunc, listeners: [],
- addListener: function (listener) {
- if (arguments.length > 1) {
- throw "Error: This event does not support filters.";
- }
- this.listeners.push(listener);
- if (this.listeners.length == 1) {
- ExecuteGenericFunction({ functionId: functionId_notifyEventListener, component: componentId_manager }, this.event);
- }
- },
- removeListener: function (listener) {
- for (var i = 0; i < this.listeners.length; i++) {
- if (this.listeners[i] === listener) {
- this.listeners.splice(i, 1);
- }
- }
- },
- hasListener: function (listener) {
- for (var i = 0; i < this.listeners.length; i++) {
- if (this.listeners[i] === listener) {
- return true;
- }
- }
- return false;
- }
- };
- storedHandlers.push(listenerHolder);
- return listenerHolder;
- }
- function InvokeHandler(eventId, sender, parameterString) {
- for (var i = 0; i < storedHandlers.length; i++) {
- if (eventId === storedHandlers[i].event) {
- var handler = storedHandlers[i].handler;
- var parameters;
- if (parameterString) {
- parameters = nativeJSON.parse(parameterString);
- }
- handler(sender, parameters, storedHandlers[i].listeners);
- return true;
- }
- }
- return false;
- }
- function getRouterAddress(destination) {
- var routerAddress = {};
- routerAddress.a = destination.component << 28;
- routerAddress.a |= destination.extensionId ?
- nativeMsContentScript.extensionIdToShortId(destination.extensionId) : 0xFFFF;
- routerAddress.b = 0;
- routerAddress.c = 0;
- routerAddress.d = destination.functionId;
- return routerAddress;
- }
- function ExecuteGenericFunction(destination, parameters, callback) {
- if (!callback && typeof (parameters) === "function") {
- callback = parameters;
- parameters = null;
- }
- var callbackId;
- if (callback) {
- callbackId = StoreCallback(callback);
- }
- var stringifiedParameters;
- if (GetTypeName(parameters) != "undefined") {
- stringifiedParameters = nativeJSON.stringify(parameters);
- }
- var routerAddress = getRouterAddress(destination);
- nativeMsContentScript.genericFunction(routerAddress, stringifiedParameters, callbackId);
- }
- nativeMsContentScript.registerGenericFunctionCallbackHandler(InvokeCallback);
- nativeMsContentScript.registerGenericPersistentCallbackHandler(InvokeHandler);
- })(window.msContentScript);
Add Comment
Please, Sign In to add comment